From f02ed1706003f5e4de633f3459c82f9817c7d715 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 5 Dec 2025 16:13:57 +0100 Subject: [PATCH 01/46] kk --- Cargo.lock | 77 +- Cargo.toml | 176 +- crates/docs_rs_build_queue/Cargo.toml | 16 + crates/docs_rs_build_queue/src/config.rs | 17 + crates/docs_rs_build_queue/src/lib.rs | 1137 +++++++++++ crates/docs_rs_build_queue/src/metrics.rs | 20 + crates/docs_rs_build_queue/src/rebuilds.rs | 139 ++ crates/docs_rs_database/Cargo.toml | 25 + crates/docs_rs_database/src/config.rs | 18 + crates/docs_rs_database/src/lib.rs | 247 +++ crates/docs_rs_database/src/service_config.rs | 45 + crates/docs_rs_database/src/types/mod.rs | 1 + .../docs_rs_database/src}/types/version.rs | 4 +- crates/docs_rs_env_vars/Cargo.toml | 8 + crates/docs_rs_env_vars/src/lib.rs | 37 + crates/docs_rs_opentelemetry/Cargo.toml | 14 + crates/docs_rs_opentelemetry/src/config.rs | 15 + .../docs_rs_opentelemetry/src/lib.rs | 11 +- crates/docs_rs_watcher/Cargo.toml | 14 + crates/docs_rs_watcher/src/config.rs | 35 + {src => crates/docs_rs_watcher/src}/index.rs | 0 crates/docs_rs_watcher/src/lib.rs | 109 + .../src}/repositories/github.rs | 0 .../src}/repositories/gitlab.rs | 0 .../docs_rs_watcher/src}/repositories/mod.rs | 0 .../src}/repositories/updater.rs | 0 src/build_queue.rs | 1780 ----------------- src/config.rs | 41 +- src/db/types/mod.rs | 1 - src/lib.rs | 7 - src/metrics/mod.rs | 1 - src/utils/daemon.rs | 102 +- 32 files changed, 2075 insertions(+), 2022 deletions(-) create mode 100644 crates/docs_rs_build_queue/Cargo.toml create mode 100644 crates/docs_rs_build_queue/src/config.rs create mode 100644 crates/docs_rs_build_queue/src/lib.rs create mode 100644 crates/docs_rs_build_queue/src/metrics.rs create mode 100644 crates/docs_rs_build_queue/src/rebuilds.rs create mode 100644 crates/docs_rs_database/Cargo.toml create mode 100644 crates/docs_rs_database/src/config.rs create mode 100644 crates/docs_rs_database/src/lib.rs create mode 100644 crates/docs_rs_database/src/service_config.rs create mode 100644 crates/docs_rs_database/src/types/mod.rs rename {src/db => crates/docs_rs_database/src}/types/version.rs (95%) create mode 100644 crates/docs_rs_env_vars/Cargo.toml create mode 100644 crates/docs_rs_env_vars/src/lib.rs create mode 100644 crates/docs_rs_opentelemetry/Cargo.toml create mode 100644 crates/docs_rs_opentelemetry/src/config.rs rename src/metrics/otel.rs => crates/docs_rs_opentelemetry/src/lib.rs (92%) create mode 100644 crates/docs_rs_watcher/Cargo.toml create mode 100644 crates/docs_rs_watcher/src/config.rs rename {src => crates/docs_rs_watcher/src}/index.rs (100%) create mode 100644 crates/docs_rs_watcher/src/lib.rs rename {src => crates/docs_rs_watcher/src}/repositories/github.rs (100%) rename {src => crates/docs_rs_watcher/src}/repositories/gitlab.rs (100%) rename {src => crates/docs_rs_watcher/src}/repositories/mod.rs (100%) rename {src => crates/docs_rs_watcher/src}/repositories/updater.rs (100%) delete mode 100644 src/build_queue.rs diff --git a/Cargo.lock b/Cargo.lock index 59a57c71e..ffe8964cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1961,12 +1961,11 @@ dependencies = [ "clap", "comrak", "constant_time_eq", - "crates-index", - "crates-index-diff", "criterion", "dashmap", "derive_builder", "derive_more 2.0.1", + "docs_rs_env_vars", "docsrs-metadata", "flate2", "fn-error-context", @@ -2031,6 +2030,80 @@ dependencies = [ "zstd", ] +[[package]] +name = "docs_rs_build_queue" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_opentelemetry", + "futures-util", + "opentelemetry", + "serde", + "sqlx", + "tracing", +] + +[[package]] +name = "docs_rs_database" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode 2.0.1", + "derive_more 2.0.1", + "docs_rs_env_vars", + "docs_rs_opentelemetry", + "futures-util", + "opentelemetry", + "semver", + "serde", + "serde_json", + "serde_with", + "sqlx", + "strum", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "docs_rs_env_vars" +version = "0.1.0" +dependencies = [ + "anyhow", + "tracing", +] + +[[package]] +name = "docs_rs_opentelemetry" +version = "0.1.0" +dependencies = [ + "anyhow", + "docs_rs_env_vars", + "opentelemetry", + "opentelemetry-otlp", + "opentelemetry-resource-detectors", + "opentelemetry_sdk", + "tracing", + "url", +] + +[[package]] +name = "docs_rs_watcher" +version = "0.1.0" +dependencies = [ + "anyhow", + "crates-index", + "crates-index-diff", + "docs_rs_build_queue", + "docs_rs_env_vars", + "tokio", + "tracing", + "url", +] + [[package]] name = "docsrs-metadata" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 21cda216c..73907f31e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,124 +9,126 @@ build = "build.rs" edition = "2024" [workspace] -exclude = [ - "ignored", - "tests", - ".workspace", - ".rustwide-docker", -] +members = ["crates/*"] -[dependencies] -sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } -log = "0.4" -tracing = "0.1.37" -tracing-subscriber = { version = "0.3.20", default-features = false, features = ["ansi", "fmt", "json", "env-filter", "tracing-log"] } -tracing-log = "0.2.0" -regex = "1" -clap = { version = "4.0.22", features = [ "derive" ] } -crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } -rayon = "1.6.1" -num_cpus = "1.15.0" -crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} -reqwest = { version = "0.12", features = ["json", "gzip"] } -semver = { version = "1.0.4", features = ["serde"] } -slug = "0.1.1" -sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "sqlite", "chrono" ] } -url = { version = "2.1.1", features = ["serde"] } -docsrs-metadata = { path = "crates/metadata" } +[workspace.dependencies] anyhow = { version = "1.0.42", features = ["backtrace"]} -thiserror = "2.0.3" -comrak = { version = "0.48.0", default-features = false } -syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } -toml = "0.9.2" +bincode = "2.0.1" +chrono = { version = "0.4.11", default-features = false, features = ["clock", "serde"] } +derive_more = { version = "2.0.0", features = ["display", "deref", "from", "into", "from_str"] } +futures-util = "0.3.5" opentelemetry = "0.31.0" opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics"] } opentelemetry-resource-detectors = "0.10.0" opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } -rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } -mime_guess = "2" -zstd = "0.13.0" -flate2 = "1.1.1" -hostname = "0.4.0" -path-slash = "0.2.0" -base64 = "0.22" +semver = { version = "1.0.4", features = ["serde"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +serde_with = "3.4.0" +sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "sqlite", "chrono" ] } strum = { version = "0.27.0", features = ["derive"] } -lol_html = "2.0.0" -font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } -dashmap = "6.0.0" -zip = {version = "6.0.0", default-features = false, features = ["bzip2"]} -bzip2 = "0.6.0" -getrandom = "0.3.1" -itertools = { version = "0.14.0" } -hex = "0.4.3" -derive_more = { version = "2.0.0", features = ["display", "deref", "from", "into", "from_str"] } -sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } -derive_builder = "0.20.2" +thiserror = "2.0.3" +tokio = { version = "1.0", features = ["rt-multi-thread", "signal", "macros", "process", "sync"] } +tracing = "0.1.37" +url = { version = "2.1.1", features = ["serde"] } -# Async +[dependencies] +anyhow = { workspace = true } +askama = "0.14.0" async-compression = { version = "0.4.32", features = ["tokio", "bzip2", "zstd", "gzip"] } -tokio = { version = "1.0", features = ["rt-multi-thread", "signal", "macros", "process", "sync"] } -tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } -tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } -futures-util = "0.3.5" async-stream = "0.3.5" +async-trait = "0.1.83" aws-config = { version = "1.0.0", default-features = false, features = ["rt-tokio", "default-https-client"] } aws-sdk-s3 = "1.3.0" aws-smithy-types-convert = { version = "0.60.0", features = ["convert-chrono"] } -http = "1.0.0" - -# Data serialization and deserialization -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" -serde_with = "3.4.0" -bincode = "2.0.1" - -# axum dependencies -async-trait = "0.1.83" axum = { version = "0.8.1", features = ["macros"] } axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middleware"] } -tower = "0.5.1" -tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] } +base64 = "0.22" +bincode = { workspace = true } +bzip2 = "0.6.0" +chrono = { workspace = true } +clap = { version = "4.0.22", features = [ "derive" ] } +comrak = { version = "0.48.0", default-features = false } +constant_time_eq = "0.4.2" +dashmap = "6.0.0" +derive_builder = "0.20.2" +derive_more = { workspace = true } +docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } +docsrs-metadata = { path = "crates/metadata" } +flate2 = "1.1.1" +fn-error-context = "0.2.0" +font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } +futures-util = { workspace = true } +getrandom = "0.3.1" +hex = "0.4.3" +hostname = "0.4.0" +http = "1.0.0" +itertools = { version = "0.14.0" } +log = "0.4" +lol_html = "2.0.0" +md5 = "0.8.0" mime = "0.3.16" +mime_guess = "2" +num_cpus = "1.15.0" +opentelemetry = { workspace = true } +opentelemetry-otlp = { workspace = true } +opentelemetry-resource-detectors = { workspace = true } +opentelemetry_sdk = { workspace = true } +path-slash = "0.2.0" percent-encoding = "2.2.0" - +phf = "0.13.1" +rayon = "1.6.1" +regex = "1" +reqwest = { version = "0.12", features = ["json", "gzip"] } +rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } +semver = { workspace = true } +sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } +serde = { workspace = true } +serde_json = { workspace = true } +serde_with = { workspace = true } +slug = "0.1.1" +sqlx = { workspace = true } +strum = { workspace = true } +syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } +sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } tempfile = "3.1.0" -fn-error-context = "0.2.0" - -# Templating -askama = "0.14.0" +thiserror = { workspace = true } +tokio = { workspace = true } +tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } +toml = "0.9.2" +tower = "0.5.1" +tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] } +tracing = { workspace = true } +tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } +tracing-log = "0.2.0" +tracing-subscriber = { version = "0.3.20", default-features = false, features = ["ansi", "fmt", "json", "env-filter", "tracing-log"] } +url = { workspace = true } walkdir = "2" -phf = "0.13.1" - -# Date and Time utilities -chrono = { version = "0.4.11", default-features = false, features = ["clock", "serde"] } - -# Transitive dependencies we don't use directly but need to have specific versions of -constant_time_eq = "0.4.2" -md5 = "0.8.0" +zip = {version = "6.0.0", default-features = false, features = ["bzip2"]} +zstd = "0.13.0" [dev-dependencies] +aws-smithy-runtime = {version = "1.0.1", features = ["client", "test-util"]} +aws-smithy-types = "1.0.1" criterion = "0.8.0" -kuchikiki = "0.8" http-body-util = "0.1.0" -rand = "0.9" +indoc = "2.0.0" +kuchikiki = "0.8" mockito = "1.0.2" -test-case = "3.0.0" -tower = { version = "0.5.1", features = ["util"] } opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio", "testing"] } -aws-smithy-types = "1.0.1" -aws-smithy-runtime = {version = "1.0.1", features = ["client", "test-util"]} -indoc = "2.0.0" pretty_assertions = "1.4.0" +rand = "0.9" +test-case = "3.0.0" +tower = { version = "0.5.1", features = ["util"] } [build-dependencies] -time = "0.3" -md5 = "0.8.0" -phf_codegen = "0.13" -walkdir = "2" anyhow = { version = "1.0.42", features = ["backtrace"] } grass = { version = "0.13.1", default-features = false } +md5 = "0.8.0" +phf_codegen = "0.13" syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } +time = "0.3" +walkdir = "2" [package.metadata.cargo-machete] ignored = ["phf"] diff --git a/crates/docs_rs_build_queue/Cargo.toml b/crates/docs_rs_build_queue/Cargo.toml new file mode 100644 index 000000000..ad75e1b2d --- /dev/null +++ b/crates/docs_rs_build_queue/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "docs_rs_build_queue" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_database = { path = "../docs_rs_database" } +opentelemetry = { workspace = true } +futures-util = { workspace = true } +serde = { workspace = true } +sqlx = { workspace = true } +tracing = { workspace = true } +chrono = { workspace = true } diff --git a/crates/docs_rs_build_queue/src/config.rs b/crates/docs_rs_build_queue/src/config.rs new file mode 100644 index 000000000..074fd118b --- /dev/null +++ b/crates/docs_rs_build_queue/src/config.rs @@ -0,0 +1,17 @@ +use docs_rs_env_vars::{env, maybe_env}; + +#[derive(Debug)] +pub struct Config { + pub(crate) build_attempts: u16, + // automatic rebuild configuration + pub(crate) max_queued_rebuilds: Option, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, + max_queued_rebuilds: maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?, + }) + } +} diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs new file mode 100644 index 000000000..6cc30932b --- /dev/null +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -0,0 +1,1137 @@ +mod config; +mod metrics; +mod rebuilds; + +use anyhow::Result; +use chrono::NaiveDate; +use config::Config; +use docs_rs_database::{ + Pool, + service_config::{ConfigName, get_config, set_config}, + types::version::Version, +}; +use docs_rs_opentelemetry::AnyMeterProvider; +use futures_util::{StreamExt as _, TryStreamExt as _}; +use std::{collections::HashMap, sync::Arc}; +use tracing::{info, instrument}; + +pub const PRIORITY_DEFAULT: i32 = 0; +/// Used for workspaces to avoid blocking the queue (done through the cratesfyi CLI, not used in code) +pub const PRIORITY_DEPRIORITIZED: i32 = 1; +/// Rebuilds triggered from crates.io, see issue #2442 +pub const PRIORITY_MANUAL_FROM_CRATES_IO: i32 = 5; +/// Used for rebuilds queued through cratesfyi for crate versions failed due to a broken Rustdoc nightly version. +/// Note: a broken rustdoc version does not necessarily imply a failed build. +pub const PRIORITY_BROKEN_RUSTDOC: i32 = 10; +/// Used by the synchronize cratesfyi command when queueing builds that are in the crates.io index but not in the database. +pub const PRIORITY_CONSISTENCY_CHECK: i32 = 15; +/// The static priority for background rebuilds, used when queueing rebuilds, and when rendering them collapsed in the UI. +pub const PRIORITY_CONTINUOUS: i32 = 20; + +#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] +pub(crate) struct QueuedCrate { + #[serde(skip)] + id: i32, + pub(crate) name: String, + pub(crate) version: Version, + pub(crate) priority: i32, + pub(crate) registry: Option, + pub(crate) attempt: i32, +} + +#[derive(Debug)] +pub struct AsyncBuildQueue { + pub(crate) db: Pool, + queue_metrics: metrics::BuildQueueMetrics, + // builder_metrics: Arc, + // cdn_metrics: Arc, + max_attempts: i32, +} + +impl AsyncBuildQueue { + pub fn new(db: Pool, config: Arc, otel_meter_provider: &AnyMeterProvider) -> Self { + AsyncBuildQueue { + max_attempts: config.build_attempts.into(), + db, + queue_metrics: metrics::BuildQueueMetrics::new(otel_meter_provider), + } + } + + pub async fn add_crate( + &self, + name: &str, + version: &Version, + priority: i32, + registry: Option<&str>, + ) -> Result<()> { + let mut conn = self.db.get_async().await?; + + sqlx::query!( + "INSERT INTO queue (name, version, priority, registry) + VALUES ($1, $2, $3, $4) + ON CONFLICT (name, version) DO UPDATE + SET priority = EXCLUDED.priority, + registry = EXCLUDED.registry, + attempt = 0, + last_attempt = NULL + ;", + name, + version as _, + priority, + registry, + ) + .execute(&mut *conn) + .await?; + + Ok(()) + } + + pub async fn pending_count(&self) -> Result { + Ok(self + .pending_count_by_priority() + .await? + .values() + .sum::()) + } + + pub async fn prioritized_count(&self) -> Result { + Ok(self + .pending_count_by_priority() + .await? + .iter() + .filter(|&(&priority, _)| priority <= 0) + .map(|(_, count)| count) + .sum::()) + } + + pub async fn pending_count_by_priority(&self) -> Result> { + let mut conn = self.db.get_async().await?; + + Ok(sqlx::query!( + r#" + SELECT + priority, + COUNT(*) as "count!" + FROM queue + WHERE attempt < $1 + GROUP BY priority"#, + self.max_attempts, + ) + .fetch(&mut *conn) + .map_ok(|row| (row.priority, row.count as usize)) + .try_collect() + .await?) + } + + pub async fn failed_count(&self) -> Result { + let mut conn = self.db.get_async().await?; + + Ok(sqlx::query_scalar!( + r#"SELECT COUNT(*) as "count!" FROM queue WHERE attempt >= $1;"#, + self.max_attempts, + ) + .fetch_one(&mut *conn) + .await? as usize) + } + + pub async fn queued_crates(&self) -> Result> { + let mut conn = self.db.get_async().await?; + + Ok(sqlx::query_as!( + QueuedCrate, + r#"SELECT + id, + name, + version as "version: Version", + priority, + registry, + attempt + FROM queue + WHERE attempt < $1 + ORDER BY priority ASC, attempt ASC, id ASC"#, + self.max_attempts + ) + .fetch_all(&mut *conn) + .await?) + } + + pub async fn has_build_queued(&self, name: &str, version: &Version) -> Result { + let mut conn = self.db.get_async().await?; + Ok(sqlx::query_scalar!( + "SELECT id + FROM queue + WHERE + attempt < $1 AND + name = $2 AND + version = $3 + ", + self.max_attempts, + name, + version as _, + ) + .fetch_optional(&mut *conn) + .await? + .is_some()) + } + + pub async fn remove_crate_from_queue(&self, name: &str) -> Result<()> { + let mut conn = self.db.get_async().await?; + sqlx::query!( + "DELETE + FROM queue + WHERE name = $1 + ", + name + ) + .execute(&mut *conn) + .await?; + + Ok(()) + } + + pub async fn remove_version_from_queue(&self, name: &str, version: &Version) -> Result<()> { + let mut conn = self.db.get_async().await?; + sqlx::query!( + "DELETE + FROM queue + WHERE + name = $1 AND + version = $2 + ", + name, + version as _, + ) + .execute(&mut *conn) + .await?; + + Ok(()) + } + + /// Checks for the lock and returns whether it currently exists. + pub async fn is_locked(&self) -> Result { + let mut conn = self.db.get_async().await?; + + Ok(get_config::(&mut conn, ConfigName::QueueLocked) + .await? + .unwrap_or(false)) + } + + /// lock the queue. Daemon will check this lock and stop operating if it exists. + pub async fn lock(&self) -> Result<()> { + let mut conn = self.db.get_async().await?; + set_config(&mut conn, ConfigName::QueueLocked, true).await + } + + /// unlock the queue. + pub async fn unlock(&self) -> Result<()> { + let mut conn = self.db.get_async().await?; + set_config(&mut conn, ConfigName::QueueLocked, false).await + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::db::types::BuildStatus; +// use crate::test::{FakeBuild, KRATE, TestEnvironment, V1, V2}; +// use chrono::Utc; +// use std::time::Duration; + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_rebuild_when_old() -> Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .max_queued_rebuilds(Some(100)) +// .build()?, +// ) +// .await?; + +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .builds(vec![ +// FakeBuild::default().rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)"), +// ]) +// .create() +// .await?; + +// let build_queue = env.async_build_queue(); +// assert!(build_queue.queued_crates().await?.is_empty()); + +// let mut conn = env.async_db().async_conn().await; +// queue_rebuilds(&mut conn, env.config(), build_queue).await?; + +// let queue = build_queue.queued_crates().await?; +// assert_eq!(queue.len(), 1); +// assert_eq!(queue[0].name, "foo"); +// assert_eq!(queue[0].version, V1); +// assert_eq!(queue[0].priority, PRIORITY_CONTINUOUS); + +// Ok(()) +// } + +// /// Verifies whether a rebuild is queued for all releases with the latest build performed with a specific nightly version of rustdoc +// #[tokio::test(flavor = "multi_thread")] +// async fn test_rebuild_broken_rustdoc_specific_date_simple() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// // Matrix of test builds (crate name, nightly date, version) +// let build_matrix = [ +// // Should be skipped since this is not the latest build for this release +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), V1), +// // All those should match +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V2), +// ("foo2", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), +// // Should be skipped since the nightly doesn't match +// ("foo2", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V2), +// ]; +// for build in build_matrix.into_iter() { +// let (crate_name, nightly, version) = build; +// env.fake_release() +// .await +// .name(crate_name) +// .version(version) +// .builds(vec![ +// FakeBuild::default() +// .rustc_version( +// format!( +// "rustc 1.84.0-nightly (e7c0d2750 {})", +// nightly.format("%Y-%m-%d") +// ) +// .as_str(), +// ) +// .build_status(BuildStatus::Failure), +// ]) +// .create() +// .await?; +// } + +// let build_queue = env.async_build_queue(); +// assert!(build_queue.queued_crates().await?.is_empty()); + +// let mut conn = env.async_db().async_conn().await; +// queue_rebuilds_faulty_rustdoc( +// &mut conn, +// build_queue, +// &NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), +// &None, +// ) +// .await?; + +// let queue = build_queue.queued_crates().await?; +// assert_eq!(queue.len(), 3); +// assert_eq!(queue[0].name, "foo1"); +// assert_eq!(queue[0].version, V1); +// assert_eq!(queue[0].priority, PRIORITY_BROKEN_RUSTDOC); +// assert_eq!(queue[1].name, "foo1"); +// assert_eq!(queue[1].version, V2); +// assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); +// assert_eq!(queue[2].name, "foo2"); +// assert_eq!(queue[2].version, V1); +// assert_eq!(queue[2].priority, PRIORITY_BROKEN_RUSTDOC); + +// Ok(()) +// } + +// /// Verifies whether a rebuild is NOT queued for any crate if the nightly specified doesn't match any latest build of any release +// #[tokio::test(flavor = "multi_thread")] +// async fn test_rebuild_broken_rustdoc_specific_date_skipped() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// // Matrix of test builds (crate name, nightly date, version) +// let build_matrix = [ +// // Should be skipped since this is not the latest build for this release even if the nightly matches +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V1), +// // Should be skipped since the nightly doesn't match +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), +// // Should be skipped since the nightly doesn't match +// ("foo2", NaiveDate::from_ymd_opt(2020, 10, 4).unwrap(), V1), +// ]; +// for build in build_matrix.into_iter() { +// let (crate_name, nightly, version) = build; +// env.fake_release() +// .await +// .name(crate_name) +// .version(version) +// .builds(vec![ +// FakeBuild::default() +// .rustc_version( +// format!( +// "rustc 1.84.0-nightly (e7c0d2750 {})", +// nightly.format("%Y-%m-%d") +// ) +// .as_str(), +// ) +// .build_status(BuildStatus::Failure), +// ]) +// .create() +// .await?; +// } + +// let build_queue = env.async_build_queue(); +// assert!(build_queue.queued_crates().await?.is_empty()); + +// let mut conn = env.async_db().async_conn().await; +// queue_rebuilds_faulty_rustdoc( +// &mut conn, +// build_queue, +// &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), +// &None, +// ) +// .await?; + +// let queue = build_queue.queued_crates().await?; +// assert_eq!(queue.len(), 0); + +// Ok(()) +// } + +// /// Verifies whether a rebuild is queued for all releases with the latest build performed with a nightly version between two dates +// #[tokio::test(flavor = "multi_thread")] +// async fn test_rebuild_broken_rustdoc_date_range() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// // Matrix of test builds (crate name, nightly date, version) +// let build_matrix = [ +// // Should be skipped since this is not the latest build for this release +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), V1), +// // All those should match +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), +// ("foo1", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V2), +// ("foo2", NaiveDate::from_ymd_opt(2020, 10, 4).unwrap(), V1), +// // Should be skipped since the nightly doesn't match (end date is exclusive) +// ("foo2", NaiveDate::from_ymd_opt(2020, 10, 5).unwrap(), V2), +// ]; +// for build in build_matrix.into_iter() { +// let (crate_name, nightly, version) = build; +// env.fake_release() +// .await +// .name(crate_name) +// .version(version) +// .builds(vec![ +// FakeBuild::default() +// .rustc_version( +// format!( +// "rustc 1.84.0-nightly (e7c0d2750 {})", +// nightly.format("%Y-%m-%d") +// ) +// .as_str(), +// ) +// .build_status(BuildStatus::Failure), +// ]) +// .create() +// .await?; +// } + +// let build_queue = env.async_build_queue(); +// assert!(build_queue.queued_crates().await?.is_empty()); + +// let mut conn = env.async_db().async_conn().await; +// queue_rebuilds_faulty_rustdoc( +// &mut conn, +// build_queue, +// &NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), +// &NaiveDate::from_ymd_opt(2020, 10, 5), +// ) +// .await?; + +// let queue = build_queue.queued_crates().await?; +// assert_eq!(queue.len(), 3); +// assert_eq!(queue[0].name, "foo1"); +// assert_eq!(queue[0].version, V1); +// assert_eq!(queue[0].priority, PRIORITY_BROKEN_RUSTDOC); +// assert_eq!(queue[1].name, "foo1"); +// assert_eq!(queue[1].version, V2); +// assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); +// assert_eq!(queue[2].name, "foo2"); +// assert_eq!(queue[2].version, V1); +// assert_eq!(queue[2].priority, PRIORITY_BROKEN_RUSTDOC); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_still_rebuild_when_full_with_failed() -> Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .max_queued_rebuilds(Some(1)) +// .build()?, +// ) +// .await?; + +// let build_queue = env.async_build_queue(); +// build_queue +// .add_crate("foo1", &V1, PRIORITY_CONTINUOUS, None) +// .await?; +// build_queue +// .add_crate("foo2", &V1, PRIORITY_CONTINUOUS, None) +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!("UPDATE queue SET attempt = 99") +// .execute(&mut *conn) +// .await?; + +// assert_eq!(build_queue.queued_crates().await?.len(), 0); + +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .builds(vec![ +// FakeBuild::default().rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)"), +// ]) +// .create() +// .await?; + +// let build_queue = env.async_build_queue(); +// queue_rebuilds(&mut conn, env.config(), build_queue).await?; + +// assert_eq!(build_queue.queued_crates().await?.len(), 1); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_dont_rebuild_when_full() -> Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .max_queued_rebuilds(Some(1)) +// .build()?, +// ) +// .await?; + +// let build_queue = env.async_build_queue(); +// build_queue +// .add_crate("foo1", &V1, PRIORITY_CONTINUOUS, None) +// .await?; +// build_queue +// .add_crate("foo2", &V1, PRIORITY_CONTINUOUS, None) +// .await?; + +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .builds(vec![ +// FakeBuild::default().rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)"), +// ]) +// .create() +// .await?; + +// let build_queue = env.async_build_queue(); +// assert_eq!(build_queue.queued_crates().await?.len(), 2); + +// let mut conn = env.async_db().async_conn().await; +// queue_rebuilds(&mut conn, env.config(), build_queue).await?; + +// assert_eq!(build_queue.queued_crates().await?.len(), 2); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_add_duplicate_doesnt_fail_last_priority_wins() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// let queue = env.async_build_queue(); + +// queue.add_crate("some_crate", &V1, 0, None).await?; +// queue.add_crate("some_crate", &V1, 9, None).await?; + +// let queued_crates = queue.queued_crates().await?; +// assert_eq!(queued_crates.len(), 1); +// assert_eq!(queued_crates[0].priority, 9); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_add_duplicate_resets_attempts_and_priority() -> Result<()> { +// let env = +// TestEnvironment::with_config(TestEnvironment::base_config().build_attempts(5).build()?) +// .await?; + +// let queue = env.async_build_queue(); + +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// " +// INSERT INTO queue (name, version, priority, attempt, last_attempt ) +// VALUES ('failed_crate', $1, 0, 99, NOW())", +// V1 as _ +// ) +// .execute(&mut *conn) +// .await?; + +// assert_eq!(queue.pending_count().await?, 0); + +// queue.add_crate("failed_crate", &V1, 9, None).await?; + +// assert_eq!(queue.pending_count().await?, 1); + +// let row = sqlx::query!( +// "SELECT priority, attempt, last_attempt +// FROM queue +// WHERE name = $1 AND version = $2", +// "failed_crate", +// V1 as _ +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(row.priority, 9); +// assert_eq!(row.attempt, 0); +// assert!(row.last_attempt.is_none()); +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_has_build_queued() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// let queue = env.async_build_queue(); + +// queue.add_crate("dummy", &V1, 0, None).await?; + +// let mut conn = env.async_db().async_conn().await; +// assert!(queue.has_build_queued("dummy", &V1).await.unwrap()); + +// sqlx::query!("UPDATE queue SET attempt = 6") +// .execute(&mut *conn) +// .await +// .unwrap(); + +// assert!(!queue.has_build_queued("dummy", &V1).await.unwrap()); + +// Ok(()) +// } + +// #[test] +// fn test_wait_between_build_attempts() -> Result<()> { +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .build_attempts(99) +// .delay_between_build_attempts(Duration::from_secs(1)) +// .build()?, +// )?; + +// let runtime = env.runtime(); + +// let queue = env.build_queue(); + +// queue.add_crate("krate", &V1, 0, None)?; + +// // first let it fail +// queue.process_next_crate(|krate| { +// assert_eq!(krate.name, "krate"); +// anyhow::bail!("simulate a failure"); +// })?; + +// queue.process_next_crate(|_| { +// // this can't happen since we didn't wait between attempts +// unreachable!(); +// })?; + +// runtime.block_on(async { +// // fake the build-attempt timestamp so it's older +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// "UPDATE queue SET last_attempt = $1", +// Utc::now() - chrono::Duration::try_seconds(60).unwrap() +// ) +// .execute(&mut *conn) +// .await +// })?; + +// let mut handled = false; +// // now we can process it again +// queue.process_next_crate(|krate| { +// assert_eq!(krate.name, "krate"); +// handled = true; +// Ok(BuildPackageSummary::default()) +// })?; + +// assert!(handled); + +// Ok(()) +// } + +// #[test] +// fn test_add_and_process_crates() -> Result<()> { +// const MAX_ATTEMPTS: u16 = 3; +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .build_attempts(MAX_ATTEMPTS) +// .delay_between_build_attempts(Duration::ZERO) +// .build()?, +// )?; + +// let queue = env.build_queue(); + +// let test_crates = [ +// ("low-priority", 1000), +// ("high-priority-foo", -1000), +// ("medium-priority", -10), +// ("high-priority-bar", -1000), +// ("standard-priority", 0), +// ("high-priority-baz", -1000), +// ]; +// for krate in &test_crates { +// queue.add_crate(krate.0, &V1, krate.1, None)?; +// } + +// let assert_next = |name| -> Result<()> { +// queue.process_next_crate(|krate| { +// assert_eq!(name, krate.name); +// Ok(BuildPackageSummary::default()) +// })?; +// Ok(()) +// }; +// let assert_next_and_fail = |name| -> Result<()> { +// queue.process_next_crate(|krate| { +// assert_eq!(name, krate.name); +// anyhow::bail!("simulate a failure"); +// })?; +// Ok(()) +// }; + +// // The first processed item is the one with the highest priority added first. +// assert_next("high-priority-foo")?; + +// // Simulate a failure in high-priority-bar. +// assert_next_and_fail("high-priority-bar")?; + +// // Continue with the next high priority crate. +// assert_next("high-priority-baz")?; + +// // After all the crates with the max priority are processed, before starting to process +// // crates with a lower priority the failed crates with the max priority will be tried +// // again. +// assert_next("high-priority-bar")?; + +// // Continue processing according to the priority. +// assert_next("medium-priority")?; +// assert_next("standard-priority")?; + +// // Simulate the crate failing many times. +// for _ in 0..MAX_ATTEMPTS { +// assert_next_and_fail("low-priority")?; +// } + +// // Since low-priority failed many times it will be removed from the queue. Because of +// // that the queue should now be empty. +// let mut called = false; +// queue.process_next_crate(|_| { +// called = true; +// Ok(BuildPackageSummary::default()) +// })?; +// assert!(!called, "there were still items in the queue"); + +// let collected_metrics = env.collected_metrics(); + +// assert_eq!( +// collected_metrics +// .get_metric("builder", "docsrs.builder.total_builds")? +// .get_u64_counter() +// .value(), +// 9 +// ); + +// assert_eq!( +// collected_metrics +// .get_metric("builder", "docsrs.builder.failed_builds")? +// .get_u64_counter() +// .value(), +// 1 +// ); + +// assert_eq!( +// dbg!( +// collected_metrics +// .get_metric("builder", "docsrs.builder.build_time")? +// .get_f64_histogram() +// .count() +// ), +// 9 +// ); + +// Ok(()) +// } + +// #[test] +// fn test_invalidate_cdn_after_error() -> Result<()> { +// let mut fastly_api = mockito::Server::new(); + +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .fastly_api_host(fastly_api.url().parse().unwrap()) +// .fastly_api_token(Some("test-token".into())) +// .fastly_service_sid(Some("test-sid-1".into())) +// .build()?, +// )?; + +// let queue = env.build_queue(); + +// let m = fastly_api +// .mock("POST", "/service/test-sid-1/purge") +// .with_status(200) +// .create(); + +// queue.add_crate("will_fail", &V1, 0, None)?; + +// queue.process_next_crate(|krate| { +// assert_eq!("will_fail", krate.name); +// anyhow::bail!("simulate a failure"); +// })?; + +// m.expect(1).assert(); + +// Ok(()) +// } +// #[test] +// fn test_invalidate_cdn_after_build() -> Result<()> { +// let mut fastly_api = mockito::Server::new(); + +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .fastly_api_host(fastly_api.url().parse().unwrap()) +// .fastly_api_token(Some("test-token".into())) +// .fastly_service_sid(Some("test-sid-1".into())) +// .build()?, +// )?; + +// let queue = env.build_queue(); + +// let m = fastly_api +// .mock("POST", "/service/test-sid-1/purge") +// .with_status(200) +// .create(); + +// queue.add_crate("will_succeed", &V1, -1, None)?; + +// queue.process_next_crate(|krate| { +// assert_eq!("will_succeed", krate.name); +// Ok(BuildPackageSummary::default()) +// })?; + +// m.expect(1).assert(); + +// Ok(()) +// } + +// #[test] +// fn test_pending_count() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); + +// assert_eq!(queue.pending_count()?, 0); +// queue.add_crate("foo", &V1, 0, None)?; +// assert_eq!(queue.pending_count()?, 1); +// queue.add_crate("bar", &V1, 0, None)?; +// assert_eq!(queue.pending_count()?, 2); + +// queue.process_next_crate(|krate| { +// assert_eq!("foo", krate.name); +// Ok(BuildPackageSummary::default()) +// })?; +// assert_eq!(queue.pending_count()?, 1); + +// drop(env); + +// Ok(()) +// } + +// #[test] +// fn test_prioritized_count() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); + +// assert_eq!(queue.prioritized_count()?, 0); +// queue.add_crate("foo", &V1, 0, None)?; +// assert_eq!(queue.prioritized_count()?, 1); +// queue.add_crate("bar", &V1, -100, None)?; +// assert_eq!(queue.prioritized_count()?, 2); +// queue.add_crate("baz", &V1, 100, None)?; +// assert_eq!(queue.prioritized_count()?, 2); + +// queue.process_next_crate(|krate| { +// assert_eq!("bar", krate.name); +// Ok(BuildPackageSummary::default()) +// })?; +// assert_eq!(queue.prioritized_count()?, 1); + +// Ok(()) +// } + +// #[test] +// fn test_count_by_priority() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); + +// assert!(queue.pending_count_by_priority()?.is_empty()); + +// queue.add_crate("one", &V1, 1, None)?; +// queue.add_crate("two", &V2, 2, None)?; +// queue.add_crate("two_more", &V2, 2, None)?; + +// assert_eq!( +// queue.pending_count_by_priority()?, +// HashMap::from_iter(vec![(1, 1), (2, 2)]) +// ); + +// while queue.pending_count()? > 0 { +// queue.process_next_crate(|_| Ok(BuildPackageSummary::default()))?; +// } +// assert!(queue.pending_count_by_priority()?.is_empty()); + +// Ok(()) +// } + +// #[test] +// fn test_failed_count_for_reattempts() -> Result<()> { +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .build_attempts(MAX_ATTEMPTS) +// .delay_between_build_attempts(Duration::ZERO) +// .build()?, +// )?; + +// const MAX_ATTEMPTS: u16 = 3; + +// let queue = env.build_queue(); + +// assert_eq!(queue.failed_count()?, 0); +// queue.add_crate("foo", &V1, -100, None)?; +// assert_eq!(queue.failed_count()?, 0); +// queue.add_crate("bar", &V1, 0, None)?; + +// for _ in 0..MAX_ATTEMPTS { +// assert_eq!(queue.failed_count()?, 0); +// queue.process_next_crate(|krate| { +// assert_eq!("foo", krate.name); +// Ok(BuildPackageSummary { +// should_reattempt: true, +// ..Default::default() +// }) +// })?; +// } +// assert_eq!(queue.failed_count()?, 1); + +// queue.process_next_crate(|krate| { +// assert_eq!("bar", krate.name); +// Ok(BuildPackageSummary::default()) +// })?; +// assert_eq!(queue.failed_count()?, 1); + +// Ok(()) +// } + +// #[test] +// fn test_failed_count_after_error() -> Result<()> { +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .build_attempts(MAX_ATTEMPTS) +// .delay_between_build_attempts(Duration::ZERO) +// .build()?, +// )?; + +// const MAX_ATTEMPTS: u16 = 3; + +// let queue = env.build_queue(); + +// assert_eq!(queue.failed_count()?, 0); +// queue.add_crate("foo", &V1, -100, None)?; +// assert_eq!(queue.failed_count()?, 0); +// queue.add_crate("bar", &V1, 0, None)?; + +// for _ in 0..MAX_ATTEMPTS { +// assert_eq!(queue.failed_count()?, 0); +// queue.process_next_crate(|krate| { +// assert_eq!("foo", krate.name); +// anyhow::bail!("this failed"); +// })?; +// } +// assert_eq!(queue.failed_count()?, 1); + +// queue.process_next_crate(|krate| { +// assert_eq!("bar", krate.name); +// Ok(BuildPackageSummary::default()) +// })?; +// assert_eq!(queue.failed_count()?, 1); + +// Ok(()) +// } + +// #[test] +// fn test_queued_crates() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); + +// let test_crates = [("bar", 0), ("foo", -10), ("baz", 10)]; +// for krate in &test_crates { +// queue.add_crate(krate.0, &V1, krate.1, None)?; +// } + +// assert_eq!( +// vec![ +// ("foo".into(), V1, -10), +// ("bar".into(), V1, 0), +// ("baz".into(), V1, 10), +// ], +// queue +// .queued_crates()? +// .into_iter() +// .map(|c| (c.name.clone(), c.version, c.priority)) +// .collect::>() +// ); + +// Ok(()) +// } + +// #[test] +// fn test_last_seen_reference_in_db() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); +// queue.unlock()?; +// assert!(!queue.is_locked()?); +// // initial db ref is empty +// assert_eq!(queue.last_seen_reference()?, None); +// assert!(!queue.is_locked()?); + +// let oid = crates_index_diff::gix::ObjectId::from_hex( +// b"ffffffffffffffffffffffffffffffffffffffff", +// )?; +// queue.set_last_seen_reference(oid)?; + +// assert_eq!(queue.last_seen_reference()?, Some(oid)); +// assert!(!queue.is_locked()?); + +// Ok(()) +// } + +// #[test] +// fn test_broken_db_reference_breaks() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// set_config(&mut conn, ConfigName::LastSeenIndexReference, "invalid") +// .await +// .unwrap(); +// }); + +// let queue = env.build_queue(); +// assert!(queue.last_seen_reference().is_err()); + +// Ok(()) +// } + +// #[test] +// fn test_queue_lock() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); +// // unlocked without config +// assert!(!queue.is_locked()?); + +// queue.lock()?; +// assert!(queue.is_locked()?); + +// queue.unlock()?; +// assert!(!queue.is_locked()?); + +// Ok(()) +// } + +// #[test] +// fn test_add_long_name() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); + +// let name: String = "krate".repeat(100); + +// queue.add_crate(&name, &V1, 0, None)?; + +// queue.process_next_crate(|krate| { +// assert_eq!(name, krate.name); +// Ok(BuildPackageSummary::default()) +// })?; + +// Ok(()) +// } + +// #[test] +// fn test_add_long_version() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let queue = env.build_queue(); + +// let long_version = Version::parse(&format!( +// "1.2.3-{}+{}", +// "prerelease".repeat(100), +// "build".repeat(100) +// ))?; + +// queue.add_crate("krate", &long_version, 0, None)?; + +// queue.process_next_crate(|krate| { +// assert_eq!(long_version, krate.version); +// Ok(BuildPackageSummary::default()) +// })?; + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_delete_version_from_queue() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// let queue = env.async_build_queue(); +// assert_eq!(queue.pending_count().await?, 0); + +// queue.add_crate(KRATE, &V1, 0, None).await?; +// queue.add_crate(KRATE, &V2, 0, None).await?; + +// assert_eq!(queue.pending_count().await?, 2); +// queue.remove_version_from_queue(KRATE, &V1).await?; + +// assert_eq!(queue.pending_count().await?, 1); + +// // only v2 remains +// if let [k] = queue.queued_crates().await?.as_slice() { +// assert_eq!(k.name, KRATE); +// assert_eq!(k.version, V2); +// } else { +// panic!("expected only one queued crate"); +// } + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_delete_crate_from_queue() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// let queue = env.async_build_queue(); +// assert_eq!(queue.pending_count().await?, 0); + +// queue.add_crate(KRATE, &V1, 0, None).await?; +// queue.add_crate(KRATE, &V2, 0, None).await?; + +// assert_eq!(queue.pending_count().await?, 2); +// queue.remove_crate_from_queue(KRATE).await?; + +// assert_eq!(queue.pending_count().await?, 0); + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_build_queue/src/metrics.rs b/crates/docs_rs_build_queue/src/metrics.rs new file mode 100644 index 000000000..ddf29985c --- /dev/null +++ b/crates/docs_rs_build_queue/src/metrics.rs @@ -0,0 +1,20 @@ +use docs_rs_opentelemetry::AnyMeterProvider; +use opentelemetry::metrics::Counter; + +#[derive(Debug)] +pub(crate) struct BuildQueueMetrics { + queued_builds: Counter, +} + +impl BuildQueueMetrics { + pub(crate) fn new(meter_provider: &AnyMeterProvider) -> Self { + let meter = meter_provider.meter("build_queue"); + const PREFIX: &str = "docsrs.build_queue"; + Self { + queued_builds: meter + .u64_counter(format!("{PREFIX}.queued_builds")) + .with_unit("1") + .build(), + } + } +} diff --git a/crates/docs_rs_build_queue/src/rebuilds.rs b/crates/docs_rs_build_queue/src/rebuilds.rs new file mode 100644 index 000000000..28a087f5f --- /dev/null +++ b/crates/docs_rs_build_queue/src/rebuilds.rs @@ -0,0 +1,139 @@ +use crate::{AsyncBuildQueue, PRIORITY_BROKEN_RUSTDOC, PRIORITY_CONTINUOUS, config::Config}; +use anyhow::Result; +use chrono::NaiveDate; +use docs_rs_database::types::version::Version; +use futures_util::StreamExt as _; +use tracing::{info, instrument}; + +/// Queue rebuilds as configured. +/// +/// The idea is to rebuild: +/// * the latest release of each crate +/// * when the nightly version is older than our configured threshold +/// * and there was a successful build for that release, that included documentation. +/// * starting with the oldest nightly versions. +/// * also checking if there is already a build queued. +/// +/// This might exclude releases from rebuilds that +/// * previously failed but would succeed with a newer nightly version +/// * previously failed but would succeed just with a retry. +#[instrument(skip_all)] +pub async fn queue_rebuilds( + conn: &mut sqlx::PgConnection, + config: &Config, + build_queue: &AsyncBuildQueue, +) -> Result<()> { + let already_queued_rebuilds: usize = build_queue + .pending_count_by_priority() + .await? + .iter() + .filter_map(|(priority, count)| (*priority >= PRIORITY_CONTINUOUS).then_some(count)) + .sum(); + + let rebuilds_to_queue = config + .max_queued_rebuilds + .expect("config.max_queued_rebuilds not set") as i64 + - already_queued_rebuilds as i64; + + if rebuilds_to_queue <= 0 { + info!("not queueing rebuilds; queue limit reached"); + return Ok(()); + } + + let mut results = sqlx::query!( + r#"SELECT i.* FROM ( + SELECT + c.name, + r.version as "version: Version", + ( + SELECT MAX(COALESCE(b.build_finished, b.build_started)) + FROM builds AS b + WHERE b.rid = r.id + ) AS last_build_attempt + FROM crates AS c + INNER JOIN releases AS r ON c.latest_version_id = r.id + + WHERE + r.rustdoc_status = TRUE + ) as i + ORDER BY i.last_build_attempt ASC + LIMIT $1"#, + rebuilds_to_queue, + ) + .fetch(&mut *conn); + + while let Some(row) = results.next().await { + let row = row?; + + if !build_queue + .has_build_queued(&row.name, &row.version) + .await? + { + info!("queueing rebuild for {} {}...", &row.name, &row.version); + build_queue + .add_crate(&row.name, &row.version, PRIORITY_CONTINUOUS, None) + .await?; + } + } + + Ok(()) +} + +/// Queue rebuilds for failed crates due to a faulty version of rustdoc +/// +/// It is assumed that the version of rustdoc matches the one of rustc, which is persisted in the DB. +/// The priority of the resulting rebuild requests will be lower than previously failed builds. +/// If a crate is already queued to be rebuilt, it will not be requeued. +/// Start date is inclusive, end date is exclusive. +#[instrument(skip_all)] +pub async fn queue_rebuilds_faulty_rustdoc( + conn: &mut sqlx::PgConnection, + build_queue: &AsyncBuildQueue, + start_nightly_date: &NaiveDate, + end_nightly_date: &Option, +) -> Result { + let end_nightly_date = + end_nightly_date.unwrap_or_else(|| start_nightly_date.succ_opt().unwrap()); + let mut results = sqlx::query!( + r#" + SELECT c.name, + r.version AS "version: Version" + FROM crates AS c + JOIN releases AS r + ON c.id = r.crate_id + JOIN release_build_status AS rbs + ON rbs.rid = r.id + JOIN builds AS b + ON b.rid = r.id + AND b.build_finished = rbs.last_build_time + AND b.rustc_nightly_date >= $1 + AND b.rustc_nightly_date < $2 + "#, + start_nightly_date, + end_nightly_date + ) + .fetch(&mut *conn); + + let mut results_count = 0; + while let Some(row) = results.next().await { + let row = row?; + + if !build_queue + .has_build_queued(&row.name, &row.version) + .await? + { + results_count += 1; + info!( + name=%row.name, + version=%row.version, + priority=PRIORITY_BROKEN_RUSTDOC, + "queueing rebuild" + ); + build_queue + .add_crate(&row.name, &row.version, PRIORITY_BROKEN_RUSTDOC, None) + .await?; + } + } + + Ok(results_count) +} diff --git a/crates/docs_rs_database/Cargo.toml b/crates/docs_rs_database/Cargo.toml new file mode 100644 index 000000000..0170311af --- /dev/null +++ b/crates/docs_rs_database/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "docs_rs_database" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +derive_more = { workspace = true } +serde_with = { workspace = true } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +futures-util = { workspace = true } +opentelemetry = { workspace = true } +semver = { workspace = true } +sqlx = { workspace = true } +bincode = { workspace = true } +thiserror = { workspace = true } +strum = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +[features] +testing = [] diff --git a/crates/docs_rs_database/src/config.rs b/crates/docs_rs_database/src/config.rs new file mode 100644 index 000000000..25ed3af8b --- /dev/null +++ b/crates/docs_rs_database/src/config.rs @@ -0,0 +1,18 @@ +use docs_rs_env_vars::{env, require_env}; + +#[derive(Debug)] +pub struct Config { + pub(crate) database_url: String, + pub(crate) max_pool_size: u32, + pub(crate) min_pool_idle: u32, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + database_url: require_env("DOCSRS_DATABASE_URL")?, + max_pool_size: env("DOCSRS_MAX_POOL_SIZE", 90u32)?, + min_pool_idle: env("DOCSRS_MIN_POOL_IDLE", 10u32)?, + }) + } +} diff --git a/crates/docs_rs_database/src/lib.rs b/crates/docs_rs_database/src/lib.rs new file mode 100644 index 000000000..72adaa5ae --- /dev/null +++ b/crates/docs_rs_database/src/lib.rs @@ -0,0 +1,247 @@ +mod config; +pub mod service_config; +pub mod types; + +use docs_rs_opentelemetry::AnyMeterProvider; +use futures_util::{future::BoxFuture, stream::BoxStream}; +use opentelemetry::metrics::{Counter, ObservableGauge}; +use sqlx::{Executor, postgres::PgPoolOptions}; +use std::{ + ops::{Deref, DerefMut}, + sync::Arc, + time::Duration, +}; +use tokio::runtime; +use tracing::debug; + +const DEFAULT_SCHEMA: &str = "public"; + +#[derive(Debug)] +struct PoolMetrics { + failed_connections: Counter, + _idle_connections: ObservableGauge, + _used_connections: ObservableGauge, + _max_connections: ObservableGauge, +} + +impl PoolMetrics { + fn new(pool: sqlx::PgPool, meter_provider: &AnyMeterProvider) -> Self { + let meter = meter_provider.meter("pool"); + const PREFIX: &str = "docsrs.db.pool"; + Self { + failed_connections: meter + .u64_counter(format!("{PREFIX}.failed_connections")) + .with_unit("1") + .build(), + _idle_connections: meter + .u64_observable_gauge(format!("{PREFIX}.idle_connections")) + .with_unit("1") + .with_callback({ + let pool = pool.clone(); + move |observer| { + observer.observe(pool.num_idle() as u64, &[]); + } + }) + .build(), + _used_connections: meter + .u64_observable_gauge(format!("{PREFIX}.used_connections")) + .with_unit("1") + .with_callback({ + let pool = pool.clone(); + move |observer| { + let used = pool.size() as u64 - pool.num_idle() as u64; + observer.observe(used, &[]); + } + }) + .build(), + _max_connections: meter + .u64_observable_gauge(format!("{PREFIX}.max_connections")) + .with_unit("1") + .with_callback({ + let pool = pool.clone(); + move |observer| { + observer.observe(pool.size() as u64, &[]); + } + }) + .build(), + } + } +} + +#[derive(Debug, Clone)] +pub struct Pool { + async_pool: sqlx::PgPool, + runtime: runtime::Handle, + otel_metrics: Arc, +} + +impl Pool { + pub async fn new( + config: &config::Config, + otel_meter_provider: &AnyMeterProvider, + ) -> Result { + debug!( + "creating database pool (if this hangs, consider running `docker-compose up -d db s3`)" + ); + Self::new_inner(config, DEFAULT_SCHEMA, otel_meter_provider).await + } + + #[cfg(feature = "testing")] + pub(crate) async fn new_with_schema( + config: &config::Config, + schema: &str, + otel_meter_provider: &AnyMeterProvider, + ) -> Result { + Self::new_inner(config, schema, otel_meter_provider).await + } + + async fn new_inner( + config: &config::Config, + schema: &str, + otel_meter_provider: &AnyMeterProvider, + ) -> Result { + let acquire_timeout = Duration::from_secs(30); + let max_lifetime = Duration::from_secs(30 * 60); + let idle_timeout = Duration::from_secs(10 * 60); + + let async_pool = PgPoolOptions::new() + .max_connections(config.max_pool_size) + .min_connections(config.min_pool_idle) + .max_lifetime(max_lifetime) + .acquire_timeout(acquire_timeout) + .idle_timeout(idle_timeout) + .after_connect({ + let schema = schema.to_owned(); + move |conn, _meta| { + Box::pin({ + let schema = schema.clone(); + + async move { + if schema != DEFAULT_SCHEMA { + conn.execute( + format!("SET search_path TO {schema}, {DEFAULT_SCHEMA};") + .as_str(), + ) + .await?; + } + + Ok(()) + } + }) + } + }) + .connect_lazy(&config.database_url) + .map_err(PoolError::AsyncPoolCreationFailed)?; + + Ok(Pool { + async_pool: async_pool.clone(), + runtime: runtime::Handle::current(), + otel_metrics: Arc::new(PoolMetrics::new(async_pool, otel_meter_provider)), + }) + } + + pub async fn get_async(&self) -> Result { + match self.async_pool.acquire().await { + Ok(conn) => Ok(AsyncPoolClient { + inner: Some(conn), + runtime: self.runtime.clone(), + }), + Err(err) => { + self.otel_metrics.failed_connections.add(1, &[]); + Err(PoolError::AsyncClientError(err)) + } + } + } +} + +/// This impl allows us to use our own pool as an executor for SQLx queries. +impl sqlx::Executor<'_> for &'_ Pool +where + for<'c> &'c mut ::Connection: + sqlx::Executor<'c, Database = sqlx::Postgres>, +{ + type Database = sqlx::Postgres; + + fn fetch_many<'e, 'q: 'e, E>( + self, + query: E, + ) -> BoxStream< + 'e, + Result< + sqlx::Either< + ::QueryResult, + ::Row, + >, + sqlx::Error, + >, + > + where + E: sqlx::Execute<'q, Self::Database> + 'q, + { + self.async_pool.fetch_many(query) + } + + fn fetch_optional<'e, 'q: 'e, E>( + self, + query: E, + ) -> BoxFuture<'e, Result::Row>, sqlx::Error>> + where + E: sqlx::Execute<'q, Self::Database> + 'q, + { + self.async_pool.fetch_optional(query) + } + + fn prepare_with<'e, 'q: 'e>( + self, + sql: &'q str, + parameters: &'e [::TypeInfo], + ) -> BoxFuture<'e, Result<::Statement<'q>, sqlx::Error>> { + self.async_pool.prepare_with(sql, parameters) + } + + fn describe<'e, 'q: 'e>( + self, + sql: &'q str, + ) -> BoxFuture<'e, Result, sqlx::Error>> { + self.async_pool.describe(sql) + } +} + +/// we wrap `sqlx::PoolConnection` so we can drop it in a sync context +/// and enter the runtime. +/// Otherwise dropping the PoolConnection will panic because it can't spawn a task. +#[derive(Debug)] +pub struct AsyncPoolClient { + inner: Option>, + runtime: runtime::Handle, +} + +impl Deref for AsyncPoolClient { + type Target = sqlx::PgConnection; + + fn deref(&self) -> &Self::Target { + self.inner.as_ref().unwrap() + } +} + +impl DerefMut for AsyncPoolClient { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.as_mut().unwrap() + } +} + +impl Drop for AsyncPoolClient { + fn drop(&mut self) { + let _guard = self.runtime.enter(); + drop(self.inner.take()) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum PoolError { + #[error("failed to create the database connection pool")] + AsyncPoolCreationFailed(#[source] sqlx::Error), + + #[error("failed to get a database connection")] + AsyncClientError(#[source] sqlx::Error), +} diff --git a/crates/docs_rs_database/src/service_config.rs b/crates/docs_rs_database/src/service_config.rs new file mode 100644 index 000000000..c17834b50 --- /dev/null +++ b/crates/docs_rs_database/src/service_config.rs @@ -0,0 +1,45 @@ +use anyhow::Result; +use serde::{Serialize, de::DeserializeOwned}; + +#[derive(strum::IntoStaticStr)] +#[strum(serialize_all = "snake_case")] +pub enum ConfigName { + RustcVersion, + LastSeenIndexReference, + QueueLocked, + Toolchain, +} + +pub async fn set_config( + conn: &mut sqlx::PgConnection, + name: ConfigName, + value: impl Serialize, +) -> anyhow::Result<()> { + let name: &'static str = name.into(); + sqlx::query!( + "INSERT INTO config (name, value) + VALUES ($1, $2) + ON CONFLICT (name) DO UPDATE SET value = $2;", + name, + &serde_json::to_value(value)?, + ) + .execute(conn) + .await?; + Ok(()) +} + +pub async fn get_config(conn: &mut sqlx::PgConnection, name: ConfigName) -> Result> +where + T: DeserializeOwned, +{ + let name: &'static str = name.into(); + Ok( + match sqlx::query!("SELECT value FROM config WHERE name = $1;", name) + .fetch_optional(conn) + .await? + { + Some(row) => serde_json::from_value(row.value)?, + None => None, + }, + ) +} diff --git a/crates/docs_rs_database/src/types/mod.rs b/crates/docs_rs_database/src/types/mod.rs new file mode 100644 index 000000000..a6db76ad4 --- /dev/null +++ b/crates/docs_rs_database/src/types/mod.rs @@ -0,0 +1 @@ +pub mod version; diff --git a/src/db/types/version.rs b/crates/docs_rs_database/src/types/version.rs similarity index 95% rename from src/db/types/version.rs rename to crates/docs_rs_database/src/types/version.rs index 396c2de26..63cdbd921 100644 --- a/src/db/types/version.rs +++ b/crates/docs_rs_database/src/types/version.rs @@ -1,6 +1,8 @@ +// FIXME: we might be able to drop this disallowed-types thing when +// the rest of the codebase just uses this subcrate? #[allow(clippy::disallowed_types)] mod version_impl { - use crate::error::Result; + use anyhow::Result; use derive_more::{Deref, Display, From, Into}; use serde_with::{DeserializeFromStr, SerializeDisplay}; use sqlx::{ diff --git a/crates/docs_rs_env_vars/Cargo.toml b/crates/docs_rs_env_vars/Cargo.toml new file mode 100644 index 000000000..768bfb99d --- /dev/null +++ b/crates/docs_rs_env_vars/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "docs_rs_env_vars" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +tracing = { workspace = true } diff --git a/crates/docs_rs_env_vars/src/lib.rs b/crates/docs_rs_env_vars/src/lib.rs new file mode 100644 index 000000000..bc01bdec9 --- /dev/null +++ b/crates/docs_rs_env_vars/src/lib.rs @@ -0,0 +1,37 @@ +use anyhow::{Context as _, Result, anyhow}; +use std::{env::VarError, error::Error, str::FromStr}; +use tracing::trace; + +pub fn env(var: &str, default: T) -> Result +where + T: FromStr, + T::Err: Error + Send + Sync + 'static, +{ + Ok(maybe_env(var)?.unwrap_or(default)) +} + +pub fn require_env(var: &str) -> Result +where + T: FromStr, + ::Err: Error + Send + Sync + 'static, +{ + maybe_env(var)?.with_context(|| anyhow!("configuration variable {} is missing", var)) +} + +pub fn maybe_env(var: &str) -> Result> +where + T: FromStr, + T::Err: Error + Send + Sync + 'static, +{ + match std::env::var(var) { + Ok(content) => Ok(content + .parse::() + .map(Some) + .with_context(|| format!("failed to parse configuration variable {var}"))?), + Err(VarError::NotPresent) => { + trace!("optional configuration variable {} is not set", var); + Ok(None) + } + Err(VarError::NotUnicode(_)) => Err(anyhow!("configuration variable {} is not UTF-8", var)), + } +} diff --git a/crates/docs_rs_opentelemetry/Cargo.toml b/crates/docs_rs_opentelemetry/Cargo.toml new file mode 100644 index 000000000..3965b36f8 --- /dev/null +++ b/crates/docs_rs_opentelemetry/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "docs_rs_opentelemetry" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +opentelemetry = { workspace = true } +opentelemetry-otlp = { workspace = true } +opentelemetry-resource-detectors = { workspace = true } +opentelemetry_sdk = { workspace = true } +tracing = { workspace = true } +url = { workspace = true } diff --git a/crates/docs_rs_opentelemetry/src/config.rs b/crates/docs_rs_opentelemetry/src/config.rs new file mode 100644 index 000000000..8a40168db --- /dev/null +++ b/crates/docs_rs_opentelemetry/src/config.rs @@ -0,0 +1,15 @@ +use docs_rs_env_vars::maybe_env; +use url::Url; + +pub struct Config { + // opentelemetry endpoint to send OTLP to + pub(crate) endpoint: Option, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + endpoint: maybe_env("OTEL_EXPORTER_OTLP_ENDPOINT")?, + }) + } +} diff --git a/src/metrics/otel.rs b/crates/docs_rs_opentelemetry/src/lib.rs similarity index 92% rename from src/metrics/otel.rs rename to crates/docs_rs_opentelemetry/src/lib.rs index 9fd794c52..16b4e3911 100644 --- a/src/metrics/otel.rs +++ b/crates/docs_rs_opentelemetry/src/lib.rs @@ -1,4 +1,5 @@ -use crate::Config; +mod config; + use anyhow::Result; use opentelemetry::{ InstrumentationScope, @@ -26,8 +27,8 @@ impl MeterProviderWithExt for opentelemetry_sdk::metrics::SdkMeterProvider { /// opentelemetry metric provider setup, /// if no endpoint is configured, use a no-op provider -pub(crate) fn get_meter_provider(config: &Config) -> Result { - if let Some(ref endpoint) = config.opentelemetry_endpoint { +pub fn get_meter_provider(config: &config::Config) -> Result { + if let Some(ref endpoint) = config.endpoint { let endpoint = endpoint.to_string(); info!(endpoint, "setting up OpenTelemetry metrics exporter"); @@ -61,7 +62,7 @@ pub(crate) fn get_meter_provider(config: &Config) -> Result { /// For now, copy/paste from opentelemetry-sdk, see /// https://github.com/open-telemetry/opentelemetry-rust/pull/3111 #[derive(Debug, Default)] -pub(crate) struct NoopMeterProvider { +pub struct NoopMeterProvider { _private: (), } @@ -86,7 +87,7 @@ impl MeterProviderWithExt for NoopMeterProvider { /// A no-op instance of a `Meter` #[derive(Debug, Default)] -pub(crate) struct NoopMeter { +pub struct NoopMeter { _private: (), } diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml new file mode 100644 index 000000000..d0c0e6a2e --- /dev/null +++ b/crates/docs_rs_watcher/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "docs_rs_watcher" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } +crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} +docs_rs_build_queue = { path = "../docs_rs_build_queue" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +tokio = { workspace = true } +tracing = { workspace = true } +url = { workspace = true } diff --git a/crates/docs_rs_watcher/src/config.rs b/crates/docs_rs_watcher/src/config.rs new file mode 100644 index 000000000..647ca3cc7 --- /dev/null +++ b/crates/docs_rs_watcher/src/config.rs @@ -0,0 +1,35 @@ +use docs_rs_env_vars::{env, maybe_env, require_env}; +use std::{path::PathBuf, time::Duration}; +use url::Url; + +#[derive(Debug)] +pub struct Config { + pub(crate) registry_index_path: PathBuf, + pub(crate) registry_url: Option, + pub(crate) registry_api_host: Url, + + /// How long to wait between registry checks + pub(crate) delay_between_registry_fetches: Duration, + + // Time between 'git gc --auto' calls in seconds + pub(crate) registry_gc_interval: u64, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; + Ok(Self { + registry_index_path: env("REGISTRY_INDEX_PATH", prefix.join("crates.io-index"))?, + registry_url: maybe_env("REGISTRY_URL")?, + registry_api_host: env( + "DOCSRS_REGISTRY_API_HOST", + "https://crates.io".parse().unwrap(), + )?, + delay_between_registry_fetches: Duration::from_secs(env::( + "DOCSRS_DELAY_BETWEEN_REGISTRY_FETCHES", + 60, + )?), + registry_gc_interval: env("DOCSRS_REGISTRY_GC_INTERVAL", 60 * 60)?, + }) + } +} diff --git a/src/index.rs b/crates/docs_rs_watcher/src/index.rs similarity index 100% rename from src/index.rs rename to crates/docs_rs_watcher/src/index.rs diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs new file mode 100644 index 000000000..5577fd20d --- /dev/null +++ b/crates/docs_rs_watcher/src/lib.rs @@ -0,0 +1,109 @@ +mod config; +mod index; + +use anyhow::Error; +use docs_rs_build_queue::AsyncBuildQueue; +use std::time::Instant; +use tracing::debug; + +/// Run the registry watcher +/// NOTE: this should only be run once, otherwise crates would be added +/// to the queue multiple times. +pub async fn watch_registry( + build_queue: &AsyncBuildQueue, + config: &config::Config, +) -> Result<(), Error> { + let mut last_gc = Instant::now(); + + loop { + if build_queue.is_locked().await? { + debug!("Queue is locked, skipping checking new crates"); + } else { + debug!("Checking new crates"); + let index = index::Index::from_config(config).await?; + // TODO: + // match build_queue + // .get_new_crates(&index) + // .await + // .context("Failed to get new crates") + // { + // Ok(n) => debug!("{} crates added to queue", n), + // Err(e) => report_error(&e), + // } + + if last_gc.elapsed().as_secs() >= config.registry_gc_interval { + index.run_git_gc().await; + last_gc = Instant::now(); + } + } + tokio::time::sleep(config.delay_between_registry_fetches).await; + } +} + +// async fn start_registry_watcher( +// build_queue: &AsyncBuildQueue, +// config: &config::Config, +// ) -> Result<(), Error> { +// tokio::spawn(async move { +// // space this out to prevent it from clashing against the queue-builder thread on launch +// tokio::time::sleep(Duration::from_secs(30)).await; + +// watch_registry(&build_queue, &config).await +// }); + +// Ok(()) +// } + +pub async fn start_background_repository_stats_updater() -> Result<(), Error> { + todo!(); + // // This call will still skip github repositories updates and continue if no token is provided + // // (gitlab doesn't require to have a token). The only time this can return an error is when + // // creating a pool or if config fails, which shouldn't happen here because this is run right at + // // startup. + // let updater = context.repository_stats_updater.clone(); + // let runtime = context.runtime.clone(); + // async_cron( + // &runtime, + // "repository stats updater", + // Duration::from_secs(60 * 60), + // move || { + // let updater = updater.clone(); + // async move { + // updater.update_all_crates().await?; + // Ok(()) + // } + // }, + // ); + // Ok(()) +} + +pub fn start_background_queue_rebuild() -> Result<(), Error> { + todo!() + + // let runtime = context.runtime.clone(); + // let pool = context.pool.clone(); + // let config = context.config.clone(); + // let build_queue = context.async_build_queue.clone(); + + // if config.max_queued_rebuilds.is_none() { + // info!("rebuild config incomplete, skipping rebuild queueing"); + // return Ok(()); + // } + + // async_cron( + // &runtime, + // "background queue rebuilder", + // Duration::from_secs(60 * 60), + // move || { + // let pool = pool.clone(); + // let build_queue = build_queue.clone(); + // let config = config.clone(); + // async move { + // let mut conn = pool.get_async().await?; + // queue_rebuilds(&mut conn, &config, &build_queue).await?; + // Ok(()) + // } + // }, + // ); + // Ok(()) +} diff --git a/src/repositories/github.rs b/crates/docs_rs_watcher/src/repositories/github.rs similarity index 100% rename from src/repositories/github.rs rename to crates/docs_rs_watcher/src/repositories/github.rs diff --git a/src/repositories/gitlab.rs b/crates/docs_rs_watcher/src/repositories/gitlab.rs similarity index 100% rename from src/repositories/gitlab.rs rename to crates/docs_rs_watcher/src/repositories/gitlab.rs diff --git a/src/repositories/mod.rs b/crates/docs_rs_watcher/src/repositories/mod.rs similarity index 100% rename from src/repositories/mod.rs rename to crates/docs_rs_watcher/src/repositories/mod.rs diff --git a/src/repositories/updater.rs b/crates/docs_rs_watcher/src/repositories/updater.rs similarity index 100% rename from src/repositories/updater.rs rename to crates/docs_rs_watcher/src/repositories/updater.rs diff --git a/src/build_queue.rs b/src/build_queue.rs deleted file mode 100644 index e1d5945ea..000000000 --- a/src/build_queue.rs +++ /dev/null @@ -1,1780 +0,0 @@ -use crate::{ - BuildPackageSummary, Config, Context, Index, RustwideBuilder, - cdn::{self, CdnMetrics}, - db::{ - CrateId, Pool, delete_crate, delete_version, - types::{krate_name::KrateName, version::Version}, - update_latest_version_id, - }, - docbuilder::{BuilderMetrics, PackageKind}, - error::Result, - metrics::otel::AnyMeterProvider, - storage::AsyncStorage, - utils::{ConfigName, get_config, get_crate_priority, report_error, retry, set_config}, -}; -use anyhow::Context as _; -use chrono::NaiveDate; -use fn_error_context::context; -use futures_util::{StreamExt, stream::TryStreamExt}; -use opentelemetry::metrics::Counter; -use sqlx::Connection as _; -use std::{collections::HashMap, sync::Arc, time::Instant}; -use tokio::runtime; -use tracing::{debug, error, info, instrument, warn}; - -#[derive(Debug)] -struct BuildQueueMetrics { - queued_builds: Counter, -} - -impl BuildQueueMetrics { - fn new(meter_provider: &AnyMeterProvider) -> Self { - let meter = meter_provider.meter("build_queue"); - const PREFIX: &str = "docsrs.build_queue"; - Self { - queued_builds: meter - .u64_counter(format!("{PREFIX}.queued_builds")) - .with_unit("1") - .build(), - } - } -} - -pub(crate) const PRIORITY_DEFAULT: i32 = 0; -/// Used for workspaces to avoid blocking the queue (done through the cratesfyi CLI, not used in code) -#[allow(dead_code)] -pub(crate) const PRIORITY_DEPRIORITIZED: i32 = 1; -/// Rebuilds triggered from crates.io, see issue #2442 -pub(crate) const PRIORITY_MANUAL_FROM_CRATES_IO: i32 = 5; -/// Used for rebuilds queued through cratesfyi for crate versions failed due to a broken Rustdoc nightly version. -/// Note: a broken rustdoc version does not necessarily imply a failed build. -pub(crate) const PRIORITY_BROKEN_RUSTDOC: i32 = 10; -/// Used by the synchronize cratesfyi command when queueing builds that are in the crates.io index but not in the database. -pub(crate) const PRIORITY_CONSISTENCY_CHECK: i32 = 15; -/// The static priority for background rebuilds, used when queueing rebuilds, and when rendering them collapsed in the UI. -pub(crate) const PRIORITY_CONTINUOUS: i32 = 20; - -#[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] -pub(crate) struct QueuedCrate { - #[serde(skip)] - id: i32, - pub(crate) name: String, - pub(crate) version: Version, - pub(crate) priority: i32, - pub(crate) registry: Option, - pub(crate) attempt: i32, -} - -#[derive(Debug)] -pub struct AsyncBuildQueue { - config: Arc, - storage: Arc, - pub(crate) db: Pool, - queue_metrics: BuildQueueMetrics, - builder_metrics: Arc, - cdn_metrics: Arc, - max_attempts: i32, -} - -impl AsyncBuildQueue { - pub fn new( - db: Pool, - config: Arc, - storage: Arc, - cdn_metrics: Arc, - otel_meter_provider: &AnyMeterProvider, - ) -> Self { - AsyncBuildQueue { - max_attempts: config.build_attempts.into(), - config, - db, - storage, - queue_metrics: BuildQueueMetrics::new(otel_meter_provider), - builder_metrics: Arc::new(BuilderMetrics::new(otel_meter_provider)), - cdn_metrics, - } - } - - pub fn builder_metrics(&self) -> Arc { - self.builder_metrics.clone() - } - - pub async fn last_seen_reference(&self) -> Result> { - let mut conn = self.db.get_async().await?; - if let Some(value) = - get_config::(&mut conn, ConfigName::LastSeenIndexReference).await? - { - return Ok(Some(crates_index_diff::gix::ObjectId::from_hex( - value.as_bytes(), - )?)); - } - Ok(None) - } - - pub async fn set_last_seen_reference( - &self, - oid: crates_index_diff::gix::ObjectId, - ) -> Result<()> { - let mut conn = self.db.get_async().await?; - set_config( - &mut conn, - ConfigName::LastSeenIndexReference, - oid.to_string(), - ) - .await?; - Ok(()) - } - - #[context("error trying to add {name}-{version} to build queue")] - pub async fn add_crate( - &self, - name: &str, - version: &Version, - priority: i32, - registry: Option<&str>, - ) -> Result<()> { - let mut conn = self.db.get_async().await?; - - sqlx::query!( - "INSERT INTO queue (name, version, priority, registry) - VALUES ($1, $2, $3, $4) - ON CONFLICT (name, version) DO UPDATE - SET priority = EXCLUDED.priority, - registry = EXCLUDED.registry, - attempt = 0, - last_attempt = NULL - ;", - name, - version as _, - priority, - registry, - ) - .execute(&mut *conn) - .await?; - - Ok(()) - } - - pub(crate) async fn pending_count(&self) -> Result { - Ok(self - .pending_count_by_priority() - .await? - .values() - .sum::()) - } - - pub(crate) async fn prioritized_count(&self) -> Result { - Ok(self - .pending_count_by_priority() - .await? - .iter() - .filter(|&(&priority, _)| priority <= 0) - .map(|(_, count)| count) - .sum::()) - } - - pub(crate) async fn pending_count_by_priority(&self) -> Result> { - let mut conn = self.db.get_async().await?; - - Ok(sqlx::query!( - r#" - SELECT - priority, - COUNT(*) as "count!" - FROM queue - WHERE attempt < $1 - GROUP BY priority"#, - self.max_attempts, - ) - .fetch(&mut *conn) - .map_ok(|row| (row.priority, row.count as usize)) - .try_collect() - .await?) - } - - pub(crate) async fn failed_count(&self) -> Result { - let mut conn = self.db.get_async().await?; - - Ok(sqlx::query_scalar!( - r#"SELECT COUNT(*) as "count!" FROM queue WHERE attempt >= $1;"#, - self.max_attempts, - ) - .fetch_one(&mut *conn) - .await? as usize) - } - - pub(crate) async fn queued_crates(&self) -> Result> { - let mut conn = self.db.get_async().await?; - - Ok(sqlx::query_as!( - QueuedCrate, - r#"SELECT - id, - name, - version as "version: Version", - priority, - registry, - attempt - FROM queue - WHERE attempt < $1 - ORDER BY priority ASC, attempt ASC, id ASC"#, - self.max_attempts - ) - .fetch_all(&mut *conn) - .await?) - } - - pub(crate) async fn has_build_queued(&self, name: &str, version: &Version) -> Result { - let mut conn = self.db.get_async().await?; - Ok(sqlx::query_scalar!( - "SELECT id - FROM queue - WHERE - attempt < $1 AND - name = $2 AND - version = $3 - ", - self.max_attempts, - name, - version as _, - ) - .fetch_optional(&mut *conn) - .await? - .is_some()) - } - - async fn remove_crate_from_queue(&self, name: &str) -> Result<()> { - let mut conn = self.db.get_async().await?; - sqlx::query!( - "DELETE - FROM queue - WHERE name = $1 - ", - name - ) - .execute(&mut *conn) - .await?; - - Ok(()) - } - - async fn remove_version_from_queue(&self, name: &str, version: &Version) -> Result<()> { - let mut conn = self.db.get_async().await?; - sqlx::query!( - "DELETE - FROM queue - WHERE - name = $1 AND - version = $2 - ", - name, - version as _, - ) - .execute(&mut *conn) - .await?; - - Ok(()) - } -} - -/// Locking functions. -impl AsyncBuildQueue { - /// Checks for the lock and returns whether it currently exists. - pub async fn is_locked(&self) -> Result { - let mut conn = self.db.get_async().await?; - - Ok(get_config::(&mut conn, ConfigName::QueueLocked) - .await? - .unwrap_or(false)) - } - - /// lock the queue. Daemon will check this lock and stop operating if it exists. - pub async fn lock(&self) -> Result<()> { - let mut conn = self.db.get_async().await?; - set_config(&mut conn, ConfigName::QueueLocked, true).await - } - - /// unlock the queue. - pub async fn unlock(&self) -> Result<()> { - let mut conn = self.db.get_async().await?; - set_config(&mut conn, ConfigName::QueueLocked, false).await - } -} - -/// Index methods. -impl AsyncBuildQueue { - async fn queue_crate_invalidation(&self, krate: &str) { - let krate = match krate - .parse::() - .with_context(|| format!("can't parse crate name '{}'", krate)) - { - Ok(krate) => krate, - Err(err) => { - report_error(&err); - return; - } - }; - - if let Err(err) = - cdn::queue_crate_invalidation(&self.config, &self.cdn_metrics, &krate).await - { - report_error(&err); - } - } - - /// Updates registry index repository and adds new crates into build queue. - /// - /// Returns the number of crates added - pub async fn get_new_crates(&self, index: &Index) -> Result { - let last_seen_reference = self.last_seen_reference().await?; - let last_seen_reference = if let Some(oid) = last_seen_reference { - oid - } else { - warn!( - "no last-seen reference found in our database. We assume a fresh install and - set the latest reference (HEAD) as last. This means we will then start to queue - builds for new releases only from now on, and not for all existing releases." - ); - index.latest_commit_reference().await? - }; - - index.set_last_seen_reference(last_seen_reference).await?; - - let (changes, new_reference) = index.peek_changes_ordered().await?; - - let mut conn = self.db.get_async().await?; - let mut crates_added = 0; - - debug!("queueing changes from {last_seen_reference} to {new_reference}"); - - for change in &changes { - if let Some((ref krate, ..)) = change.crate_deleted() { - match delete_crate(&mut conn, &self.storage, &self.config, krate) - .await - .with_context(|| format!("failed to delete crate {krate}")) - { - Ok(_) => info!( - "crate {} was deleted from the index and the database", - krate - ), - Err(err) => report_error(&err), - } - - self.queue_crate_invalidation(krate).await; - self.remove_crate_from_queue(krate).await?; - continue; - } - - if let Some(release) = change.version_deleted() { - let version: Version = release - .version - .parse() - .context("couldn't parse release version as semver")?; - - match delete_version( - &mut conn, - &self.storage, - &self.config, - &release.name, - &version, - ) - .await - .with_context(|| { - format!( - "failed to delete version {}-{}", - release.name, release.version - ) - }) { - Ok(_) => info!( - "release {}-{} was deleted from the index and the database", - release.name, release.version - ), - Err(err) => report_error(&err), - } - - self.queue_crate_invalidation(&release.name).await; - self.remove_version_from_queue(&release.name, &version) - .await?; - continue; - } - - if let Some(release) = change.added() { - let priority = get_crate_priority(&mut conn, &release.name).await?; - - match self - .add_crate( - &release.name, - &release - .version - .parse() - .context("couldn't parse release version as semver")?, - priority, - index.repository_url(), - ) - .await - .with_context(|| { - format!( - "failed adding {}-{} into build queue", - release.name, release.version - ) - }) { - Ok(()) => { - debug!( - "{}-{} added into build queue", - release.name, release.version - ); - self.queue_metrics.queued_builds.add(1, &[]); - crates_added += 1; - } - Err(err) => report_error(&err), - } - } - - let yanked = change.yanked(); - let unyanked = change.unyanked(); - if let Some(release) = yanked.or(unyanked) { - // FIXME: delay yanks of crates that have not yet finished building - // https://github.com/rust-lang/docs.rs/issues/1934 - if let Ok(release_version) = Version::parse(&release.version) - && let Err(err) = self - .set_yanked_inner( - &mut conn, - release.name.as_str(), - &release_version, - yanked.is_some(), - ) - .await - { - report_error(&err); - } - - self.queue_crate_invalidation(&release.name).await; - } - } - - // set the reference in the database - // so this survives recreating the registry watcher - // server. - self.set_last_seen_reference(new_reference).await?; - - Ok(crates_added) - } - - pub async fn set_yanked(&self, name: &str, version: &Version, yanked: bool) -> Result<()> { - let mut conn = self.db.get_async().await?; - self.set_yanked_inner(&mut conn, name, version, yanked) - .await - } - - #[context("error trying to set {name}-{version} to yanked: {yanked}")] - async fn set_yanked_inner( - &self, - conn: &mut sqlx::PgConnection, - name: &str, - version: &Version, - yanked: bool, - ) -> Result<()> { - let activity = if yanked { "yanked" } else { "unyanked" }; - - if let Some(crate_id) = sqlx::query_scalar!( - r#"UPDATE releases - SET yanked = $3 - FROM crates - WHERE crates.id = releases.crate_id - AND name = $1 - AND version = $2 - RETURNING crates.id as "id: CrateId" - "#, - name, - version as _, - yanked, - ) - .fetch_optional(&mut *conn) - .await? - { - debug!("{}-{} {}", name, version, activity); - update_latest_version_id(&mut *conn, crate_id).await?; - } else { - match self - .has_build_queued(name, version) - .await - .context("error trying to fetch build queue") - { - Ok(false) => { - error!( - "tried to yank or unyank non-existing release: {} {}", - name, version - ); - } - Ok(true) => { - // the rustwide builder will fetch the current yank state from - // crates.io, so and missed update here will be fixed after the - // build is finished. - } - Err(err) => { - report_error(&err); - } - } - } - - Ok(()) - } -} - -#[derive(Debug)] -pub struct BuildQueue { - runtime: runtime::Handle, - inner: Arc, -} - -/// sync versions of async methods -impl BuildQueue { - pub fn add_crate( - &self, - name: &str, - version: &Version, - priority: i32, - registry: Option<&str>, - ) -> Result<()> { - self.runtime - .block_on(self.inner.add_crate(name, version, priority, registry)) - } - - pub fn set_yanked(&self, name: &str, version: &Version, yanked: bool) -> Result<()> { - self.runtime - .block_on(self.inner.set_yanked(name, version, yanked)) - } - pub fn is_locked(&self) -> Result { - self.runtime.block_on(self.inner.is_locked()) - } - pub fn lock(&self) -> Result<()> { - self.runtime.block_on(self.inner.lock()) - } - pub fn unlock(&self) -> Result<()> { - self.runtime.block_on(self.inner.unlock()) - } - pub fn last_seen_reference(&self) -> Result> { - self.runtime.block_on(self.inner.last_seen_reference()) - } - pub fn set_last_seen_reference(&self, oid: crates_index_diff::gix::ObjectId) -> Result<()> { - self.runtime - .block_on(self.inner.set_last_seen_reference(oid)) - } - #[cfg(test)] - pub(crate) fn pending_count(&self) -> Result { - self.runtime.block_on(self.inner.pending_count()) - } - #[cfg(test)] - pub(crate) fn prioritized_count(&self) -> Result { - self.runtime.block_on(self.inner.prioritized_count()) - } - #[cfg(test)] - pub(crate) fn pending_count_by_priority(&self) -> Result> { - self.runtime - .block_on(self.inner.pending_count_by_priority()) - } - #[cfg(test)] - pub(crate) fn failed_count(&self) -> Result { - self.runtime.block_on(self.inner.failed_count()) - } - #[cfg(test)] - pub(crate) fn queued_crates(&self) -> Result> { - self.runtime.block_on(self.inner.queued_crates()) - } -} - -impl BuildQueue { - pub fn new(runtime: runtime::Handle, inner: Arc) -> Self { - Self { runtime, inner } - } - - fn process_next_crate( - &self, - f: impl FnOnce(&QueuedCrate) -> Result, - ) -> Result<()> { - let mut conn = self.runtime.block_on(self.inner.db.get_async())?; - let mut transaction = self.runtime.block_on(conn.begin())?; - - // fetch the next available crate from the queue table. - // We are using `SELECT FOR UPDATE` inside a transaction so - // the QueuedCrate is locked until we are finished with it. - // `SKIP LOCKED` here will enable another build-server to just - // skip over taken (=locked) rows and start building the first - // available one. - let to_process = match self.runtime.block_on( - sqlx::query_as!( - QueuedCrate, - r#"SELECT - id, - name, - version as "version: Version", - priority, - registry, - attempt - FROM queue - WHERE - attempt < $1 AND - (last_attempt IS NULL OR last_attempt < NOW() - make_interval(secs => $2)) - ORDER BY priority ASC, attempt ASC, id ASC - LIMIT 1 - FOR UPDATE SKIP LOCKED"#, - self.inner.max_attempts, - self.inner.config.delay_between_build_attempts.as_secs_f64(), - ) - .fetch_optional(&mut *transaction), - )? { - Some(krate) => krate, - None => return Ok(()), - }; - - let res = { - let instant = Instant::now(); - let res = f(&to_process); - let elapsed = instant.elapsed().as_secs_f64(); - self.inner.builder_metrics.build_time.record(elapsed, &[]); - res - }; - - self.inner.builder_metrics.total_builds.add(1, &[]); - - self.runtime - .block_on(self.inner.queue_crate_invalidation(&to_process.name)); - - let mut increase_attempt_count = || -> Result<()> { - let attempt: i32 = self.runtime.block_on( - sqlx::query_scalar!( - "UPDATE queue - SET - attempt = attempt + 1, - last_attempt = NOW() - WHERE id = $1 - RETURNING attempt;", - to_process.id, - ) - .fetch_one(&mut *transaction), - )?; - - if attempt >= self.inner.max_attempts { - self.inner.builder_metrics.failed_builds.add(1, &[]); - } - Ok(()) - }; - - match res { - Ok(BuildPackageSummary { - should_reattempt: false, - successful: _, - }) => { - self.runtime.block_on( - sqlx::query!("DELETE FROM queue WHERE id = $1;", to_process.id) - .execute(&mut *transaction), - )?; - } - Ok(BuildPackageSummary { - should_reattempt: true, - successful: _, - }) => { - increase_attempt_count()?; - } - Err(e) => { - increase_attempt_count()?; - report_error(&e.context(format!( - "Failed to build package {}-{} from queue", - to_process.name, to_process.version - ))) - } - } - - self.runtime.block_on(transaction.commit())?; - Ok(()) - } - - /// Builds the top package from the queue. Returns whether there was a package in the queue. - /// - /// Note that this will return `Ok(true)` even if the package failed to build. - pub(crate) fn build_next_queue_package( - &self, - context: &Context, - builder: &mut RustwideBuilder, - ) -> Result { - let mut processed = false; - - self.process_next_crate(|krate| { - processed = true; - - let kind = krate - .registry - .as_ref() - .map(|r| PackageKind::Registry(r.as_str())) - .unwrap_or(PackageKind::CratesIo); - - if let Err(err) = retry( - || { - builder - .reinitialize_workspace_if_interval_passed(context) - .context("Reinitialize workspace failed, locking queue") - }, - 3, - ) { - report_error(&err); - self.lock()?; - return Err(err); - } - - if let Err(err) = builder - .update_toolchain_and_add_essential_files() - .context("Updating toolchain failed, locking queue") - { - report_error(&err); - self.lock()?; - return Err(err); - } - - builder.build_package(&krate.name, &krate.version, kind, krate.attempt == 0) - })?; - - Ok(processed) - } -} - -/// Queue rebuilds as configured. -/// -/// The idea is to rebuild: -/// * the latest release of each crate -/// * when the nightly version is older than our configured threshold -/// * and there was a successful build for that release, that included documentation. -/// * starting with the oldest nightly versions. -/// * also checking if there is already a build queued. -/// -/// This might exclude releases from rebuilds that -/// * previously failed but would succeed with a newer nightly version -/// * previously failed but would succeed just with a retry. -#[instrument(skip_all)] -pub async fn queue_rebuilds( - conn: &mut sqlx::PgConnection, - config: &Config, - build_queue: &AsyncBuildQueue, -) -> Result<()> { - let already_queued_rebuilds: usize = build_queue - .pending_count_by_priority() - .await? - .iter() - .filter_map(|(priority, count)| (*priority >= PRIORITY_CONTINUOUS).then_some(count)) - .sum(); - - let rebuilds_to_queue = config - .max_queued_rebuilds - .expect("config.max_queued_rebuilds not set") as i64 - - already_queued_rebuilds as i64; - - if rebuilds_to_queue <= 0 { - info!("not queueing rebuilds; queue limit reached"); - return Ok(()); - } - - let mut results = sqlx::query!( - r#"SELECT i.* FROM ( - SELECT - c.name, - r.version as "version: Version", - ( - SELECT MAX(COALESCE(b.build_finished, b.build_started)) - FROM builds AS b - WHERE b.rid = r.id - ) AS last_build_attempt - FROM crates AS c - INNER JOIN releases AS r ON c.latest_version_id = r.id - - WHERE - r.rustdoc_status = TRUE - ) as i - ORDER BY i.last_build_attempt ASC - LIMIT $1"#, - rebuilds_to_queue, - ) - .fetch(&mut *conn); - - while let Some(row) = results.next().await { - let row = row?; - - if !build_queue - .has_build_queued(&row.name, &row.version) - .await? - { - info!("queueing rebuild for {} {}...", &row.name, &row.version); - build_queue - .add_crate(&row.name, &row.version, PRIORITY_CONTINUOUS, None) - .await?; - } - } - - Ok(()) -} - -/// Queue rebuilds for failed crates due to a faulty version of rustdoc -/// -/// It is assumed that the version of rustdoc matches the one of rustc, which is persisted in the DB. -/// The priority of the resulting rebuild requests will be lower than previously failed builds. -/// If a crate is already queued to be rebuilt, it will not be requeued. -/// Start date is inclusive, end date is exclusive. -#[instrument(skip_all)] -pub async fn queue_rebuilds_faulty_rustdoc( - conn: &mut sqlx::PgConnection, - build_queue: &AsyncBuildQueue, - start_nightly_date: &NaiveDate, - end_nightly_date: &Option, -) -> Result { - let end_nightly_date = - end_nightly_date.unwrap_or_else(|| start_nightly_date.succ_opt().unwrap()); - let mut results = sqlx::query!( - r#" -SELECT c.name, - r.version AS "version: Version" -FROM crates AS c - JOIN releases AS r - ON c.id = r.crate_id - JOIN release_build_status AS rbs - ON rbs.rid = r.id - JOIN builds AS b - ON b.rid = r.id - AND b.build_finished = rbs.last_build_time - AND b.rustc_nightly_date >= $1 - AND b.rustc_nightly_date < $2 - - -"#, - start_nightly_date, - end_nightly_date - ) - .fetch(&mut *conn); - - let mut results_count = 0; - while let Some(row) = results.next().await { - let row = row?; - - if !build_queue - .has_build_queued(&row.name, &row.version) - .await? - { - results_count += 1; - info!( - name=%row.name, - version=%row.version, - priority=PRIORITY_BROKEN_RUSTDOC, - "queueing rebuild" - ); - build_queue - .add_crate(&row.name, &row.version, PRIORITY_BROKEN_RUSTDOC, None) - .await?; - } - } - - Ok(results_count) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::types::BuildStatus; - use crate::test::{FakeBuild, KRATE, TestEnvironment, V1, V2}; - use chrono::Utc; - use std::time::Duration; - - #[tokio::test(flavor = "multi_thread")] - async fn test_rebuild_when_old() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_queued_rebuilds(Some(100)) - .build()?, - ) - .await?; - - env.fake_release() - .await - .name("foo") - .version(V1) - .builds(vec![ - FakeBuild::default().rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)"), - ]) - .create() - .await?; - - let build_queue = env.async_build_queue(); - assert!(build_queue.queued_crates().await?.is_empty()); - - let mut conn = env.async_db().async_conn().await; - queue_rebuilds(&mut conn, env.config(), build_queue).await?; - - let queue = build_queue.queued_crates().await?; - assert_eq!(queue.len(), 1); - assert_eq!(queue[0].name, "foo"); - assert_eq!(queue[0].version, V1); - assert_eq!(queue[0].priority, PRIORITY_CONTINUOUS); - - Ok(()) - } - - /// Verifies whether a rebuild is queued for all releases with the latest build performed with a specific nightly version of rustdoc - #[tokio::test(flavor = "multi_thread")] - async fn test_rebuild_broken_rustdoc_specific_date_simple() -> Result<()> { - let env = TestEnvironment::new().await?; - - // Matrix of test builds (crate name, nightly date, version) - let build_matrix = [ - // Should be skipped since this is not the latest build for this release - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), V1), - // All those should match - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V2), - ("foo2", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), - // Should be skipped since the nightly doesn't match - ("foo2", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V2), - ]; - for build in build_matrix.into_iter() { - let (crate_name, nightly, version) = build; - env.fake_release() - .await - .name(crate_name) - .version(version) - .builds(vec![ - FakeBuild::default() - .rustc_version( - format!( - "rustc 1.84.0-nightly (e7c0d2750 {})", - nightly.format("%Y-%m-%d") - ) - .as_str(), - ) - .build_status(BuildStatus::Failure), - ]) - .create() - .await?; - } - - let build_queue = env.async_build_queue(); - assert!(build_queue.queued_crates().await?.is_empty()); - - let mut conn = env.async_db().async_conn().await; - queue_rebuilds_faulty_rustdoc( - &mut conn, - build_queue, - &NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), - &None, - ) - .await?; - - let queue = build_queue.queued_crates().await?; - assert_eq!(queue.len(), 3); - assert_eq!(queue[0].name, "foo1"); - assert_eq!(queue[0].version, V1); - assert_eq!(queue[0].priority, PRIORITY_BROKEN_RUSTDOC); - assert_eq!(queue[1].name, "foo1"); - assert_eq!(queue[1].version, V2); - assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); - assert_eq!(queue[2].name, "foo2"); - assert_eq!(queue[2].version, V1); - assert_eq!(queue[2].priority, PRIORITY_BROKEN_RUSTDOC); - - Ok(()) - } - - /// Verifies whether a rebuild is NOT queued for any crate if the nightly specified doesn't match any latest build of any release - #[tokio::test(flavor = "multi_thread")] - async fn test_rebuild_broken_rustdoc_specific_date_skipped() -> Result<()> { - let env = TestEnvironment::new().await?; - - // Matrix of test builds (crate name, nightly date, version) - let build_matrix = [ - // Should be skipped since this is not the latest build for this release even if the nightly matches - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V1), - // Should be skipped since the nightly doesn't match - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), - // Should be skipped since the nightly doesn't match - ("foo2", NaiveDate::from_ymd_opt(2020, 10, 4).unwrap(), V1), - ]; - for build in build_matrix.into_iter() { - let (crate_name, nightly, version) = build; - env.fake_release() - .await - .name(crate_name) - .version(version) - .builds(vec![ - FakeBuild::default() - .rustc_version( - format!( - "rustc 1.84.0-nightly (e7c0d2750 {})", - nightly.format("%Y-%m-%d") - ) - .as_str(), - ) - .build_status(BuildStatus::Failure), - ]) - .create() - .await?; - } - - let build_queue = env.async_build_queue(); - assert!(build_queue.queued_crates().await?.is_empty()); - - let mut conn = env.async_db().async_conn().await; - queue_rebuilds_faulty_rustdoc( - &mut conn, - build_queue, - &NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), - &None, - ) - .await?; - - let queue = build_queue.queued_crates().await?; - assert_eq!(queue.len(), 0); - - Ok(()) - } - - /// Verifies whether a rebuild is queued for all releases with the latest build performed with a nightly version between two dates - #[tokio::test(flavor = "multi_thread")] - async fn test_rebuild_broken_rustdoc_date_range() -> Result<()> { - let env = TestEnvironment::new().await?; - - // Matrix of test builds (crate name, nightly date, version) - let build_matrix = [ - // Should be skipped since this is not the latest build for this release - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 1).unwrap(), V1), - // All those should match - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), V1), - ("foo1", NaiveDate::from_ymd_opt(2020, 10, 3).unwrap(), V2), - ("foo2", NaiveDate::from_ymd_opt(2020, 10, 4).unwrap(), V1), - // Should be skipped since the nightly doesn't match (end date is exclusive) - ("foo2", NaiveDate::from_ymd_opt(2020, 10, 5).unwrap(), V2), - ]; - for build in build_matrix.into_iter() { - let (crate_name, nightly, version) = build; - env.fake_release() - .await - .name(crate_name) - .version(version) - .builds(vec![ - FakeBuild::default() - .rustc_version( - format!( - "rustc 1.84.0-nightly (e7c0d2750 {})", - nightly.format("%Y-%m-%d") - ) - .as_str(), - ) - .build_status(BuildStatus::Failure), - ]) - .create() - .await?; - } - - let build_queue = env.async_build_queue(); - assert!(build_queue.queued_crates().await?.is_empty()); - - let mut conn = env.async_db().async_conn().await; - queue_rebuilds_faulty_rustdoc( - &mut conn, - build_queue, - &NaiveDate::from_ymd_opt(2020, 10, 2).unwrap(), - &NaiveDate::from_ymd_opt(2020, 10, 5), - ) - .await?; - - let queue = build_queue.queued_crates().await?; - assert_eq!(queue.len(), 3); - assert_eq!(queue[0].name, "foo1"); - assert_eq!(queue[0].version, V1); - assert_eq!(queue[0].priority, PRIORITY_BROKEN_RUSTDOC); - assert_eq!(queue[1].name, "foo1"); - assert_eq!(queue[1].version, V2); - assert_eq!(queue[1].priority, PRIORITY_BROKEN_RUSTDOC); - assert_eq!(queue[2].name, "foo2"); - assert_eq!(queue[2].version, V1); - assert_eq!(queue[2].priority, PRIORITY_BROKEN_RUSTDOC); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_still_rebuild_when_full_with_failed() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_queued_rebuilds(Some(1)) - .build()?, - ) - .await?; - - let build_queue = env.async_build_queue(); - build_queue - .add_crate("foo1", &V1, PRIORITY_CONTINUOUS, None) - .await?; - build_queue - .add_crate("foo2", &V1, PRIORITY_CONTINUOUS, None) - .await?; - - let mut conn = env.async_db().async_conn().await; - sqlx::query!("UPDATE queue SET attempt = 99") - .execute(&mut *conn) - .await?; - - assert_eq!(build_queue.queued_crates().await?.len(), 0); - - env.fake_release() - .await - .name("foo") - .version(V1) - .builds(vec![ - FakeBuild::default().rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)"), - ]) - .create() - .await?; - - let build_queue = env.async_build_queue(); - queue_rebuilds(&mut conn, env.config(), build_queue).await?; - - assert_eq!(build_queue.queued_crates().await?.len(), 1); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_dont_rebuild_when_full() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_queued_rebuilds(Some(1)) - .build()?, - ) - .await?; - - let build_queue = env.async_build_queue(); - build_queue - .add_crate("foo1", &V1, PRIORITY_CONTINUOUS, None) - .await?; - build_queue - .add_crate("foo2", &V1, PRIORITY_CONTINUOUS, None) - .await?; - - env.fake_release() - .await - .name("foo") - .version(V1) - .builds(vec![ - FakeBuild::default().rustc_version("rustc 1.84.0-nightly (e7c0d2750 2020-10-15)"), - ]) - .create() - .await?; - - let build_queue = env.async_build_queue(); - assert_eq!(build_queue.queued_crates().await?.len(), 2); - - let mut conn = env.async_db().async_conn().await; - queue_rebuilds(&mut conn, env.config(), build_queue).await?; - - assert_eq!(build_queue.queued_crates().await?.len(), 2); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_add_duplicate_doesnt_fail_last_priority_wins() -> Result<()> { - let env = TestEnvironment::new().await?; - - let queue = env.async_build_queue(); - - queue.add_crate("some_crate", &V1, 0, None).await?; - queue.add_crate("some_crate", &V1, 9, None).await?; - - let queued_crates = queue.queued_crates().await?; - assert_eq!(queued_crates.len(), 1); - assert_eq!(queued_crates[0].priority, 9); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_add_duplicate_resets_attempts_and_priority() -> Result<()> { - let env = - TestEnvironment::with_config(TestEnvironment::base_config().build_attempts(5).build()?) - .await?; - - let queue = env.async_build_queue(); - - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - " - INSERT INTO queue (name, version, priority, attempt, last_attempt ) - VALUES ('failed_crate', $1, 0, 99, NOW())", - V1 as _ - ) - .execute(&mut *conn) - .await?; - - assert_eq!(queue.pending_count().await?, 0); - - queue.add_crate("failed_crate", &V1, 9, None).await?; - - assert_eq!(queue.pending_count().await?, 1); - - let row = sqlx::query!( - "SELECT priority, attempt, last_attempt - FROM queue - WHERE name = $1 AND version = $2", - "failed_crate", - V1 as _ - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(row.priority, 9); - assert_eq!(row.attempt, 0); - assert!(row.last_attempt.is_none()); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_has_build_queued() -> Result<()> { - let env = TestEnvironment::new().await?; - - let queue = env.async_build_queue(); - - queue.add_crate("dummy", &V1, 0, None).await?; - - let mut conn = env.async_db().async_conn().await; - assert!(queue.has_build_queued("dummy", &V1).await.unwrap()); - - sqlx::query!("UPDATE queue SET attempt = 6") - .execute(&mut *conn) - .await - .unwrap(); - - assert!(!queue.has_build_queued("dummy", &V1).await.unwrap()); - - Ok(()) - } - - #[test] - fn test_wait_between_build_attempts() -> Result<()> { - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .build_attempts(99) - .delay_between_build_attempts(Duration::from_secs(1)) - .build()?, - )?; - - let runtime = env.runtime(); - - let queue = env.build_queue(); - - queue.add_crate("krate", &V1, 0, None)?; - - // first let it fail - queue.process_next_crate(|krate| { - assert_eq!(krate.name, "krate"); - anyhow::bail!("simulate a failure"); - })?; - - queue.process_next_crate(|_| { - // this can't happen since we didn't wait between attempts - unreachable!(); - })?; - - runtime.block_on(async { - // fake the build-attempt timestamp so it's older - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - "UPDATE queue SET last_attempt = $1", - Utc::now() - chrono::Duration::try_seconds(60).unwrap() - ) - .execute(&mut *conn) - .await - })?; - - let mut handled = false; - // now we can process it again - queue.process_next_crate(|krate| { - assert_eq!(krate.name, "krate"); - handled = true; - Ok(BuildPackageSummary::default()) - })?; - - assert!(handled); - - Ok(()) - } - - #[test] - fn test_add_and_process_crates() -> Result<()> { - const MAX_ATTEMPTS: u16 = 3; - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .build_attempts(MAX_ATTEMPTS) - .delay_between_build_attempts(Duration::ZERO) - .build()?, - )?; - - let queue = env.build_queue(); - - let test_crates = [ - ("low-priority", 1000), - ("high-priority-foo", -1000), - ("medium-priority", -10), - ("high-priority-bar", -1000), - ("standard-priority", 0), - ("high-priority-baz", -1000), - ]; - for krate in &test_crates { - queue.add_crate(krate.0, &V1, krate.1, None)?; - } - - let assert_next = |name| -> Result<()> { - queue.process_next_crate(|krate| { - assert_eq!(name, krate.name); - Ok(BuildPackageSummary::default()) - })?; - Ok(()) - }; - let assert_next_and_fail = |name| -> Result<()> { - queue.process_next_crate(|krate| { - assert_eq!(name, krate.name); - anyhow::bail!("simulate a failure"); - })?; - Ok(()) - }; - - // The first processed item is the one with the highest priority added first. - assert_next("high-priority-foo")?; - - // Simulate a failure in high-priority-bar. - assert_next_and_fail("high-priority-bar")?; - - // Continue with the next high priority crate. - assert_next("high-priority-baz")?; - - // After all the crates with the max priority are processed, before starting to process - // crates with a lower priority the failed crates with the max priority will be tried - // again. - assert_next("high-priority-bar")?; - - // Continue processing according to the priority. - assert_next("medium-priority")?; - assert_next("standard-priority")?; - - // Simulate the crate failing many times. - for _ in 0..MAX_ATTEMPTS { - assert_next_and_fail("low-priority")?; - } - - // Since low-priority failed many times it will be removed from the queue. Because of - // that the queue should now be empty. - let mut called = false; - queue.process_next_crate(|_| { - called = true; - Ok(BuildPackageSummary::default()) - })?; - assert!(!called, "there were still items in the queue"); - - let collected_metrics = env.collected_metrics(); - - assert_eq!( - collected_metrics - .get_metric("builder", "docsrs.builder.total_builds")? - .get_u64_counter() - .value(), - 9 - ); - - assert_eq!( - collected_metrics - .get_metric("builder", "docsrs.builder.failed_builds")? - .get_u64_counter() - .value(), - 1 - ); - - assert_eq!( - dbg!( - collected_metrics - .get_metric("builder", "docsrs.builder.build_time")? - .get_f64_histogram() - .count() - ), - 9 - ); - - Ok(()) - } - - #[test] - fn test_invalidate_cdn_after_error() -> Result<()> { - let mut fastly_api = mockito::Server::new(); - - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .fastly_api_host(fastly_api.url().parse().unwrap()) - .fastly_api_token(Some("test-token".into())) - .fastly_service_sid(Some("test-sid-1".into())) - .build()?, - )?; - - let queue = env.build_queue(); - - let m = fastly_api - .mock("POST", "/service/test-sid-1/purge") - .with_status(200) - .create(); - - queue.add_crate("will_fail", &V1, 0, None)?; - - queue.process_next_crate(|krate| { - assert_eq!("will_fail", krate.name); - anyhow::bail!("simulate a failure"); - })?; - - m.expect(1).assert(); - - Ok(()) - } - #[test] - fn test_invalidate_cdn_after_build() -> Result<()> { - let mut fastly_api = mockito::Server::new(); - - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .fastly_api_host(fastly_api.url().parse().unwrap()) - .fastly_api_token(Some("test-token".into())) - .fastly_service_sid(Some("test-sid-1".into())) - .build()?, - )?; - - let queue = env.build_queue(); - - let m = fastly_api - .mock("POST", "/service/test-sid-1/purge") - .with_status(200) - .create(); - - queue.add_crate("will_succeed", &V1, -1, None)?; - - queue.process_next_crate(|krate| { - assert_eq!("will_succeed", krate.name); - Ok(BuildPackageSummary::default()) - })?; - - m.expect(1).assert(); - - Ok(()) - } - - #[test] - fn test_pending_count() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - - assert_eq!(queue.pending_count()?, 0); - queue.add_crate("foo", &V1, 0, None)?; - assert_eq!(queue.pending_count()?, 1); - queue.add_crate("bar", &V1, 0, None)?; - assert_eq!(queue.pending_count()?, 2); - - queue.process_next_crate(|krate| { - assert_eq!("foo", krate.name); - Ok(BuildPackageSummary::default()) - })?; - assert_eq!(queue.pending_count()?, 1); - - drop(env); - - Ok(()) - } - - #[test] - fn test_prioritized_count() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - - assert_eq!(queue.prioritized_count()?, 0); - queue.add_crate("foo", &V1, 0, None)?; - assert_eq!(queue.prioritized_count()?, 1); - queue.add_crate("bar", &V1, -100, None)?; - assert_eq!(queue.prioritized_count()?, 2); - queue.add_crate("baz", &V1, 100, None)?; - assert_eq!(queue.prioritized_count()?, 2); - - queue.process_next_crate(|krate| { - assert_eq!("bar", krate.name); - Ok(BuildPackageSummary::default()) - })?; - assert_eq!(queue.prioritized_count()?, 1); - - Ok(()) - } - - #[test] - fn test_count_by_priority() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - - assert!(queue.pending_count_by_priority()?.is_empty()); - - queue.add_crate("one", &V1, 1, None)?; - queue.add_crate("two", &V2, 2, None)?; - queue.add_crate("two_more", &V2, 2, None)?; - - assert_eq!( - queue.pending_count_by_priority()?, - HashMap::from_iter(vec![(1, 1), (2, 2)]) - ); - - while queue.pending_count()? > 0 { - queue.process_next_crate(|_| Ok(BuildPackageSummary::default()))?; - } - assert!(queue.pending_count_by_priority()?.is_empty()); - - Ok(()) - } - - #[test] - fn test_failed_count_for_reattempts() -> Result<()> { - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .build_attempts(MAX_ATTEMPTS) - .delay_between_build_attempts(Duration::ZERO) - .build()?, - )?; - - const MAX_ATTEMPTS: u16 = 3; - - let queue = env.build_queue(); - - assert_eq!(queue.failed_count()?, 0); - queue.add_crate("foo", &V1, -100, None)?; - assert_eq!(queue.failed_count()?, 0); - queue.add_crate("bar", &V1, 0, None)?; - - for _ in 0..MAX_ATTEMPTS { - assert_eq!(queue.failed_count()?, 0); - queue.process_next_crate(|krate| { - assert_eq!("foo", krate.name); - Ok(BuildPackageSummary { - should_reattempt: true, - ..Default::default() - }) - })?; - } - assert_eq!(queue.failed_count()?, 1); - - queue.process_next_crate(|krate| { - assert_eq!("bar", krate.name); - Ok(BuildPackageSummary::default()) - })?; - assert_eq!(queue.failed_count()?, 1); - - Ok(()) - } - - #[test] - fn test_failed_count_after_error() -> Result<()> { - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .build_attempts(MAX_ATTEMPTS) - .delay_between_build_attempts(Duration::ZERO) - .build()?, - )?; - - const MAX_ATTEMPTS: u16 = 3; - - let queue = env.build_queue(); - - assert_eq!(queue.failed_count()?, 0); - queue.add_crate("foo", &V1, -100, None)?; - assert_eq!(queue.failed_count()?, 0); - queue.add_crate("bar", &V1, 0, None)?; - - for _ in 0..MAX_ATTEMPTS { - assert_eq!(queue.failed_count()?, 0); - queue.process_next_crate(|krate| { - assert_eq!("foo", krate.name); - anyhow::bail!("this failed"); - })?; - } - assert_eq!(queue.failed_count()?, 1); - - queue.process_next_crate(|krate| { - assert_eq!("bar", krate.name); - Ok(BuildPackageSummary::default()) - })?; - assert_eq!(queue.failed_count()?, 1); - - Ok(()) - } - - #[test] - fn test_queued_crates() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - - let test_crates = [("bar", 0), ("foo", -10), ("baz", 10)]; - for krate in &test_crates { - queue.add_crate(krate.0, &V1, krate.1, None)?; - } - - assert_eq!( - vec![ - ("foo".into(), V1, -10), - ("bar".into(), V1, 0), - ("baz".into(), V1, 10), - ], - queue - .queued_crates()? - .into_iter() - .map(|c| (c.name.clone(), c.version, c.priority)) - .collect::>() - ); - - Ok(()) - } - - #[test] - fn test_last_seen_reference_in_db() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - queue.unlock()?; - assert!(!queue.is_locked()?); - // initial db ref is empty - assert_eq!(queue.last_seen_reference()?, None); - assert!(!queue.is_locked()?); - - let oid = crates_index_diff::gix::ObjectId::from_hex( - b"ffffffffffffffffffffffffffffffffffffffff", - )?; - queue.set_last_seen_reference(oid)?; - - assert_eq!(queue.last_seen_reference()?, Some(oid)); - assert!(!queue.is_locked()?); - - Ok(()) - } - - #[test] - fn test_broken_db_reference_breaks() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - set_config(&mut conn, ConfigName::LastSeenIndexReference, "invalid") - .await - .unwrap(); - }); - - let queue = env.build_queue(); - assert!(queue.last_seen_reference().is_err()); - - Ok(()) - } - - #[test] - fn test_queue_lock() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - // unlocked without config - assert!(!queue.is_locked()?); - - queue.lock()?; - assert!(queue.is_locked()?); - - queue.unlock()?; - assert!(!queue.is_locked()?); - - Ok(()) - } - - #[test] - fn test_add_long_name() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - - let name: String = "krate".repeat(100); - - queue.add_crate(&name, &V1, 0, None)?; - - queue.process_next_crate(|krate| { - assert_eq!(name, krate.name); - Ok(BuildPackageSummary::default()) - })?; - - Ok(()) - } - - #[test] - fn test_add_long_version() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let queue = env.build_queue(); - - let long_version = Version::parse(&format!( - "1.2.3-{}+{}", - "prerelease".repeat(100), - "build".repeat(100) - ))?; - - queue.add_crate("krate", &long_version, 0, None)?; - - queue.process_next_crate(|krate| { - assert_eq!(long_version, krate.version); - Ok(BuildPackageSummary::default()) - })?; - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_delete_version_from_queue() -> Result<()> { - let env = TestEnvironment::new().await?; - - let queue = env.async_build_queue(); - assert_eq!(queue.pending_count().await?, 0); - - queue.add_crate(KRATE, &V1, 0, None).await?; - queue.add_crate(KRATE, &V2, 0, None).await?; - - assert_eq!(queue.pending_count().await?, 2); - queue.remove_version_from_queue(KRATE, &V1).await?; - - assert_eq!(queue.pending_count().await?, 1); - - // only v2 remains - if let [k] = queue.queued_crates().await?.as_slice() { - assert_eq!(k.name, KRATE); - assert_eq!(k.version, V2); - } else { - panic!("expected only one queued crate"); - } - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_delete_crate_from_queue() -> Result<()> { - let env = TestEnvironment::new().await?; - - let queue = env.async_build_queue(); - assert_eq!(queue.pending_count().await?, 0); - - queue.add_crate(KRATE, &V1, 0, None).await?; - queue.add_crate(KRATE, &V2, 0, None).await?; - - assert_eq!(queue.pending_count().await?, 2); - queue.remove_crate_from_queue(KRATE).await?; - - assert_eq!(queue.pending_count().await?, 0); - - Ok(()) - } -} diff --git a/src/config.rs b/src/config.rs index 2f27a9765..87fc52b4e 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,14 +1,11 @@ use crate::storage::StorageKind; -use anyhow::{Context, Result, anyhow, bail}; +use anyhow::{Result, bail}; +use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{ - env::VarError, - error::Error, io, path::{self, Path, PathBuf}, - str::FromStr, time::Duration, }; -use tracing::trace; use url::Url; #[derive(Debug, derive_builder::Builder)] @@ -274,37 +271,3 @@ fn ensure_absolute_path(path: PathBuf) -> io::Result { Ok(path::absolute(&path)?) } } - -fn env(var: &str, default: T) -> Result -where - T: FromStr, - T::Err: Error + Send + Sync + 'static, -{ - Ok(maybe_env(var)?.unwrap_or(default)) -} - -fn require_env(var: &str) -> Result -where - T: FromStr, - ::Err: Error + Send + Sync + 'static, -{ - maybe_env(var)?.with_context(|| anyhow!("configuration variable {} is missing", var)) -} - -fn maybe_env(var: &str) -> Result> -where - T: FromStr, - T::Err: Error + Send + Sync + 'static, -{ - match std::env::var(var) { - Ok(content) => Ok(content - .parse::() - .map(Some) - .with_context(|| format!("failed to parse configuration variable {var}"))?), - Err(VarError::NotPresent) => { - trace!("optional configuration variable {} is not set", var); - Ok(None) - } - Err(VarError::NotUnicode(_)) => Err(anyhow!("configuration variable {} is not UTF-8", var)), - } -} diff --git a/src/db/types/mod.rs b/src/db/types/mod.rs index a7e863537..e08784ada 100644 --- a/src/db/types/mod.rs +++ b/src/db/types/mod.rs @@ -3,7 +3,6 @@ use serde::{Deserialize, Serialize}; pub mod dependencies; pub mod krate_name; -pub mod version; #[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] #[sqlx(transparent)] diff --git a/src/lib.rs b/src/lib.rs index 09f6873d1..5e52e4252 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,31 +2,24 @@ //! documentation of crates for the Rust Programming Language. #![allow(clippy::cognitive_complexity)] -pub use self::build_queue::{ - AsyncBuildQueue, BuildQueue, queue_rebuilds, queue_rebuilds_faulty_rustdoc, -}; pub use self::config::Config; pub use self::context::Context; pub use self::docbuilder::PackageKind; pub use self::docbuilder::{BuildPackageSummary, RustwideBuilder}; -pub use self::index::Index; pub use self::registry_api::RegistryApi; pub use self::storage::{AsyncStorage, Storage}; pub use self::web::start_web_server; pub use font_awesome_as_a_crate::icons; -mod build_queue; pub mod cdn; mod config; mod context; pub mod db; mod docbuilder; mod error; -pub mod index; pub mod metrics; mod registry_api; -pub mod repositories; pub mod storage; #[cfg(test)] mod test; diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs index 49ec2857e..eaa13f986 100644 --- a/src/metrics/mod.rs +++ b/src/metrics/mod.rs @@ -1,4 +1,3 @@ -pub(crate) mod otel; pub(crate) mod service; /// the measured times from cdn invalidations, meaning: diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs index 1cefffcce..9c7ac2e80 100644 --- a/src/utils/daemon.rs +++ b/src/utils/daemon.rs @@ -3,9 +3,8 @@ //! This daemon will start web server, track new packages and build them use crate::{ - AsyncBuildQueue, Config, Context, Index, RustwideBuilder, + Context, RustwideBuilder, metrics::service::OtelServiceMetrics, - queue_rebuilds, utils::{queue_builder, report_error}, web::start_web_server, }; @@ -14,103 +13,8 @@ use std::future::Future; use std::sync::Arc; use std::thread; use std::time::Duration; -use tokio::{runtime, time::Instant}; -use tracing::{debug, info, trace}; - -/// Run the registry watcher -/// NOTE: this should only be run once, otherwise crates would be added -/// to the queue multiple times. -pub async fn watch_registry(build_queue: &AsyncBuildQueue, config: &Config) -> Result<(), Error> { - let mut last_gc = Instant::now(); - - loop { - if build_queue.is_locked().await? { - debug!("Queue is locked, skipping checking new crates"); - } else { - debug!("Checking new crates"); - let index = Index::from_config(config).await?; - match build_queue - .get_new_crates(&index) - .await - .context("Failed to get new crates") - { - Ok(n) => debug!("{} crates added to queue", n), - Err(e) => report_error(&e), - } - - if last_gc.elapsed().as_secs() >= config.registry_gc_interval { - index.run_git_gc().await; - last_gc = Instant::now(); - } - } - tokio::time::sleep(config.delay_between_registry_fetches).await; - } -} - -fn start_registry_watcher(context: &Context) -> Result<(), Error> { - let build_queue = context.async_build_queue.clone(); - let config = context.config.clone(); - - context.runtime.spawn(async move { - // space this out to prevent it from clashing against the queue-builder thread on launch - tokio::time::sleep(Duration::from_secs(30)).await; - - watch_registry(&build_queue, &config).await - }); - - Ok(()) -} - -pub fn start_background_repository_stats_updater(context: &Context) -> Result<(), Error> { - // This call will still skip github repositories updates and continue if no token is provided - // (gitlab doesn't require to have a token). The only time this can return an error is when - // creating a pool or if config fails, which shouldn't happen here because this is run right at - // startup. - let updater = context.repository_stats_updater.clone(); - let runtime = context.runtime.clone(); - async_cron( - &runtime, - "repository stats updater", - Duration::from_secs(60 * 60), - move || { - let updater = updater.clone(); - async move { - updater.update_all_crates().await?; - Ok(()) - } - }, - ); - Ok(()) -} - -pub fn start_background_queue_rebuild(context: &Context) -> Result<(), Error> { - let runtime = context.runtime.clone(); - let pool = context.pool.clone(); - let config = context.config.clone(); - let build_queue = context.async_build_queue.clone(); - - if config.max_queued_rebuilds.is_none() { - info!("rebuild config incomplete, skipping rebuild queueing"); - return Ok(()); - } - - async_cron( - &runtime, - "background queue rebuilder", - Duration::from_secs(60 * 60), - move || { - let pool = pool.clone(); - let build_queue = build_queue.clone(); - let config = config.clone(); - async move { - let mut conn = pool.get_async().await?; - queue_rebuilds(&mut conn, &config, &build_queue).await?; - Ok(()) - } - }, - ); - Ok(()) -} +use tokio::runtime; +use tracing::{info, trace}; pub fn start_background_service_metric_collector(context: &Context) -> Result<(), Error> { let runtime = context.runtime.clone(); From 7f414baa30f917403d69c95a3e5cd297a78c8d4e Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 5 Dec 2025 16:48:17 +0100 Subject: [PATCH 02/46] WIP --- Cargo.lock | 7 + Cargo.toml | 10 +- crates/docs_rs_build_queue/src/lib.rs | 3 +- crates/docs_rs_watcher/Cargo.toml | 4 + .../docs_rs_watcher/src}/consistency/data.rs | 2 +- crates/docs_rs_watcher/src/consistency/db.rs | 162 +++++++++ .../docs_rs_watcher/src}/consistency/diff.rs | 208 +++++------ .../docs_rs_watcher/src}/consistency/index.rs | 3 +- crates/docs_rs_watcher/src/consistency/mod.rs | 343 ++++++++++++++++++ crates/docs_rs_watcher/src/index.rs | 16 +- crates/docs_rs_watcher/src/lib.rs | 2 + crates/docs_rs_watcher/src/utils.rs | 43 +++ src/cdn/mod.rs | 6 +- src/context.rs | 13 +- src/db/mod.rs | 2 - src/db/pool.rs | 243 ------------- src/docbuilder/rustwide_builder.rs | 9 +- src/metrics/service.rs | 3 +- src/utils/consistency/db.rs | 158 -------- src/utils/consistency/mod.rs | 338 ----------------- src/utils/mod.rs | 47 +-- 21 files changed, 702 insertions(+), 920 deletions(-) rename {src/utils => crates/docs_rs_watcher/src}/consistency/data.rs (87%) create mode 100644 crates/docs_rs_watcher/src/consistency/db.rs rename {src/utils => crates/docs_rs_watcher/src}/consistency/diff.rs (57%) rename {src/utils => crates/docs_rs_watcher/src}/consistency/index.rs (95%) create mode 100644 crates/docs_rs_watcher/src/consistency/mod.rs create mode 100644 crates/docs_rs_watcher/src/utils.rs delete mode 100644 src/db/pool.rs delete mode 100644 src/utils/consistency/db.rs delete mode 100644 src/utils/consistency/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ffe8964cc..76deb393b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1965,7 +1965,10 @@ dependencies = [ "dashmap", "derive_builder", "derive_more 2.0.1", + "docs_rs_build_queue", + "docs_rs_database", "docs_rs_env_vars", + "docs_rs_opentelemetry", "docsrs-metadata", "flate2", "fn-error-context", @@ -2098,7 +2101,11 @@ dependencies = [ "crates-index", "crates-index-diff", "docs_rs_build_queue", + "docs_rs_database", "docs_rs_env_vars", + "itertools 0.14.0", + "rayon", + "sqlx", "tokio", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index 73907f31e..023ab84a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ build = "build.rs" edition = "2024" [workspace] +resolver = "2" members = ["crates/*"] [workspace.dependencies] @@ -25,12 +26,14 @@ semver = { version = "1.0.4", features = ["serde"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = "3.4.0" +itertools = { version = "0.14.0" } sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "sqlite", "chrono" ] } strum = { version = "0.27.0", features = ["derive"] } thiserror = "2.0.3" tokio = { version = "1.0", features = ["rt-multi-thread", "signal", "macros", "process", "sync"] } tracing = "0.1.37" url = { version = "2.1.1", features = ["serde"] } +rayon = "1.6.1" [dependencies] anyhow = { workspace = true } @@ -54,6 +57,9 @@ dashmap = "6.0.0" derive_builder = "0.20.2" derive_more = { workspace = true } docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } +docs_rs_database = { path = "crates/docs_rs_database" } +docs_rs_build_queue = { path = "crates/docs_rs_build_queue" } +docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } docsrs-metadata = { path = "crates/metadata" } flate2 = "1.1.1" fn-error-context = "0.2.0" @@ -63,7 +69,7 @@ getrandom = "0.3.1" hex = "0.4.3" hostname = "0.4.0" http = "1.0.0" -itertools = { version = "0.14.0" } +itertools = { workspace = true } log = "0.4" lol_html = "2.0.0" md5 = "0.8.0" @@ -77,7 +83,7 @@ opentelemetry_sdk = { workspace = true } path-slash = "0.2.0" percent-encoding = "2.2.0" phf = "0.13.1" -rayon = "1.6.1" +rayon = { workspace = true } regex = "1" reqwest = { version = "0.12", features = ["json", "gzip"] } rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 6cc30932b..31365a30c 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -2,9 +2,10 @@ mod config; mod metrics; mod rebuilds; +pub use config::Config; + use anyhow::Result; use chrono::NaiveDate; -use config::Config; use docs_rs_database::{ Pool, service_config::{ConfigName, get_config, set_config}, diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index d0c0e6a2e..bca1a13bd 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -7,8 +7,12 @@ edition = "2024" anyhow = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} +docs_rs_database = { path = "../docs_rs_database" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true } +itertools = { workspace = true } +sqlx = { workspace = true } +rayon = { workspace = true } diff --git a/src/utils/consistency/data.rs b/crates/docs_rs_watcher/src/consistency/data.rs similarity index 87% rename from src/utils/consistency/data.rs rename to crates/docs_rs_watcher/src/consistency/data.rs index c38333f50..583749170 100644 --- a/src/utils/consistency/data.rs +++ b/crates/docs_rs_watcher/src/consistency/data.rs @@ -1,4 +1,4 @@ -use crate::db::types::version::Version; +use docs_rs_database::types::version::Version; #[derive(Clone, PartialEq, Debug)] pub(super) struct Crate { diff --git a/crates/docs_rs_watcher/src/consistency/db.rs b/crates/docs_rs_watcher/src/consistency/db.rs new file mode 100644 index 000000000..904e3e532 --- /dev/null +++ b/crates/docs_rs_watcher/src/consistency/db.rs @@ -0,0 +1,162 @@ +use super::data::{Crate, Crates, Release, Releases}; +use anyhow::Result; +use docs_rs_build_queue::Config as BuildQueueConfig; +use docs_rs_database::types::version::Version; +use itertools::Itertools; + +pub(super) async fn load( + conn: &mut sqlx::PgConnection, + config: &BuildQueueConfig, +) -> Result { + let rows = sqlx::query!( + r#"SELECT + name as "name!", + version as "version!: Version", + yanked + FROM ( + SELECT + crates.name, + releases.version, + releases.yanked + FROM crates + INNER JOIN releases ON releases.crate_id = crates.id + UNION ALL + -- crates & releases that are already queued + -- don't have to be requeued. + SELECT + queue.name, + queue.version, + NULL as yanked + FROM queue + LEFT OUTER JOIN crates ON crates.name = queue.name + LEFT OUTER JOIN releases ON ( + releases.crate_id = crates.id AND + releases.version = queue.version + ) + WHERE queue.attempt < $1 AND ( + crates.id IS NULL OR + releases.id IS NULL + ) + ) AS inp + ORDER BY name"#, + config.build_attempts as i32, + ) + .fetch_all(conn) + .await?; + + let mut crates = Crates::new(); + + for (crate_name, release_rows) in &rows.iter().chunk_by(|row| row.name.clone()) { + let mut releases: Releases = release_rows + .map(|row| Release { + version: row.version.clone(), + yanked: row.yanked, + }) + .collect(); + + releases.sort_by(|lhs, rhs| lhs.version.cmp(&rhs.version)); + + crates.push(Crate { + name: crate_name, + releases, + }); + } + + Ok(crates) +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::{V1, V2, V3, async_wrapper}; +// use pretty_assertions::assert_eq; + +// #[test] +// fn test_load() { +// async_wrapper(|env| async move { +// env.async_build_queue() +// .add_crate("queued", &V1, 0, None) +// .await?; +// env.fake_release() +// .await +// .name("krate") +// .version(V2) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("krate") +// .version(V3) +// .yanked(true) +// .create() +// .await?; + +// // these two releases are there to ensure we sort correctly. +// // In the past, we sorted the version (from the crates index & our database) +// // as string, which lead to "0.10.3" coming before "0.9.3". +// // When both sides are sorted the same way, this is fine and doesn't break the +// // consistency check. +// // But after migrating everything to using `semver::Version`, the sorting changed +// // on the index-side, while we still sorted by string on the database side. +// // +// // Since I still run the consistency check manually, every now and then, this wasn't +// // an issue, because I saw the odd huge difference. +// // +// // The solution is to sort both sides semver correctly. +// const V0_9_3: Version = Version::new(0, 9, 3); +// const V0_10_3: Version = Version::new(0, 10, 3); +// env.fake_release() +// .await +// .name("krate") +// .version(V0_9_3) +// .yanked(false) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("krate") +// .version(V0_10_3) +// .yanked(false) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// let result = load(&mut conn, env.config()).await?; + +// assert_eq!( +// result, +// vec![ +// Crate { +// name: "krate".into(), +// releases: vec![ +// Release { +// version: V0_9_3, +// yanked: Some(false), +// }, +// Release { +// version: V0_10_3, +// yanked: Some(false), +// }, +// Release { +// version: V2, +// yanked: Some(false), +// }, +// Release { +// version: V3, +// yanked: Some(true), +// } +// ] +// }, +// Crate { +// name: "queued".into(), +// releases: vec![Release { +// version: V1, +// yanked: None, +// }] +// }, +// ] +// ); +// Ok(()) +// }) +// } +// } diff --git a/src/utils/consistency/diff.rs b/crates/docs_rs_watcher/src/consistency/diff.rs similarity index 57% rename from src/utils/consistency/diff.rs rename to crates/docs_rs_watcher/src/consistency/diff.rs index efaa7fc9e..761a74d56 100644 --- a/src/utils/consistency/diff.rs +++ b/crates/docs_rs_watcher/src/consistency/diff.rs @@ -1,5 +1,5 @@ use super::data::Crate; -use crate::db::types::version::Version; +use docs_rs_database::types::version::Version; use itertools::{ EitherOrBoth::{Both, Left, Right}, Itertools, @@ -101,106 +101,106 @@ where result } -#[cfg(test)] -mod tests { - use crate::test::{V2, V3}; - - use super::super::data::Release; - use super::*; - use std::iter; - - #[test] - fn test_empty() { - assert!(calculate_diff(iter::empty(), iter::empty()).is_empty()); - } - - #[test] - fn test_crate_not_in_index() { - let db_releases = [Crate { - name: "krate".into(), - releases: vec![], - }]; - - assert_eq!( - calculate_diff(db_releases.iter(), [].iter()), - vec![Difference::CrateNotInIndex("krate".into())] - ); - } - - #[test] - fn test_crate_not_in_db() { - let index_releases = [Crate { - name: "krate".into(), - releases: vec![ - Release { - version: V2, - yanked: Some(false), - }, - Release { - version: V3, - yanked: Some(true), - }, - ], - }]; - - assert_eq!( - calculate_diff([].iter(), index_releases.iter()), - vec![Difference::CrateNotInDb("krate".into(), vec![V2, V3])] - ); - } - - #[test] - fn test_yank_diff() { - let db_releases = [Crate { - name: "krate".into(), - releases: vec![ - Release { - version: V2, - yanked: Some(true), - }, - Release { - version: V3, - yanked: Some(true), - }, - ], - }]; - let index_releases = [Crate { - name: "krate".into(), - releases: vec![ - Release { - version: V2, - yanked: Some(false), - }, - Release { - version: V3, - yanked: Some(true), - }, - ], - }]; - - assert_eq!( - calculate_diff(db_releases.iter(), index_releases.iter()), - vec![Difference::ReleaseYank("krate".into(), V2, false,)] - ); - } - - #[test] - fn test_yank_diff_without_db_data() { - let db_releases = [Crate { - name: "krate".into(), - releases: vec![Release { - version: V2, - yanked: None, - }], - }]; - let index_releases = [Crate { - name: "krate".into(), - releases: vec![Release { - version: V2, - yanked: Some(false), - }], - }]; - - assert!(calculate_diff(db_releases.iter(), index_releases.iter()).is_empty()); - } -} +// #[cfg(test)] +// mod tests { +// use crate::test::{V2, V3}; + +// use super::super::data::Release; +// use super::*; +// use std::iter; + +// #[test] +// fn test_empty() { +// assert!(calculate_diff(iter::empty(), iter::empty()).is_empty()); +// } + +// #[test] +// fn test_crate_not_in_index() { +// let db_releases = [Crate { +// name: "krate".into(), +// releases: vec![], +// }]; + +// assert_eq!( +// calculate_diff(db_releases.iter(), [].iter()), +// vec![Difference::CrateNotInIndex("krate".into())] +// ); +// } + +// #[test] +// fn test_crate_not_in_db() { +// let index_releases = [Crate { +// name: "krate".into(), +// releases: vec![ +// Release { +// version: V2, +// yanked: Some(false), +// }, +// Release { +// version: V3, +// yanked: Some(true), +// }, +// ], +// }]; + +// assert_eq!( +// calculate_diff([].iter(), index_releases.iter()), +// vec![Difference::CrateNotInDb("krate".into(), vec![V2, V3])] +// ); +// } + +// #[test] +// fn test_yank_diff() { +// let db_releases = [Crate { +// name: "krate".into(), +// releases: vec![ +// Release { +// version: V2, +// yanked: Some(true), +// }, +// Release { +// version: V3, +// yanked: Some(true), +// }, +// ], +// }]; +// let index_releases = [Crate { +// name: "krate".into(), +// releases: vec![ +// Release { +// version: V2, +// yanked: Some(false), +// }, +// Release { +// version: V3, +// yanked: Some(true), +// }, +// ], +// }]; + +// assert_eq!( +// calculate_diff(db_releases.iter(), index_releases.iter()), +// vec![Difference::ReleaseYank("krate".into(), V2, false,)] +// ); +// } + +// #[test] +// fn test_yank_diff_without_db_data() { +// let db_releases = [Crate { +// name: "krate".into(), +// releases: vec![Release { +// version: V2, +// yanked: None, +// }], +// }]; +// let index_releases = [Crate { +// name: "krate".into(), +// releases: vec![Release { +// version: V2, +// yanked: Some(false), +// }], +// }]; + +// assert!(calculate_diff(db_releases.iter(), index_releases.iter()).is_empty()); +// } +// } diff --git a/src/utils/consistency/index.rs b/crates/docs_rs_watcher/src/consistency/index.rs similarity index 95% rename from src/utils/consistency/index.rs rename to crates/docs_rs_watcher/src/consistency/index.rs index 4370daefb..4f12f6b9d 100644 --- a/src/utils/consistency/index.rs +++ b/crates/docs_rs_watcher/src/consistency/index.rs @@ -1,6 +1,7 @@ use super::data::{Crate, Crates, Release, Releases}; -use crate::{Config, db::types::version::Version, utils::run_blocking}; +use crate::{config::Config, utils::run_blocking}; use anyhow::Result; +use docs_rs_database::types::version::Version; use rayon::iter::ParallelIterator; use tracing::debug; diff --git a/crates/docs_rs_watcher/src/consistency/mod.rs b/crates/docs_rs_watcher/src/consistency/mod.rs new file mode 100644 index 000000000..5ac4156ab --- /dev/null +++ b/crates/docs_rs_watcher/src/consistency/mod.rs @@ -0,0 +1,343 @@ +// use crate::build_queue::PRIORITY_CONSISTENCY_CHECK; +// use crate::{Context, db::delete}; +use anyhow::{Context as _, Result}; +use docs_rs_build_queue::PRIORITY_CONSISTENCY_CHECK; +use itertools::Itertools; +use tracing::{info, warn}; + +mod data; +mod db; +mod diff; +mod index; + +/// consistency check +/// +/// will compare our database with the local crates.io index and +/// apply any changes that we find in the index but not our database. +/// +/// Differences that we check for, and the activities: +/// * release in index, but not our DB => queue a build for this release. +/// * crate in index, but not in our DB => queue builds for all versions of that crate. +/// * release in DB, but not in the index => delete the release from our DB & storage. +/// * crate in our DB, but not in the index => delete the whole crate from our DB & storage. +/// * different yank-state between DB & Index => update the yank-state in our DB +/// +/// Even when activities fail, the command can just be re-run. While the diff calculation will +/// be repeated, we won't re-execute fixing activities. +pub async fn run_check( + // ctx: &Context, + dry_run: bool, +) -> Result<()> { + todo!(); + // info!("Loading data from database..."); + // let mut conn = ctx.pool.get_async().await?; + // let db_data = db::load(&mut conn, &ctx.config) + // .await + // .context("Loading crate data from database for consistency check")?; + + // tracing::info!("Loading data from index..."); + // let index_data = index::load(&ctx.config) + // .await + // .context("Loading crate data from index for consistency check")?; + + // let diff = diff::calculate_diff(db_data.iter(), index_data.iter()); + // let result = handle_diff(ctx, diff.iter(), dry_run).await?; + + // println!("============"); + // println!("SUMMARY"); + // println!("============"); + // println!("difference found:"); + // for (key, count) in diff.iter().counts_by(|el| match el { + // diff::Difference::CrateNotInIndex(_) => "CrateNotInIndex", + // diff::Difference::CrateNotInDb(_, _) => "CrateNotInDb", + // diff::Difference::ReleaseNotInIndex(_, _) => "ReleaseNotInIndex", + // diff::Difference::ReleaseNotInDb(_, _) => "ReleaseNotInDb", + // diff::Difference::ReleaseYank(_, _, _) => "ReleaseYank", + // }) { + // println!("{key:17} => {count:4}"); + // } + + // println!("============"); + // if dry_run { + // println!("activities that would have been triggered:"); + // } else { + // println!("activities triggered:"); + // } + // println!("builds queued: {:4}", result.builds_queued); + // println!("crates deleted: {:4}", result.crates_deleted); + // println!("releases deleted: {:4}", result.releases_deleted); + // println!("yanks corrected: {:4}", result.yanks_corrected); + + // Ok(()) +} + +#[derive(Default)] +struct HandleResult { + builds_queued: u32, + crates_deleted: u32, + releases_deleted: u32, + yanks_corrected: u32, +} + +// async fn handle_diff<'a, I>(ctx: &Context, iter: I, dry_run: bool) -> Result +// where +// I: Iterator, +// { +// let mut result = HandleResult::default(); + +// let mut conn = ctx.pool.get_async().await?; + +// for difference in iter { +// println!("{difference}"); + +// match difference { +// diff::Difference::CrateNotInIndex(name) => { +// if !dry_run +// && let Err(err) = +// delete::delete_crate(&mut conn, &ctx.async_storage, &ctx.config, name).await +// { +// warn!("{:?}", err); +// } +// result.crates_deleted += 1; +// } +// diff::Difference::CrateNotInDb(name, versions) => { +// for version in versions { +// if !dry_run +// && let Err(err) = ctx +// .async_build_queue +// .add_crate(name, version, PRIORITY_CONSISTENCY_CHECK, None) +// .await +// { +// warn!("{:?}", err); +// } +// result.builds_queued += 1; +// } +// } +// diff::Difference::ReleaseNotInIndex(name, version) => { +// if !dry_run +// && let Err(err) = delete::delete_version( +// &mut conn, +// &ctx.async_storage, +// &ctx.config, +// name, +// version, +// ) +// .await +// { +// warn!("{:?}", err); +// } +// result.releases_deleted += 1; +// } +// diff::Difference::ReleaseNotInDb(name, version) => { +// if !dry_run +// && let Err(err) = ctx +// .async_build_queue +// .add_crate(name, version, PRIORITY_CONSISTENCY_CHECK, None) +// .await +// { +// warn!("{:?}", err); +// } +// result.builds_queued += 1; +// } +// diff::Difference::ReleaseYank(name, version, yanked) => { +// if !dry_run +// && let Err(err) = ctx +// .async_build_queue +// .set_yanked(name, version, *yanked) +// .await +// { +// warn!("{:?}", err); +// } +// result.yanks_corrected += 1; +// } +// } +// } + +// Ok(result) +// } + +// #[cfg(test)] +// mod tests { +// use super::diff::Difference; +// use super::*; +// use crate::{ +// db::types::version::Version, +// test::{TestEnvironment, V1, V2, async_wrapper}, +// }; +// use sqlx::Row as _; + +// async fn count(env: &TestEnvironment, sql: &str) -> Result { +// let mut conn = env.async_db().async_conn().await; +// Ok(sqlx::query_scalar(sql).fetch_one(&mut *conn).await?) +// } + +// async fn single_row(env: &TestEnvironment, sql: &str) -> Result> +// where +// O: Send + Unpin + for<'r> sqlx::Decode<'r, sqlx::Postgres> + sqlx::Type, +// { +// let mut conn = env.async_db().async_conn().await; +// Ok::<_, anyhow::Error>( +// sqlx::query(sql) +// .fetch_all(&mut *conn) +// .await? +// .into_iter() +// .map(|row| row.get(0)) +// .collect(), +// ) +// } + +// #[test] +// fn test_delete_crate() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("krate") +// .version(V1) +// .version(V2) +// .create() +// .await?; + +// let diff = [Difference::CrateNotInIndex("krate".into())]; + +// // calling with dry-run leads to no change +// handle_diff(&env.context, diff.iter(), true).await?; + +// assert_eq!( +// count(&env, "SELECT count(*) FROM crates WHERE name = 'krate'").await?, +// 1 +// ); + +// // without dry-run the crate will be deleted +// handle_diff(&env.context, diff.iter(), false).await?; + +// assert_eq!( +// count(&env, "SELECT count(*) FROM crates WHERE name = 'krate'").await?, +// 0 +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_delete_release() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("krate") +// .version(V1) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("krate") +// .version(V2) +// .create() +// .await?; + +// let diff = [Difference::ReleaseNotInIndex("krate".into(), V1)]; + +// assert_eq!(count(&env, "SELECT count(*) FROM releases").await?, 2); + +// handle_diff(&env.context, diff.iter(), true).await?; + +// assert_eq!(count(&env, "SELECT count(*) FROM releases").await?, 2); + +// handle_diff(&env.context, diff.iter(), false).await?; + +// assert_eq!( +// single_row::( +// &env, +// r#"SELECT version as "version: Version" FROM releases"# +// ) +// .await?, +// vec![V2] +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_wrong_yank() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("krate") +// .version(V1) +// .yanked(true) +// .create() +// .await?; + +// let diff = [Difference::ReleaseYank("krate".into(), V1, false)]; + +// handle_diff(&env.context, diff.iter(), true).await?; + +// assert_eq!( +// single_row::(&env, "SELECT yanked FROM releases").await?, +// vec![true] +// ); + +// handle_diff(&env.context, diff.iter(), false).await?; + +// assert_eq!( +// single_row::(&env, "SELECT yanked FROM releases").await?, +// vec![false] +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_missing_release_in_db() { +// async_wrapper(|env| async move { +// let diff = [Difference::ReleaseNotInDb("krate".into(), V1)]; + +// handle_diff(&env.context, diff.iter(), true).await?; + +// let build_queue = env.async_build_queue(); + +// assert!(build_queue.queued_crates().await?.is_empty()); + +// handle_diff(&env.context, diff.iter(), false).await?; + +// assert_eq!( +// build_queue +// .queued_crates() +// .await? +// .into_iter() +// .map(|c| (c.name, V1, c.priority)) +// .collect::>(), +// vec![("krate".into(), V1, 15)] +// ); +// Ok(()) +// }) +// } + +// #[test] +// fn test_missing_crate_in_db() { +// async_wrapper(|env| async move { +// let diff = [Difference::CrateNotInDb("krate".into(), vec![V1, V2])]; + +// handle_diff(&env.context, diff.iter(), true).await?; + +// let build_queue = env.async_build_queue(); + +// assert!(build_queue.queued_crates().await?.is_empty()); + +// handle_diff(&env.context, diff.iter(), false).await?; + +// assert_eq!( +// build_queue +// .queued_crates() +// .await? +// .into_iter() +// .map(|c| (c.name, c.version, c.priority)) +// .collect::>(), +// vec![("krate".into(), V1, 15), ("krate".into(), V2, 15)] +// ); +// Ok(()) +// }) +// } +// } diff --git a/crates/docs_rs_watcher/src/index.rs b/crates/docs_rs_watcher/src/index.rs index 01addd16c..2d17f224b 100644 --- a/crates/docs_rs_watcher/src/index.rs +++ b/crates/docs_rs_watcher/src/index.rs @@ -1,15 +1,12 @@ -use crate::{ - Config, - error::Result, - utils::{report_error, run_blocking}, -}; -use anyhow::Context as _; +use crate::{config::Config, utils::run_blocking}; +use anyhow::{Context as _, Result}; use crates_index_diff::{Change, gix, index::diff::Order}; use std::{ path::{Path, PathBuf}, sync::{Arc, Mutex, atomic::AtomicBool}, }; use tokio::process::Command; +use tracing::error; const THREAD_NAME: &str = "crates-index-diff"; @@ -79,7 +76,12 @@ impl Index { .with_context(|| format!("failed to run `git gc --auto`\npath: {:#?}", &self.path)); if let Err(err) = gc { - report_error(&err); + // FIXME: before we had `report_error` here. Is it worth keeping? + error!( + ?err, + path = %self.path.display(), + "failed to run `git gc --auto`" + ); } } diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index 5577fd20d..e0ef24673 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -1,5 +1,7 @@ mod config; +mod consistency; mod index; +mod utils; use anyhow::Error; use docs_rs_build_queue::AsyncBuildQueue; diff --git a/crates/docs_rs_watcher/src/utils.rs b/crates/docs_rs_watcher/src/utils.rs new file mode 100644 index 000000000..dfdc7d075 --- /dev/null +++ b/crates/docs_rs_watcher/src/utils.rs @@ -0,0 +1,43 @@ +use anyhow::{Context as _, Result}; +use std::fmt; +use std::thread; + +/// Move the execution of a blocking function into a separate, new thread. +/// +/// Only for long-running / expensive operations that would block the async runtime or its +/// blocking workerpool. +/// +/// The rule should be: +/// * async stuff -> in the tokio runtime, other async functions +/// * blocking I/O -> `spawn_blocking` +/// * CPU-Bound things: +/// - `render_in_threadpool` (continious load like rendering) +/// - `run_blocking` (sporadic CPU bound load) +/// +/// The thread-name will help us better seeing where our CPU load is coming from on the +/// servers. +/// +/// Generally speaking, using tokio's `spawn_blocking` is also ok-ish, if the work is sporadic. +/// But then I wouldn't get thread-names. +pub(crate) async fn run_blocking(name: N, f: F) -> Result +where + N: Into + fmt::Display, + F: FnOnce() -> Result + Send + 'static, + R: Send + 'static, +{ + let name = name.into(); + let span = tracing::Span::current(); + let (send, recv) = tokio::sync::oneshot::channel(); + thread::Builder::new() + .name(format!("docsrs-{name}")) + .spawn(move || { + let _guard = span.enter(); + + // `.send` only fails when the receiver is dropped while we work, + // at which point we don't need the result anymore. + let _ = send.send(f()); + }) + .with_context(|| format!("couldn't spawn worker thread for {}", &name))?; + + recv.await.context("sender was dropped")? +} diff --git a/src/cdn/mod.rs b/src/cdn/mod.rs index d6332c186..191165193 100644 --- a/src/cdn/mod.rs +++ b/src/cdn/mod.rs @@ -1,8 +1,6 @@ -use crate::{ - Config, db::types::krate_name::KrateName, metrics::otel::AnyMeterProvider, - web::headers::SurrogateKey, -}; +use crate::{Config, db::types::krate_name::KrateName, web::headers::SurrogateKey}; use anyhow::Result; +use docs_rs_opentelemetry::AnyMeterProvider; use opentelemetry::metrics::{Counter, Gauge}; use tracing::{error, info, instrument}; diff --git a/src/context.rs b/src/context.rs index a0dec9fb1..9fc819d10 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,24 +1,17 @@ -use crate::{ - AsyncBuildQueue, AsyncStorage, BuildQueue, Config, RegistryApi, Storage, - cdn::CdnMetrics, - db::Pool, - metrics::otel::{AnyMeterProvider, get_meter_provider}, - repositories::RepositoryStatsUpdater, -}; +use crate::{AsyncStorage, Config, RegistryApi, Storage, cdn::CdnMetrics}; use anyhow::Result; +use docs_rs_database::Pool; +use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; use std::sync::Arc; use tokio::runtime; pub struct Context { pub config: Arc, - pub async_build_queue: Arc, - pub build_queue: Arc, pub storage: Arc, pub async_storage: Arc, pub cdn_metrics: Arc, pub pool: Pool, pub registry_api: Arc, - pub repository_stats_updater: Arc, pub runtime: runtime::Handle, pub meter_provider: AnyMeterProvider, } diff --git a/src/db/mod.rs b/src/db/mod.rs index 9cc702bc0..61d33a6da 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -12,7 +12,6 @@ pub use self::{ delete::{delete_crate, delete_version}, file::{add_path_into_database, add_path_into_remote_archive}, overrides::Overrides, - pool::{AsyncPoolClient, Pool, PoolError}, types::{BuildId, CrateId, ReleaseId}, }; @@ -22,7 +21,6 @@ pub mod delete; pub(crate) mod file; pub(crate) mod mimes; mod overrides; -mod pool; pub mod types; static MIGRATOR: Migrator = sqlx::migrate!(); diff --git a/src/db/pool.rs b/src/db/pool.rs deleted file mode 100644 index dd32f7150..000000000 --- a/src/db/pool.rs +++ /dev/null @@ -1,243 +0,0 @@ -use crate::{Config, metrics::otel::AnyMeterProvider}; -use futures_util::{future::BoxFuture, stream::BoxStream}; -use opentelemetry::metrics::{Counter, ObservableGauge}; -use sqlx::{Executor, postgres::PgPoolOptions}; -use std::{ - ops::{Deref, DerefMut}, - sync::Arc, - time::Duration, -}; -use tokio::runtime; -use tracing::debug; - -const DEFAULT_SCHEMA: &str = "public"; - -#[derive(Debug)] -struct PoolMetrics { - failed_connections: Counter, - _idle_connections: ObservableGauge, - _used_connections: ObservableGauge, - _max_connections: ObservableGauge, -} - -impl PoolMetrics { - fn new(pool: sqlx::PgPool, meter_provider: &AnyMeterProvider) -> Self { - let meter = meter_provider.meter("pool"); - const PREFIX: &str = "docsrs.db.pool"; - Self { - failed_connections: meter - .u64_counter(format!("{PREFIX}.failed_connections")) - .with_unit("1") - .build(), - _idle_connections: meter - .u64_observable_gauge(format!("{PREFIX}.idle_connections")) - .with_unit("1") - .with_callback({ - let pool = pool.clone(); - move |observer| { - observer.observe(pool.num_idle() as u64, &[]); - } - }) - .build(), - _used_connections: meter - .u64_observable_gauge(format!("{PREFIX}.used_connections")) - .with_unit("1") - .with_callback({ - let pool = pool.clone(); - move |observer| { - let used = pool.size() as u64 - pool.num_idle() as u64; - observer.observe(used, &[]); - } - }) - .build(), - _max_connections: meter - .u64_observable_gauge(format!("{PREFIX}.max_connections")) - .with_unit("1") - .with_callback({ - let pool = pool.clone(); - move |observer| { - observer.observe(pool.size() as u64, &[]); - } - }) - .build(), - } - } -} - -#[derive(Debug, Clone)] -pub struct Pool { - async_pool: sqlx::PgPool, - runtime: runtime::Handle, - otel_metrics: Arc, -} - -impl Pool { - pub async fn new( - config: &Config, - otel_meter_provider: &AnyMeterProvider, - ) -> Result { - debug!( - "creating database pool (if this hangs, consider running `docker-compose up -d db s3`)" - ); - Self::new_inner(config, DEFAULT_SCHEMA, otel_meter_provider).await - } - - #[cfg(test)] - pub(crate) async fn new_with_schema( - config: &Config, - schema: &str, - otel_meter_provider: &AnyMeterProvider, - ) -> Result { - Self::new_inner(config, schema, otel_meter_provider).await - } - - async fn new_inner( - config: &Config, - schema: &str, - otel_meter_provider: &AnyMeterProvider, - ) -> Result { - let acquire_timeout = Duration::from_secs(30); - let max_lifetime = Duration::from_secs(30 * 60); - let idle_timeout = Duration::from_secs(10 * 60); - - let async_pool = PgPoolOptions::new() - .max_connections(config.max_pool_size) - .min_connections(config.min_pool_idle) - .max_lifetime(max_lifetime) - .acquire_timeout(acquire_timeout) - .idle_timeout(idle_timeout) - .after_connect({ - let schema = schema.to_owned(); - move |conn, _meta| { - Box::pin({ - let schema = schema.clone(); - - async move { - if schema != DEFAULT_SCHEMA { - conn.execute( - format!("SET search_path TO {schema}, {DEFAULT_SCHEMA};") - .as_str(), - ) - .await?; - } - - Ok(()) - } - }) - } - }) - .connect_lazy(&config.database_url) - .map_err(PoolError::AsyncPoolCreationFailed)?; - - Ok(Pool { - async_pool: async_pool.clone(), - runtime: runtime::Handle::current(), - otel_metrics: Arc::new(PoolMetrics::new(async_pool, otel_meter_provider)), - }) - } - - pub async fn get_async(&self) -> Result { - match self.async_pool.acquire().await { - Ok(conn) => Ok(AsyncPoolClient { - inner: Some(conn), - runtime: self.runtime.clone(), - }), - Err(err) => { - self.otel_metrics.failed_connections.add(1, &[]); - Err(PoolError::AsyncClientError(err)) - } - } - } -} - -/// This impl allows us to use our own pool as an executor for SQLx queries. -impl sqlx::Executor<'_> for &'_ Pool -where - for<'c> &'c mut ::Connection: - sqlx::Executor<'c, Database = sqlx::Postgres>, -{ - type Database = sqlx::Postgres; - - fn fetch_many<'e, 'q: 'e, E>( - self, - query: E, - ) -> BoxStream< - 'e, - Result< - sqlx::Either< - ::QueryResult, - ::Row, - >, - sqlx::Error, - >, - > - where - E: sqlx::Execute<'q, Self::Database> + 'q, - { - self.async_pool.fetch_many(query) - } - - fn fetch_optional<'e, 'q: 'e, E>( - self, - query: E, - ) -> BoxFuture<'e, Result::Row>, sqlx::Error>> - where - E: sqlx::Execute<'q, Self::Database> + 'q, - { - self.async_pool.fetch_optional(query) - } - - fn prepare_with<'e, 'q: 'e>( - self, - sql: &'q str, - parameters: &'e [::TypeInfo], - ) -> BoxFuture<'e, Result<::Statement<'q>, sqlx::Error>> { - self.async_pool.prepare_with(sql, parameters) - } - - fn describe<'e, 'q: 'e>( - self, - sql: &'q str, - ) -> BoxFuture<'e, Result, sqlx::Error>> { - self.async_pool.describe(sql) - } -} - -/// we wrap `sqlx::PoolConnection` so we can drop it in a sync context -/// and enter the runtime. -/// Otherwise dropping the PoolConnection will panic because it can't spawn a task. -#[derive(Debug)] -pub struct AsyncPoolClient { - inner: Option>, - runtime: runtime::Handle, -} - -impl Deref for AsyncPoolClient { - type Target = sqlx::PgConnection; - - fn deref(&self) -> &Self::Target { - self.inner.as_ref().unwrap() - } -} - -impl DerefMut for AsyncPoolClient { - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.as_mut().unwrap() - } -} - -impl Drop for AsyncPoolClient { - fn drop(&mut self) { - let _guard = self.runtime.enter(); - drop(self.inner.take()) - } -} - -#[derive(Debug, thiserror::Error)] -pub enum PoolError { - #[error("failed to create the database connection pool")] - AsyncPoolCreationFailed(#[source] sqlx::Error), - - #[error("failed to get a database connection")] - AsyncClientError(#[source] sqlx::Error), -} diff --git a/src/docbuilder/rustwide_builder.rs b/src/docbuilder/rustwide_builder.rs index 6d22c0033..cd068992f 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/src/docbuilder/rustwide_builder.rs @@ -1,17 +1,16 @@ use crate::{ AsyncStorage, Config, Context, RUSTDOC_STATIC_STORAGE_PREFIX, RegistryApi, Storage, db::{ - BuildId, CrateId, Pool, ReleaseId, add_doc_coverage, add_path_into_remote_archive, + BuildId, CrateId, ReleaseId, add_doc_coverage, add_path_into_remote_archive, blacklist::is_blacklisted, file::{add_path_into_database, file_list_to_json}, finish_build, finish_release, initialize_build, initialize_crate, initialize_release, - types::{BuildStatus, version::Version}, + types::BuildStatus, update_build_with_error, update_crate_data_in_database, }, docbuilder::Limits, error::Result, - metrics::{BUILD_TIME_HISTOGRAM_BUCKETS, DOCUMENTATION_SIZE_BUCKETS, otel::AnyMeterProvider}, - repositories::RepositoryStatsUpdater, + metrics::{BUILD_TIME_HISTOGRAM_BUCKETS, DOCUMENTATION_SIZE_BUCKETS}, storage::{ CompressionAlgorithm, RustdocJsonFormatVersion, compress, get_file_list, rustdoc_archive_path, rustdoc_json_path, source_archive_path, @@ -22,6 +21,8 @@ use crate::{ }, }; use anyhow::{Context as _, Error, anyhow, bail}; +use docs_rs_database::{Pool, types::version::Version}; +use docs_rs_opentelemetry::AnyMeterProvider; use docsrs_metadata::{BuildTargets, DEFAULT_TARGETS, HOST_TARGET, Metadata}; use itertools::Itertools as _; use opentelemetry::metrics::{Counter, Histogram}; diff --git a/src/metrics/service.rs b/src/metrics/service.rs index 27e6b4260..218ef78d9 100644 --- a/src/metrics/service.rs +++ b/src/metrics/service.rs @@ -1,5 +1,6 @@ -use crate::{AsyncBuildQueue, metrics::otel::AnyMeterProvider}; use anyhow::{Error, Result}; +use docs_rs_build_queue::AsyncBuildQueue; +use docs_rs_opentelemetry::AnyMeterProvider; use opentelemetry::{KeyValue, metrics::Gauge}; use std::collections::HashSet; diff --git a/src/utils/consistency/db.rs b/src/utils/consistency/db.rs deleted file mode 100644 index de4607fa5..000000000 --- a/src/utils/consistency/db.rs +++ /dev/null @@ -1,158 +0,0 @@ -use super::data::{Crate, Crates, Release, Releases}; -use crate::{Config, db::types::version::Version}; -use anyhow::Result; -use itertools::Itertools; - -pub(super) async fn load(conn: &mut sqlx::PgConnection, config: &Config) -> Result { - let rows = sqlx::query!( - r#"SELECT - name as "name!", - version as "version!: Version", - yanked - FROM ( - SELECT - crates.name, - releases.version, - releases.yanked - FROM crates - INNER JOIN releases ON releases.crate_id = crates.id - UNION ALL - -- crates & releases that are already queued - -- don't have to be requeued. - SELECT - queue.name, - queue.version, - NULL as yanked - FROM queue - LEFT OUTER JOIN crates ON crates.name = queue.name - LEFT OUTER JOIN releases ON ( - releases.crate_id = crates.id AND - releases.version = queue.version - ) - WHERE queue.attempt < $1 AND ( - crates.id IS NULL OR - releases.id IS NULL - ) - ) AS inp - ORDER BY name"#, - config.build_attempts as i32, - ) - .fetch_all(conn) - .await?; - - let mut crates = Crates::new(); - - for (crate_name, release_rows) in &rows.iter().chunk_by(|row| row.name.clone()) { - let mut releases: Releases = release_rows - .map(|row| Release { - version: row.version.clone(), - yanked: row.yanked, - }) - .collect(); - - releases.sort_by(|lhs, rhs| lhs.version.cmp(&rhs.version)); - - crates.push(Crate { - name: crate_name, - releases, - }); - } - - Ok(crates) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{V1, V2, V3, async_wrapper}; - use pretty_assertions::assert_eq; - - #[test] - fn test_load() { - async_wrapper(|env| async move { - env.async_build_queue() - .add_crate("queued", &V1, 0, None) - .await?; - env.fake_release() - .await - .name("krate") - .version(V2) - .create() - .await?; - env.fake_release() - .await - .name("krate") - .version(V3) - .yanked(true) - .create() - .await?; - - // these two releases are there to ensure we sort correctly. - // In the past, we sorted the version (from the crates index & our database) - // as string, which lead to "0.10.3" coming before "0.9.3". - // When both sides are sorted the same way, this is fine and doesn't break the - // consistency check. - // But after migrating everything to using `semver::Version`, the sorting changed - // on the index-side, while we still sorted by string on the database side. - // - // Since I still run the consistency check manually, every now and then, this wasn't - // an issue, because I saw the odd huge difference. - // - // The solution is to sort both sides semver correctly. - const V0_9_3: Version = Version::new(0, 9, 3); - const V0_10_3: Version = Version::new(0, 10, 3); - env.fake_release() - .await - .name("krate") - .version(V0_9_3) - .yanked(false) - .create() - .await?; - env.fake_release() - .await - .name("krate") - .version(V0_10_3) - .yanked(false) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - let result = load(&mut conn, env.config()).await?; - - assert_eq!( - result, - vec![ - Crate { - name: "krate".into(), - releases: vec![ - Release { - version: V0_9_3, - yanked: Some(false), - }, - Release { - version: V0_10_3, - yanked: Some(false), - }, - Release { - version: V2, - yanked: Some(false), - }, - Release { - version: V3, - yanked: Some(true), - } - ] - }, - Crate { - name: "queued".into(), - releases: vec![Release { - version: V1, - yanked: None, - }] - }, - ] - ); - Ok(()) - }) - } -} diff --git a/src/utils/consistency/mod.rs b/src/utils/consistency/mod.rs deleted file mode 100644 index 81695f246..000000000 --- a/src/utils/consistency/mod.rs +++ /dev/null @@ -1,338 +0,0 @@ -use crate::build_queue::PRIORITY_CONSISTENCY_CHECK; -use crate::{Context, db::delete}; -use anyhow::{Context as _, Result}; -use itertools::Itertools; -use tracing::{info, warn}; - -mod data; -mod db; -mod diff; -mod index; - -/// consistency check -/// -/// will compare our database with the local crates.io index and -/// apply any changes that we find in the index but not our database. -/// -/// Differences that we check for, and the activities: -/// * release in index, but not our DB => queue a build for this release. -/// * crate in index, but not in our DB => queue builds for all versions of that crate. -/// * release in DB, but not in the index => delete the release from our DB & storage. -/// * crate in our DB, but not in the index => delete the whole crate from our DB & storage. -/// * different yank-state between DB & Index => update the yank-state in our DB -/// -/// Even when activities fail, the command can just be re-run. While the diff calculation will -/// be repeated, we won't re-execute fixing activities. -pub async fn run_check(ctx: &Context, dry_run: bool) -> Result<()> { - info!("Loading data from database..."); - let mut conn = ctx.pool.get_async().await?; - let db_data = db::load(&mut conn, &ctx.config) - .await - .context("Loading crate data from database for consistency check")?; - - tracing::info!("Loading data from index..."); - let index_data = index::load(&ctx.config) - .await - .context("Loading crate data from index for consistency check")?; - - let diff = diff::calculate_diff(db_data.iter(), index_data.iter()); - let result = handle_diff(ctx, diff.iter(), dry_run).await?; - - println!("============"); - println!("SUMMARY"); - println!("============"); - println!("difference found:"); - for (key, count) in diff.iter().counts_by(|el| match el { - diff::Difference::CrateNotInIndex(_) => "CrateNotInIndex", - diff::Difference::CrateNotInDb(_, _) => "CrateNotInDb", - diff::Difference::ReleaseNotInIndex(_, _) => "ReleaseNotInIndex", - diff::Difference::ReleaseNotInDb(_, _) => "ReleaseNotInDb", - diff::Difference::ReleaseYank(_, _, _) => "ReleaseYank", - }) { - println!("{key:17} => {count:4}"); - } - - println!("============"); - if dry_run { - println!("activities that would have been triggered:"); - } else { - println!("activities triggered:"); - } - println!("builds queued: {:4}", result.builds_queued); - println!("crates deleted: {:4}", result.crates_deleted); - println!("releases deleted: {:4}", result.releases_deleted); - println!("yanks corrected: {:4}", result.yanks_corrected); - - Ok(()) -} - -#[derive(Default)] -struct HandleResult { - builds_queued: u32, - crates_deleted: u32, - releases_deleted: u32, - yanks_corrected: u32, -} - -async fn handle_diff<'a, I>(ctx: &Context, iter: I, dry_run: bool) -> Result -where - I: Iterator, -{ - let mut result = HandleResult::default(); - - let mut conn = ctx.pool.get_async().await?; - - for difference in iter { - println!("{difference}"); - - match difference { - diff::Difference::CrateNotInIndex(name) => { - if !dry_run - && let Err(err) = - delete::delete_crate(&mut conn, &ctx.async_storage, &ctx.config, name).await - { - warn!("{:?}", err); - } - result.crates_deleted += 1; - } - diff::Difference::CrateNotInDb(name, versions) => { - for version in versions { - if !dry_run - && let Err(err) = ctx - .async_build_queue - .add_crate(name, version, PRIORITY_CONSISTENCY_CHECK, None) - .await - { - warn!("{:?}", err); - } - result.builds_queued += 1; - } - } - diff::Difference::ReleaseNotInIndex(name, version) => { - if !dry_run - && let Err(err) = delete::delete_version( - &mut conn, - &ctx.async_storage, - &ctx.config, - name, - version, - ) - .await - { - warn!("{:?}", err); - } - result.releases_deleted += 1; - } - diff::Difference::ReleaseNotInDb(name, version) => { - if !dry_run - && let Err(err) = ctx - .async_build_queue - .add_crate(name, version, PRIORITY_CONSISTENCY_CHECK, None) - .await - { - warn!("{:?}", err); - } - result.builds_queued += 1; - } - diff::Difference::ReleaseYank(name, version, yanked) => { - if !dry_run - && let Err(err) = ctx - .async_build_queue - .set_yanked(name, version, *yanked) - .await - { - warn!("{:?}", err); - } - result.yanks_corrected += 1; - } - } - } - - Ok(result) -} - -#[cfg(test)] -mod tests { - use super::diff::Difference; - use super::*; - use crate::{ - db::types::version::Version, - test::{TestEnvironment, V1, V2, async_wrapper}, - }; - use sqlx::Row as _; - - async fn count(env: &TestEnvironment, sql: &str) -> Result { - let mut conn = env.async_db().async_conn().await; - Ok(sqlx::query_scalar(sql).fetch_one(&mut *conn).await?) - } - - async fn single_row(env: &TestEnvironment, sql: &str) -> Result> - where - O: Send + Unpin + for<'r> sqlx::Decode<'r, sqlx::Postgres> + sqlx::Type, - { - let mut conn = env.async_db().async_conn().await; - Ok::<_, anyhow::Error>( - sqlx::query(sql) - .fetch_all(&mut *conn) - .await? - .into_iter() - .map(|row| row.get(0)) - .collect(), - ) - } - - #[test] - fn test_delete_crate() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("krate") - .version(V1) - .version(V2) - .create() - .await?; - - let diff = [Difference::CrateNotInIndex("krate".into())]; - - // calling with dry-run leads to no change - handle_diff(&env.context, diff.iter(), true).await?; - - assert_eq!( - count(&env, "SELECT count(*) FROM crates WHERE name = 'krate'").await?, - 1 - ); - - // without dry-run the crate will be deleted - handle_diff(&env.context, diff.iter(), false).await?; - - assert_eq!( - count(&env, "SELECT count(*) FROM crates WHERE name = 'krate'").await?, - 0 - ); - - Ok(()) - }) - } - - #[test] - fn test_delete_release() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("krate") - .version(V1) - .create() - .await?; - env.fake_release() - .await - .name("krate") - .version(V2) - .create() - .await?; - - let diff = [Difference::ReleaseNotInIndex("krate".into(), V1)]; - - assert_eq!(count(&env, "SELECT count(*) FROM releases").await?, 2); - - handle_diff(&env.context, diff.iter(), true).await?; - - assert_eq!(count(&env, "SELECT count(*) FROM releases").await?, 2); - - handle_diff(&env.context, diff.iter(), false).await?; - - assert_eq!( - single_row::( - &env, - r#"SELECT version as "version: Version" FROM releases"# - ) - .await?, - vec![V2] - ); - - Ok(()) - }) - } - - #[test] - fn test_wrong_yank() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("krate") - .version(V1) - .yanked(true) - .create() - .await?; - - let diff = [Difference::ReleaseYank("krate".into(), V1, false)]; - - handle_diff(&env.context, diff.iter(), true).await?; - - assert_eq!( - single_row::(&env, "SELECT yanked FROM releases").await?, - vec![true] - ); - - handle_diff(&env.context, diff.iter(), false).await?; - - assert_eq!( - single_row::(&env, "SELECT yanked FROM releases").await?, - vec![false] - ); - - Ok(()) - }) - } - - #[test] - fn test_missing_release_in_db() { - async_wrapper(|env| async move { - let diff = [Difference::ReleaseNotInDb("krate".into(), V1)]; - - handle_diff(&env.context, diff.iter(), true).await?; - - let build_queue = env.async_build_queue(); - - assert!(build_queue.queued_crates().await?.is_empty()); - - handle_diff(&env.context, diff.iter(), false).await?; - - assert_eq!( - build_queue - .queued_crates() - .await? - .into_iter() - .map(|c| (c.name, V1, c.priority)) - .collect::>(), - vec![("krate".into(), V1, 15)] - ); - Ok(()) - }) - } - - #[test] - fn test_missing_crate_in_db() { - async_wrapper(|env| async move { - let diff = [Difference::CrateNotInDb("krate".into(), vec![V1, V2])]; - - handle_diff(&env.context, diff.iter(), true).await?; - - let build_queue = env.async_build_queue(); - - assert!(build_queue.queued_crates().await?.is_empty()); - - handle_diff(&env.context, diff.iter(), false).await?; - - assert_eq!( - build_queue - .queued_crates() - .await? - .into_iter() - .map(|c| (c.name, c.version, c.priority)) - .collect::>(), - vec![("krate".into(), V1, 15), ("krate".into(), V2, 15)] - ); - Ok(()) - }) - } -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 93b239ce7..e0f44d507 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,7 +7,7 @@ pub(crate) use self::{ rustc_version::{get_correct_docsrs_style_file, parse_rustc_version}, }; pub use self::{ - daemon::{start_daemon, watch_registry}, + daemon::start_daemon, queue::{ get_crate_pattern_and_priority, get_crate_priority, list_crate_priorities, remove_crate_priority, set_crate_priority, @@ -16,7 +16,6 @@ pub use self::{ }; pub(crate) mod cargo_metadata; -pub mod consistency; mod copy; pub mod daemon; mod html; @@ -25,9 +24,9 @@ pub(crate) mod queue_builder; pub(crate) mod rustc_version; pub(crate) mod sized_buffer; -use anyhow::{Context as _, Result}; +use anyhow::Result; use serde::{Serialize, de::DeserializeOwned}; -use std::{fmt, future::Future, panic, thread, time::Duration}; +use std::{future::Future, panic, thread, time::Duration}; use tracing::{Span, error, warn}; pub(crate) fn report_error(err: &anyhow::Error) { @@ -127,46 +126,6 @@ where } } -/// Move the execution of a blocking function into a separate, new thread. -/// -/// Only for long-running / expensive operations that would block the async runtime or its -/// blocking workerpool. -/// -/// The rule should be: -/// * async stuff -> in the tokio runtime, other async functions -/// * blocking I/O -> `spawn_blocking` -/// * CPU-Bound things: -/// - `render_in_threadpool` (continious load like rendering) -/// - `run_blocking` (sporadic CPU bound load) -/// -/// The thread-name will help us better seeing where our CPU load is coming from on the -/// servers. -/// -/// Generally speaking, using tokio's `spawn_blocking` is also ok-ish, if the work is sporadic. -/// But then I wouldn't get thread-names. -pub(crate) async fn run_blocking(name: N, f: F) -> Result -where - N: Into + fmt::Display, - F: FnOnce() -> Result + Send + 'static, - R: Send + 'static, -{ - let name = name.into(); - let span = tracing::Span::current(); - let (send, recv) = tokio::sync::oneshot::channel(); - thread::Builder::new() - .name(format!("docsrs-{name}")) - .spawn(move || { - let _guard = span.enter(); - - // `.send` only fails when the receiver is dropped while we work, - // at which point we don't need the result anymore. - let _ = send.send(f()); - }) - .with_context(|| format!("couldn't spawn worker thread for {}", &name))?; - - recv.await.context("sender was dropped")? -} - pub(crate) fn retry(mut f: impl FnMut() -> Result, max_attempts: u32) -> Result { for attempt in 1.. { match f() { From 5483bca9171bb0e730abe55a39134a716bf94ff9 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 5 Dec 2025 18:24:45 +0100 Subject: [PATCH 03/46] alksdjfas --- Cargo.lock | 81 +- Cargo.toml | 42 +- build.rs | 49 - crates/docs_rs_build_queue/src/config.rs | 4 +- crates/docs_rs_build_queue/src/lib.rs | 14 +- crates/docs_rs_build_queue/src/metrics.rs | 4 +- crates/docs_rs_database/Cargo.toml | 12 +- crates/docs_rs_database/src/lib.rs | 3 +- crates/docs_rs_database/src/mimes.rs | 46 + crates/docs_rs_database/src/types/mod.rs | 15 + crates/docs_rs_headers/Cargo.toml | 8 + crates/docs_rs_headers/src/etag.rs | 42 + crates/docs_rs_headers/src/lib.rs | 2 + crates/docs_rs_opentelemetry/src/config.rs | 2 +- crates/docs_rs_opentelemetry/src/lib.rs | 2 +- crates/docs_rs_storage/Cargo.toml | 40 + .../docs_rs_storage/src}/archive_index.rs | 18 +- .../docs_rs_storage/src}/compression.rs | 8 +- crates/docs_rs_storage/src/config.rs | 98 + .../docs_rs_storage/src}/database.rs | 4 +- crates/docs_rs_storage/src/errors.rs | 3 + .../db => crates/docs_rs_storage/src}/file.rs | 45 +- .../docs_rs_storage/src/lib.rs | 1922 ++++++++--------- .../docs_rs_storage/src}/s3.rs | 6 +- crates/docs_rs_storage/src/utils/mod.rs | 1 + .../src}/utils/sized_buffer.rs | 12 +- crates/docs_rs_utils/Cargo.toml | 15 + crates/docs_rs_utils/build.rs | 51 + crates/docs_rs_utils/src/lib.rs | 102 + crates/docs_rs_watcher/Cargo.toml | 12 + crates/docs_rs_watcher/src/config.rs | 20 +- crates/docs_rs_watcher/src/lib.rs | 1 + .../src/repositories/github.rs | 18 +- .../src/repositories/gitlab.rs | 11 +- .../docs_rs_watcher/src/repositories/mod.rs | 4 - .../src/repositories/updater.rs | 14 +- crates/docs_rs_watcher/src/utils.rs | 2 +- src/config.rs | 105 +- src/context.rs | 6 +- src/db/mimes.rs | 20 - src/db/types/mod.rs | 13 - src/error.rs | 4 - src/lib.rs | 10 - src/utils/mod.rs | 257 +-- src/web/headers/mod.rs | 40 - 45 files changed, 1644 insertions(+), 1544 deletions(-) create mode 100644 crates/docs_rs_database/src/mimes.rs create mode 100644 crates/docs_rs_headers/Cargo.toml create mode 100644 crates/docs_rs_headers/src/etag.rs create mode 100644 crates/docs_rs_headers/src/lib.rs create mode 100644 crates/docs_rs_storage/Cargo.toml rename {src/storage => crates/docs_rs_storage/src}/archive_index.rs (93%) rename {src/storage => crates/docs_rs_storage/src}/compression.rs (96%) create mode 100644 crates/docs_rs_storage/src/config.rs rename {src/storage => crates/docs_rs_storage/src}/database.rs (98%) create mode 100644 crates/docs_rs_storage/src/errors.rs rename {src/db => crates/docs_rs_storage/src}/file.rs (69%) rename src/storage/mod.rs => crates/docs_rs_storage/src/lib.rs (52%) rename {src/storage => crates/docs_rs_storage/src}/s3.rs (98%) create mode 100644 crates/docs_rs_storage/src/utils/mod.rs rename {src => crates/docs_rs_storage/src}/utils/sized_buffer.rs (90%) create mode 100644 crates/docs_rs_utils/Cargo.toml create mode 100644 crates/docs_rs_utils/build.rs create mode 100644 crates/docs_rs_utils/src/lib.rs delete mode 100644 src/db/mimes.rs diff --git a/Cargo.lock b/Cargo.lock index 76deb393b..c449fb4a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1944,25 +1944,18 @@ version = "0.6.0" dependencies = [ "anyhow", "askama", - "async-compression", "async-stream", - "async-trait", - "aws-config", - "aws-sdk-s3", "aws-smithy-runtime", "aws-smithy-types", - "aws-smithy-types-convert", "axum", "axum-extra", "base64 0.22.1", "bincode 2.0.1", - "bzip2", "chrono", "clap", "comrak", "constant_time_eq", "criterion", - "dashmap", "derive_builder", "derive_more 2.0.1", "docs_rs_build_queue", @@ -1970,7 +1963,6 @@ dependencies = [ "docs_rs_env_vars", "docs_rs_opentelemetry", "docsrs-metadata", - "flate2", "fn-error-context", "font-awesome-as-a-crate", "futures-util", @@ -1987,7 +1979,6 @@ dependencies = [ "lol_html", "md5", "mime", - "mime_guess", "mockito", "num_cpus", "opentelemetry", @@ -2017,7 +2008,6 @@ dependencies = [ "tempfile", "test-case", "thiserror 2.0.17", - "time", "tokio", "tokio-util", "toml 0.9.8", @@ -2029,8 +2019,6 @@ dependencies = [ "tracing-subscriber", "url", "walkdir", - "zip", - "zstd", ] [[package]] @@ -2059,6 +2047,8 @@ dependencies = [ "docs_rs_env_vars", "docs_rs_opentelemetry", "futures-util", + "mime", + "mime_guess", "opentelemetry", "semver", "serde", @@ -2079,6 +2069,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "docs_rs_headers" +version = "0.1.0" +dependencies = [ + "headers", + "md5", +] + [[package]] name = "docs_rs_opentelemetry" version = "0.1.0" @@ -2093,19 +2091,78 @@ dependencies = [ "url", ] +[[package]] +name = "docs_rs_storage" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-compression", + "async-stream", + "aws-config", + "aws-sdk-s3", + "aws-smithy-types-convert", + "bzip2", + "chrono", + "dashmap", + "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_headers", + "docs_rs_opentelemetry", + "docs_rs_utils", + "flate2", + "futures-util", + "http 1.4.0", + "itertools 0.14.0", + "mime", + "opentelemetry", + "serde", + "serde_json", + "sqlx", + "strum", + "tempfile", + "test-case", + "thiserror 2.0.17", + "tokio", + "tracing", + "walkdir", + "zip", + "zstd", +] + +[[package]] +name = "docs_rs_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "time", + "tokio", + "tracing", +] + [[package]] name = "docs_rs_watcher" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "chrono", "crates-index", "crates-index-diff", "docs_rs_build_queue", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_utils", + "futures-util", "itertools 0.14.0", + "mockito", "rayon", + "regex", + "reqwest", + "serde", + "serde_json", "sqlx", + "thiserror 2.0.17", "tokio", "tracing", "url", diff --git a/Cargo.toml b/Cargo.toml index 023ab84a1..b86ebdc78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,29 +31,33 @@ sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "sqlite", "c strum = { version = "0.27.0", features = ["derive"] } thiserror = "2.0.3" tokio = { version = "1.0", features = ["rt-multi-thread", "signal", "macros", "process", "sync"] } +walkdir = "2" +regex = "1" tracing = "0.1.37" url = { version = "2.1.1", features = ["serde"] } rayon = "1.6.1" +mime = "0.3.16" +async-stream = "0.3.5" +tempfile = "3.1.0" +http = "1.0.0" +reqwest = { version = "0.12", features = ["json", "gzip"] } + +# dev dependencies +test-case = "3.0.0" +mockito = "1.0.2" [dependencies] anyhow = { workspace = true } askama = "0.14.0" -async-compression = { version = "0.4.32", features = ["tokio", "bzip2", "zstd", "gzip"] } -async-stream = "0.3.5" -async-trait = "0.1.83" -aws-config = { version = "1.0.0", default-features = false, features = ["rt-tokio", "default-https-client"] } -aws-sdk-s3 = "1.3.0" -aws-smithy-types-convert = { version = "0.60.0", features = ["convert-chrono"] } +async-stream = { workspace = true } axum = { version = "0.8.1", features = ["macros"] } axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middleware"] } base64 = "0.22" bincode = { workspace = true } -bzip2 = "0.6.0" chrono = { workspace = true } clap = { version = "4.0.22", features = [ "derive" ] } comrak = { version = "0.48.0", default-features = false } constant_time_eq = "0.4.2" -dashmap = "6.0.0" derive_builder = "0.20.2" derive_more = { workspace = true } docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } @@ -61,20 +65,17 @@ docs_rs_database = { path = "crates/docs_rs_database" } docs_rs_build_queue = { path = "crates/docs_rs_build_queue" } docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } docsrs-metadata = { path = "crates/metadata" } -flate2 = "1.1.1" fn-error-context = "0.2.0" font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } futures-util = { workspace = true } getrandom = "0.3.1" hex = "0.4.3" hostname = "0.4.0" -http = "1.0.0" +http = { workspace = true } itertools = { workspace = true } log = "0.4" lol_html = "2.0.0" -md5 = "0.8.0" -mime = "0.3.16" -mime_guess = "2" +mime = { workspace = true } num_cpus = "1.15.0" opentelemetry = { workspace = true } opentelemetry-otlp = { workspace = true } @@ -84,8 +85,8 @@ path-slash = "0.2.0" percent-encoding = "2.2.0" phf = "0.13.1" rayon = { workspace = true } -regex = "1" -reqwest = { version = "0.12", features = ["json", "gzip"] } +regex = { workspace = true } +reqwest = { workspace = true } rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } semver = { workspace = true } sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } @@ -97,7 +98,7 @@ sqlx = { workspace = true } strum = { workspace = true } syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } -tempfile = "3.1.0" +tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } @@ -109,9 +110,7 @@ tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } tracing-log = "0.2.0" tracing-subscriber = { version = "0.3.20", default-features = false, features = ["ansi", "fmt", "json", "env-filter", "tracing-log"] } url = { workspace = true } -walkdir = "2" -zip = {version = "6.0.0", default-features = false, features = ["bzip2"]} -zstd = "0.13.0" +walkdir = { workspace = true } [dev-dependencies] aws-smithy-runtime = {version = "1.0.1", features = ["client", "test-util"]} @@ -120,11 +119,11 @@ criterion = "0.8.0" http-body-util = "0.1.0" indoc = "2.0.0" kuchikiki = "0.8" -mockito = "1.0.2" +mockito = { workspace = true } opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio", "testing"] } pretty_assertions = "1.4.0" rand = "0.9" -test-case = "3.0.0" +test-case = { workspace = true } tower = { version = "0.5.1", features = ["util"] } [build-dependencies] @@ -133,7 +132,6 @@ grass = { version = "0.13.1", default-features = false } md5 = "0.8.0" phf_codegen = "0.13" syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } -time = "0.3" walkdir = "2" [package.metadata.cargo-machete] diff --git a/build.rs b/build.rs index beabb7ea4..2aecb1f6c 100644 --- a/build.rs +++ b/build.rs @@ -73,7 +73,6 @@ type ETagMap<'a> = phf_codegen::Map<'a, String>; fn main() -> Result<()> { let out_dir = env::var("OUT_DIR").context("missing OUT_DIR")?; let out_dir = Path::new(&out_dir); - read_git_version()?; let mut etag_map: ETagMap = ETagMap::new(); @@ -94,54 +93,6 @@ fn main() -> Result<()> { Ok(()) } -fn read_git_version() -> Result<()> { - if let Ok(v) = env::var("GIT_SHA") { - // first try to read an externally provided git SAH, e.g., from CI - println!("cargo:rustc-env=GIT_SHA={v}"); - } else { - // then try to read the git repo. - let maybe_hash = get_git_hash()?; - let git_hash = maybe_hash.as_deref().unwrap_or("???????"); - println!("cargo:rustc-env=GIT_SHA={git_hash}"); - } - - println!( - "cargo:rustc-env=BUILD_DATE={}", - time::OffsetDateTime::now_utc().date(), - ); - - Ok(()) -} - -fn get_git_hash() -> Result> { - use std::process::Command; - - let output = Command::new("git") - .args(["rev-parse", "--short", "HEAD"]) - .output(); - - match output { - Ok(output) if output.status.success() => { - let hash = String::from_utf8(output.stdout)?.trim().to_string(); - - // TODO: are these right? - tracked::track(".git/HEAD")?; - tracked::track(".git/index")?; - - Ok(Some(hash)) - } - Ok(output) => { - let err = String::from_utf8_lossy(&output.stderr); - eprintln!("failed to get git repo: {}", err.trim()); - Ok(None) - } - Err(err) => { - eprintln!("failed to execute git: {err}"); - Ok(None) - } - } -} - fn etag_from_path(path: impl AsRef) -> Result { Ok(etag_from_content(std::fs::read(&path)?)) } diff --git a/crates/docs_rs_build_queue/src/config.rs b/crates/docs_rs_build_queue/src/config.rs index 074fd118b..3842ba3bb 100644 --- a/crates/docs_rs_build_queue/src/config.rs +++ b/crates/docs_rs_build_queue/src/config.rs @@ -2,9 +2,9 @@ use docs_rs_env_vars::{env, maybe_env}; #[derive(Debug)] pub struct Config { - pub(crate) build_attempts: u16, + pub build_attempts: u16, // automatic rebuild configuration - pub(crate) max_queued_rebuilds: Option, + pub max_queued_rebuilds: Option, } impl Config { diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 31365a30c..5d3ee2954 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -30,19 +30,19 @@ pub const PRIORITY_CONSISTENCY_CHECK: i32 = 15; pub const PRIORITY_CONTINUOUS: i32 = 20; #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize)] -pub(crate) struct QueuedCrate { +pub struct QueuedCrate { #[serde(skip)] id: i32, - pub(crate) name: String, - pub(crate) version: Version, - pub(crate) priority: i32, - pub(crate) registry: Option, - pub(crate) attempt: i32, + pub name: String, + pub version: Version, + pub priority: i32, + pub registry: Option, + pub attempt: i32, } #[derive(Debug)] pub struct AsyncBuildQueue { - pub(crate) db: Pool, + pub db: Pool, queue_metrics: metrics::BuildQueueMetrics, // builder_metrics: Arc, // cdn_metrics: Arc, diff --git a/crates/docs_rs_build_queue/src/metrics.rs b/crates/docs_rs_build_queue/src/metrics.rs index ddf29985c..bcdb451e2 100644 --- a/crates/docs_rs_build_queue/src/metrics.rs +++ b/crates/docs_rs_build_queue/src/metrics.rs @@ -2,12 +2,12 @@ use docs_rs_opentelemetry::AnyMeterProvider; use opentelemetry::metrics::Counter; #[derive(Debug)] -pub(crate) struct BuildQueueMetrics { +pub struct BuildQueueMetrics { queued_builds: Counter, } impl BuildQueueMetrics { - pub(crate) fn new(meter_provider: &AnyMeterProvider) -> Self { + pub fn new(meter_provider: &AnyMeterProvider) -> Self { let meter = meter_provider.meter("build_queue"); const PREFIX: &str = "docsrs.build_queue"; Self { diff --git a/crates/docs_rs_database/Cargo.toml b/crates/docs_rs_database/Cargo.toml index 0170311af..811a35b0d 100644 --- a/crates/docs_rs_database/Cargo.toml +++ b/crates/docs_rs_database/Cargo.toml @@ -5,21 +5,23 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +bincode = { workspace = true } derive_more = { workspace = true } -serde_with = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } futures-util = { workspace = true } +mime = { workspace = true } opentelemetry = { workspace = true } semver = { workspace = true } -sqlx = { workspace = true } -bincode = { workspace = true } -thiserror = { workspace = true } -strum = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +serde_with = { workspace = true } +sqlx = { workspace = true } +strum = { workspace = true } +thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +mime_guess = "2" [features] testing = [] diff --git a/crates/docs_rs_database/src/lib.rs b/crates/docs_rs_database/src/lib.rs index 72adaa5ae..fbd0473b2 100644 --- a/crates/docs_rs_database/src/lib.rs +++ b/crates/docs_rs_database/src/lib.rs @@ -1,4 +1,5 @@ mod config; +pub mod mimes; pub mod service_config; pub mod types; @@ -87,7 +88,7 @@ impl Pool { } #[cfg(feature = "testing")] - pub(crate) async fn new_with_schema( + pub async fn new_with_schema( config: &config::Config, schema: &str, otel_meter_provider: &AnyMeterProvider, diff --git a/crates/docs_rs_database/src/mimes.rs b/crates/docs_rs_database/src/mimes.rs new file mode 100644 index 000000000..74cf54795 --- /dev/null +++ b/crates/docs_rs_database/src/mimes.rs @@ -0,0 +1,46 @@ +use mime::Mime; +use std::{ffi::OsStr, path::Path, sync::LazyLock}; + +macro_rules! mime { + ($id:ident, $mime:expr) => { + pub static $id: LazyLock = LazyLock::new(|| $mime.parse().unwrap()); + }; +} + +mime!(APPLICATION_ZIP, "application/zip"); +mime!(APPLICATION_ZSTD, "application/zstd"); +mime!(APPLICATION_GZIP, "application/gzip"); +mime!( + APPLICATION_OPENSEARCH_XML, + "application/opensearchdescription+xml" +); +mime!(APPLICATION_XML, "application/xml"); +mime!(TEXT_MARKDOWN, "text/markdown"); +mime!(TEXT_RUST, "text/rust"); +mime!(TEXT_TOML, "text/toml"); + +pub fn detect_mime(file_path: impl AsRef) -> Mime { + let mime = mime_guess::from_path(file_path.as_ref()) + .first() + .unwrap_or(mime::TEXT_PLAIN); + + match mime.as_ref() { + "text/plain" | "text/troff" | "text/x-markdown" | "text/x-rust" | "text/x-toml" => { + match file_path.as_ref().extension().and_then(OsStr::to_str) { + Some("md") => TEXT_MARKDOWN.clone(), + Some("rs") => TEXT_RUST.clone(), + Some("markdown") => TEXT_MARKDOWN.clone(), + Some("css") => mime::TEXT_CSS, + Some("toml") => TEXT_TOML.clone(), + Some("js") => mime::TEXT_JAVASCRIPT, + Some("json") => mime::APPLICATION_JSON, + Some("gz") => APPLICATION_GZIP.clone(), + Some("zst") => APPLICATION_ZSTD.clone(), + _ => mime, + } + } + "image/svg" => mime::IMAGE_SVG, + + _ => mime, + } +} diff --git a/crates/docs_rs_database/src/types/mod.rs b/crates/docs_rs_database/src/types/mod.rs index a6db76ad4..0762b5109 100644 --- a/crates/docs_rs_database/src/types/mod.rs +++ b/crates/docs_rs_database/src/types/mod.rs @@ -1 +1,16 @@ +use derive_more::{Display, FromStr}; +use serde::Serialize; + pub mod version; + +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] +#[sqlx(transparent)] +pub struct CrateId(pub i32); + +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, FromStr, Serialize, sqlx::Type)] +#[sqlx(transparent)] +pub struct ReleaseId(pub i32); + +#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] +#[sqlx(transparent)] +pub struct BuildId(pub i32); diff --git a/crates/docs_rs_headers/Cargo.toml b/crates/docs_rs_headers/Cargo.toml new file mode 100644 index 000000000..2ffaea105 --- /dev/null +++ b/crates/docs_rs_headers/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "docs_rs_headers" +version = "0.1.0" +edition = "2024" + +[dependencies] +md5 = "0.8.0" +headers = "0.4.1" # not sure if we want this in this crate diff --git a/crates/docs_rs_headers/src/etag.rs b/crates/docs_rs_headers/src/etag.rs new file mode 100644 index 000000000..cf38e150e --- /dev/null +++ b/crates/docs_rs_headers/src/etag.rs @@ -0,0 +1,42 @@ +use headers::ETag; +use std::io::{self, Write as _}; + +/// compute our etag header value from some content +/// +/// Has to match the implementation in our build-script. +pub fn compute_etag>(content: T) -> ETag { + let mut computer = ETagComputer::new(); + computer.write_all(content.as_ref()).unwrap(); + computer.finalize() +} + +/// Helper type to compute ETag values. +/// +/// Works the same way as the inner `md5::Context`, +/// but produces an `ETag` when finalized. +pub struct ETagComputer(md5::Context); + +impl ETagComputer { + pub fn new() -> Self { + Self(md5::Context::new()) + } + + pub fn consume>(&mut self, data: T) { + self.0.consume(data.as_ref()); + } + + pub fn finalize(self) -> ETag { + let digest = self.0.finalize(); + format!("\"{:x}\"", digest).parse().unwrap() + } +} + +impl io::Write for ETagComputer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.0.flush() + } +} diff --git a/crates/docs_rs_headers/src/lib.rs b/crates/docs_rs_headers/src/lib.rs new file mode 100644 index 000000000..97f979113 --- /dev/null +++ b/crates/docs_rs_headers/src/lib.rs @@ -0,0 +1,2 @@ +pub mod etag; +pub use headers::ETag; diff --git a/crates/docs_rs_opentelemetry/src/config.rs b/crates/docs_rs_opentelemetry/src/config.rs index 8a40168db..e40a38d86 100644 --- a/crates/docs_rs_opentelemetry/src/config.rs +++ b/crates/docs_rs_opentelemetry/src/config.rs @@ -3,7 +3,7 @@ use url::Url; pub struct Config { // opentelemetry endpoint to send OTLP to - pub(crate) endpoint: Option, + pub endpoint: Option, } impl Config { diff --git a/crates/docs_rs_opentelemetry/src/lib.rs b/crates/docs_rs_opentelemetry/src/lib.rs index 16b4e3911..d1e10a172 100644 --- a/crates/docs_rs_opentelemetry/src/lib.rs +++ b/crates/docs_rs_opentelemetry/src/lib.rs @@ -93,7 +93,7 @@ pub struct NoopMeter { impl NoopMeter { /// Create a new no-op meter core. - pub(crate) fn new() -> Self { + pub fn new() -> Self { NoopMeter { _private: () } } } diff --git a/crates/docs_rs_storage/Cargo.toml b/crates/docs_rs_storage/Cargo.toml new file mode 100644 index 000000000..f3b938513 --- /dev/null +++ b/crates/docs_rs_storage/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "docs_rs_storage" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +async-compression = { version = "0.4.32", features = ["tokio", "bzip2", "zstd", "gzip"] } +async-stream = { workspace = true } +aws-config = { version = "1.0.0", default-features = false, features = ["rt-tokio", "default-https-client"] } +aws-sdk-s3 = "1.3.0" +aws-smithy-types-convert = { version = "0.60.0", features = ["convert-chrono"] } +bzip2 = "0.6.0" +chrono = { workspace = true } +dashmap = "6.0.0" +docs_rs_database = { path = "../docs_rs_database" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_utils = { path = "../docs_rs_utils" } +docs_rs_headers = { path = "../docs_rs_headers" } +flate2 = "1.1.1" +futures-util = { workspace = true } +http = { workspace = true } +itertools = { workspace = true } +mime = { workspace = true } +opentelemetry = { workspace = true } +serde = { workspace = true } +sqlx = { workspace = true } +strum = { workspace = true } +tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +walkdir = { workspace = true } +zip = {version = "6.0.0", default-features = false, features = ["bzip2"]} +zstd = "0.13.0" +serde_json = { workspace = true } + +[dev-dependencies] +test-case = { workspace = true } diff --git a/src/storage/archive_index.rs b/crates/docs_rs_storage/src/archive_index.rs similarity index 93% rename from src/storage/archive_index.rs rename to crates/docs_rs_storage/src/archive_index.rs index 498a2ec7a..9871ec291 100644 --- a/src/storage/archive_index.rs +++ b/crates/docs_rs_storage/src/archive_index.rs @@ -1,24 +1,22 @@ -use crate::{ - error::Result, - storage::{FileRange, compression::CompressionAlgorithm}, -}; -use anyhow::{Context as _, bail}; +use anyhow::{Context as _, Result, bail}; use itertools::Itertools as _; use sqlx::{Acquire as _, QueryBuilder, Row as _, Sqlite}; use std::{fs, io, path::Path}; use tracing::instrument; +use crate::{CompressionAlgorithm, FileRange}; + #[derive(PartialEq, Eq, Debug)] -pub(crate) struct FileInfo { +pub struct FileInfo { range: FileRange, compression: CompressionAlgorithm, } impl FileInfo { - pub(crate) fn range(&self) -> FileRange { + pub fn range(&self) -> FileRange { self.range.clone() } - pub(crate) fn compression(&self) -> CompressionAlgorithm { + pub fn compression(&self) -> CompressionAlgorithm { self.compression } } @@ -63,7 +61,7 @@ async fn sqlite_open>(path: P) -> Result { /// /// Will delete the destination file if it already exists. #[instrument(skip(zipfile))] -pub(crate) async fn create + std::fmt::Debug>( +pub async fn create + std::fmt::Debug>( zipfile: &mut R, destination: P, ) -> Result<()> { @@ -165,7 +163,7 @@ where } #[instrument] -pub(crate) async fn find_in_file + std::fmt::Debug>( +pub async fn find_in_file + std::fmt::Debug>( archive_index_path: P, search_for: &str, ) -> Result> { diff --git a/src/storage/compression.rs b/crates/docs_rs_storage/src/compression.rs similarity index 96% rename from src/storage/compression.rs rename to crates/docs_rs_storage/src/compression.rs index 8070d7bcd..12f2f6b59 100644 --- a/src/storage/compression.rs +++ b/crates/docs_rs_storage/src/compression.rs @@ -53,7 +53,7 @@ impl std::convert::TryFrom for CompressionAlgorithm { } } -pub(crate) fn file_extension_for(algorithm: CompressionAlgorithm) -> &'static str { +pub fn file_extension_for(algorithm: CompressionAlgorithm) -> &'static str { match algorithm { CompressionAlgorithm::Zstd => "zst", CompressionAlgorithm::Bzip2 => "bz2", @@ -61,7 +61,7 @@ pub(crate) fn file_extension_for(algorithm: CompressionAlgorithm) -> &'static st } } -pub(crate) fn compression_from_file_extension(ext: &str) -> Option { +pub fn compression_from_file_extension(ext: &str) -> Option { match ext { "zst" => Some(CompressionAlgorithm::Zstd), "bz2" => Some(CompressionAlgorithm::Bzip2), @@ -169,6 +169,8 @@ pub fn decompress( #[cfg(test)] mod tests { + use crate::errors::SizeLimitReached; + use super::*; use strum::IntoEnumIterator; use test_case::test_case; @@ -219,7 +221,7 @@ mod tests { assert!( err.downcast_ref::() .and_then(|io| io.get_ref()) - .and_then(|err| err.downcast_ref::()) + .and_then(|err| err.downcast_ref::()) .is_some() ); } diff --git a/crates/docs_rs_storage/src/config.rs b/crates/docs_rs_storage/src/config.rs new file mode 100644 index 000000000..977ae5911 --- /dev/null +++ b/crates/docs_rs_storage/src/config.rs @@ -0,0 +1,98 @@ +use crate::StorageKind; +use docs_rs_env_vars::{env, maybe_env, require_env}; +use std::{ + io, + path::{self, Path, PathBuf}, +}; + +fn ensure_absolute_path(path: PathBuf) -> io::Result { + if path.is_absolute() { + Ok(path) + } else { + Ok(path::absolute(&path)?) + } +} + +#[derive(Debug)] +pub struct Config { + pub temp_dir: PathBuf, + + // Storage params + pub storage_backend: StorageKind, + + // AWS SDK configuration + pub aws_sdk_max_retries: u32, + + // S3 params + pub s3_bucket: String, + pub s3_region: String, + pub s3_endpoint: Option, + + // DO NOT CONFIGURE THIS THROUGH AN ENVIRONMENT VARIABLE! + // Accidentally turning this on outside of the test suite might cause data loss in the + // production environment. + #[cfg(test)] + pub s3_bucket_is_temporary: bool, + + // Max size of the files served by the docs.rs frontend + pub max_file_size: usize, + pub max_file_size_html: usize, + + // where do we want to store the locally cached index files + // for the remote archives? + pub local_archive_cache_path: PathBuf, + + // expected number of entries in the local archive cache. + // Makes server restarts faster by preallocating some data structures. + // General numbers (as of 2025-12): + // * we have ~1.5 mio releases with archive storage (and 400k without) + // * each release has on average 2 archive files (rustdoc, source) + // so, over all, 3 mio archive index files in S3. + // + // While due to crawlers we will download _all_ of them over time, the old + // metric "releases accessed in the last 10 minutes" was around 50k, if I + // recall correctly. + // We're using a local DashMap to store some locks for these indexes, + // and we already know in advance we need these 50k entries. + // So we can preallocate the DashMap with this number to avoid resizes. + pub local_archive_cache_expected_count: usize, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; + + Ok(Self { + temp_dir: prefix.join("tmp"), + storage_backend: env("DOCSRS_STORAGE_BACKEND", StorageKind::Database)?, + aws_sdk_max_retries: env("DOCSRS_AWS_SDK_MAX_RETRIES", 6u32)?, + s3_bucket: env("DOCSRS_S3_BUCKET", "rust-docs-rs".to_string())?, + s3_region: env("S3_REGION", "us-west-1".to_string())?, + s3_endpoint: maybe_env("S3_ENDPOINT")?, + local_archive_cache_path: ensure_absolute_path(env( + "DOCSRS_ARCHIVE_INDEX_CACHE_PATH", + prefix.join("archive_cache"), + )?)?, + local_archive_cache_expected_count: env( + "DOCSRS_ARCHIVE_INDEX_EXPECTED_COUNT", + 100_000usize, + )?, + max_file_size: env("DOCSRS_MAX_FILE_SIZE", 50 * 1024 * 1024)?, + max_file_size_html: env("DOCSRS_MAX_FILE_SIZE_HTML", 50 * 1024 * 1024)?, + #[cfg(test)] + s3_bucket_is_temporary: false, + }) + } + + pub fn max_file_size_for(&self, path: impl AsRef) -> usize { + static HTML: &str = "html"; + + if let Some(ext) = path.as_ref().extension() + && ext == HTML + { + self.max_file_size_html + } else { + self.max_file_size + } + } +} diff --git a/src/storage/database.rs b/crates/docs_rs_storage/src/database.rs similarity index 98% rename from src/storage/database.rs rename to crates/docs_rs_storage/src/database.rs index b6335b881..216249756 100644 --- a/src/storage/database.rs +++ b/crates/docs_rs_storage/src/database.rs @@ -1,6 +1,8 @@ use super::{BlobUpload, FileRange, StorageMetrics, StreamingBlob}; -use crate::{db::Pool, error::Result, web::headers::compute_etag}; +use anyhow::Result; use chrono::{DateTime, Utc}; +use docs_rs_database::Pool; +use docs_rs_headers::etag::compute_etag; use futures_util::stream::{Stream, TryStreamExt}; use sqlx::Acquire; use std::io; diff --git a/crates/docs_rs_storage/src/errors.rs b/crates/docs_rs_storage/src/errors.rs new file mode 100644 index 000000000..2dc94ac14 --- /dev/null +++ b/crates/docs_rs_storage/src/errors.rs @@ -0,0 +1,3 @@ +#[derive(Debug, Copy, Clone, thiserror::Error)] +#[error("the size limit for the buffer was reached")] +pub struct SizeLimitReached; diff --git a/src/db/file.rs b/crates/docs_rs_storage/src/file.rs similarity index 69% rename from src/db/file.rs rename to crates/docs_rs_storage/src/file.rs index 1648c01a9..eca7ccb73 100644 --- a/src/db/file.rs +++ b/crates/docs_rs_storage/src/file.rs @@ -7,57 +7,29 @@ //! It's recommended that you use the S3 bucket in production to avoid running out of disk space. //! However, postgres is still available for testing and backwards compatibility. -use crate::error::Result; -use crate::{ - db::mimes, - storage::{AsyncStorage, CompressionAlgorithm}, -}; +use anyhow::Result; +use docs_rs_database::mimes::detect_mime; use mime::Mime; use serde_json::Value; -use std::ffi::OsStr; use std::path::{Path, PathBuf}; use tracing::instrument; +use crate::{AsyncStorage, CompressionAlgorithm}; + /// represents a file path from our source or documentation builds. /// Used to return metadata about the file. #[derive(Debug)] pub struct FileEntry { - pub(crate) path: PathBuf, - pub(crate) size: u64, + pub path: PathBuf, + pub size: u64, } impl FileEntry { - pub(crate) fn mime(&self) -> Mime { + pub fn mime(&self) -> Mime { detect_mime(&self.path) } } -pub(crate) fn detect_mime(file_path: impl AsRef) -> Mime { - let mime = mime_guess::from_path(file_path.as_ref()) - .first() - .unwrap_or(mime::TEXT_PLAIN); - - match mime.as_ref() { - "text/plain" | "text/troff" | "text/x-markdown" | "text/x-rust" | "text/x-toml" => { - match file_path.as_ref().extension().and_then(OsStr::to_str) { - Some("md") => mimes::TEXT_MARKDOWN.clone(), - Some("rs") => mimes::TEXT_RUST.clone(), - Some("markdown") => mimes::TEXT_MARKDOWN.clone(), - Some("css") => mime::TEXT_CSS, - Some("toml") => mimes::TEXT_TOML.clone(), - Some("js") => mime::TEXT_JAVASCRIPT, - Some("json") => mime::APPLICATION_JSON, - Some("gz") => mimes::APPLICATION_GZIP.clone(), - Some("zst") => mimes::APPLICATION_ZSTD.clone(), - _ => mime, - } - } - "image/svg" => mime::IMAGE_SVG, - - _ => mime, - } -} - /// Store all files in a directory and return [[mimetype, filename]] as Json /// /// If there is an S3 Client configured, store files into an S3 bucket; @@ -87,7 +59,7 @@ pub async fn add_path_into_remote_archive + std::fmt::Debug>( Ok((file_list, algorithm)) } -pub(crate) fn file_list_to_json(files: impl IntoIterator) -> Value { +pub fn file_list_to_json(files: impl IntoIterator) -> Value { Value::Array( files .into_iter() @@ -105,6 +77,7 @@ pub(crate) fn file_list_to_json(files: impl IntoIterator) -> V #[cfg(test)] mod tests { use super::*; + use docs_rs_database::mimes; use test_case::test_case; // some standard mime types that mime-guess handles diff --git a/src/storage/mod.rs b/crates/docs_rs_storage/src/lib.rs similarity index 52% rename from src/storage/mod.rs rename to crates/docs_rs_storage/src/lib.rs index 264e07b1b..731861326 100644 --- a/src/storage/mod.rs +++ b/crates/docs_rs_storage/src/lib.rs @@ -1,7 +1,13 @@ mod archive_index; -pub(crate) mod compression; +pub mod compression; +mod config; mod database; +mod errors; +mod file; mod s3; +mod utils; + +use crate::{config::Config, file::FileEntry}; pub use self::compression::{CompressionAlgorithm, CompressionAlgorithms, compress, decompress}; use self::{ @@ -9,27 +15,20 @@ use self::{ database::DatabaseBackend, s3::S3Backend, }; -use crate::{ - Config, - db::{ - BuildId, Pool, - file::{FileEntry, detect_mime}, - mimes, - types::version::Version, - }, - error::Result, - metrics::otel::AnyMeterProvider, - utils::spawn_blocking, -}; -use anyhow::anyhow; -use axum_extra::headers; +use anyhow::{Result, anyhow}; use chrono::{DateTime, Utc}; use dashmap::DashMap; -use fn_error_context::context; +use docs_rs_database::{ + Pool, + mimes::{self, detect_mime}, + types::{BuildId, version::Version}, +}; +use docs_rs_headers::ETag; +use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_utils::spawn_blocking; use futures_util::stream::BoxStream; use mime::Mime; use opentelemetry::metrics::Counter; -use path_slash::PathExt; use std::{ fmt, fs::{self, File}, @@ -57,15 +56,15 @@ type FileRange = RangeInclusive; #[derive(Debug, thiserror::Error)] #[error("path not found")] -pub(crate) struct PathNotFoundError; +pub struct PathNotFoundError; /// represents a blob to be uploaded to storage. #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct BlobUpload { - pub(crate) path: String, - pub(crate) mime: Mime, - pub(crate) content: Vec, - pub(crate) compression: Option, +pub struct BlobUpload { + pub path: String, + pub mime: Mime, + pub content: Vec, + pub compression: Option, } impl From for BlobUpload { @@ -80,23 +79,23 @@ impl From for BlobUpload { } #[derive(Clone, Debug, PartialEq, Eq)] -pub(crate) struct Blob { - pub(crate) path: String, - pub(crate) mime: Mime, - pub(crate) date_updated: DateTime, - pub(crate) etag: Option, - pub(crate) content: Vec, - pub(crate) compression: Option, +pub struct Blob { + pub path: String, + pub mime: Mime, + pub date_updated: DateTime, + pub etag: Option, + pub content: Vec, + pub compression: Option, } -pub(crate) struct StreamingBlob { - pub(crate) path: String, - pub(crate) mime: Mime, - pub(crate) date_updated: DateTime, - pub(crate) etag: Option, - pub(crate) compression: Option, - pub(crate) content_length: usize, - pub(crate) content: Box, +pub struct StreamingBlob { + pub path: String, + pub mime: Mime, + pub date_updated: DateTime, + pub etag: Option, + pub compression: Option, + pub content_length: usize, + pub content: Box, } impl std::fmt::Debug for StreamingBlob { @@ -114,7 +113,7 @@ impl std::fmt::Debug for StreamingBlob { impl StreamingBlob { /// wrap the content stream in a streaming decompressor according to the /// algorithm found in `compression` attribute. - pub(crate) async fn decompress(mut self) -> Result { + pub async fn decompress(mut self) -> Result { let Some(alg) = self.compression else { return Ok(self); }; @@ -145,8 +144,8 @@ impl StreamingBlob { } /// consume the inner stream and materialize the full blob into memory. - pub(crate) async fn materialize(mut self, max_size: usize) -> Result { - let mut content = crate::utils::sized_buffer::SizedBuffer::new(max_size); + pub async fn materialize(mut self, max_size: usize) -> Result { + let mut content = utils::sized_buffer::SizedBuffer::new(max_size); content.reserve(self.content_length); tokio::io::copy(&mut self.content, &mut content).await?; @@ -287,7 +286,7 @@ impl AsyncStorage { } #[instrument] - pub(crate) async fn exists(&self, path: &str) -> Result { + pub async fn exists(&self, path: &str) -> Result { match &self.backend { StorageBackend::Database(db) => db.exists(path).await, StorageBackend::S3(s3) => s3.exists(path).await, @@ -304,7 +303,7 @@ impl AsyncStorage { /// * `archive_storage` - if `true`, we will assume we have a remove ZIP archive and an index /// where we can fetch the requested path from inside the ZIP file. #[instrument] - pub(crate) async fn stream_rustdoc_file( + pub async fn stream_rustdoc_file( &self, name: &str, version: &Version, @@ -323,8 +322,7 @@ impl AsyncStorage { }) } - #[context("fetching {path} from {name} {version} (archive: {archive_storage})")] - pub(crate) async fn fetch_source_file( + pub async fn fetch_source_file( &self, name: &str, version: &Version, @@ -339,7 +337,7 @@ impl AsyncStorage { } #[instrument] - pub(crate) async fn stream_source_file( + pub async fn stream_source_file( &self, name: &str, version: &Version, @@ -358,7 +356,7 @@ impl AsyncStorage { } #[instrument] - pub(crate) async fn rustdoc_file_exists( + pub async fn rustdoc_file_exists( &self, name: &str, version: &Version, @@ -377,7 +375,7 @@ impl AsyncStorage { } #[instrument] - pub(crate) async fn exists_in_archive( + pub async fn exists_in_archive( &self, archive_path: &str, latest_build_id: Option, @@ -400,7 +398,7 @@ impl AsyncStorage { /// get, decompress and materialize an object from store #[instrument] - pub(crate) async fn get(&self, path: &str, max_size: usize) -> Result { + pub async fn get(&self, path: &str, max_size: usize) -> Result { self.get_stream(path).await?.materialize(max_size).await } @@ -409,7 +407,7 @@ impl AsyncStorage { /// We don't decompress ourselves, S3 only decompresses with a correct /// `Content-Encoding` header set, which we don't. #[instrument] - pub(crate) async fn get_raw_stream(&self, path: &str) -> Result { + pub async fn get_raw_stream(&self, path: &str) -> Result { match &self.backend { StorageBackend::Database(db) => db.get_stream(path, None).await, StorageBackend::S3(s3) => s3.get_stream(path, None).await, @@ -418,13 +416,13 @@ impl AsyncStorage { /// get a decompressing stream to an object in storage. #[instrument] - pub(crate) async fn get_stream(&self, path: &str) -> Result { + pub async fn get_stream(&self, path: &str) -> Result { Ok(self.get_raw_stream(path).await?.decompress().await?) } /// get, decompress and materialize part of an object from store #[instrument] - pub(super) async fn get_range( + pub async fn get_range( &self, path: &str, max_size: usize, @@ -439,7 +437,7 @@ impl AsyncStorage { /// get a decompressing stream to a range inside an object in storage #[instrument] - pub(super) async fn get_range_stream( + pub async fn get_range_stream( &self, path: &str, range: FileRange, @@ -608,7 +606,7 @@ impl AsyncStorage { } #[instrument] - pub(crate) async fn get_from_archive( + pub async fn get_from_archive( &self, archive_path: &str, latest_build_id: Option, @@ -622,7 +620,7 @@ impl AsyncStorage { } #[instrument(skip(self))] - pub(crate) async fn stream_from_archive( + pub async fn stream_from_archive( &self, archive_path: &str, latest_build_id: Option, @@ -683,7 +681,7 @@ impl AsyncStorage { } #[instrument(skip(self))] - pub(crate) async fn store_all_in_archive( + pub async fn store_all_in_archive( &self, archive_path: &str, root_dir: &Path, @@ -778,7 +776,7 @@ impl AsyncStorage { /// Store all files in `root_dir` into the backend under `prefix`. #[instrument(skip(self))] - pub(crate) async fn store_all( + pub async fn store_all( &self, prefix: &Path, root_dir: &Path, @@ -804,7 +802,8 @@ impl AsyncStorage { let file_size = file.metadata()?.len(); let content = compress(file, alg)?; - let bucket_path = prefix.join(&file_path).to_slash().unwrap().to_string(); + // FIXME: we has `PathExt::to_slash` in here + let bucket_path = prefix.join(&file_path).to_string_lossy().to_string(); let file_info = FileEntry { path: file_path, @@ -830,14 +829,14 @@ impl AsyncStorage { } #[cfg(test)] - pub(crate) async fn store_blobs(&self, blobs: Vec) -> Result<()> { + pub async fn store_blobs(&self, blobs: Vec) -> Result<()> { self.store_inner(blobs).await } // Store file into the backend at the given path, uncompressed. // The path will also be used to determine the mime type. #[instrument(skip(self, content))] - pub(crate) async fn store_one_uncompressed( + pub async fn store_one_uncompressed( &self, path: impl Into + std::fmt::Debug, content: impl Into>, @@ -860,7 +859,7 @@ impl AsyncStorage { // Store file into the backend at the given path (also used to detect mime type), returns the // chosen compression algorithm #[instrument(skip(self, content))] - pub(crate) async fn store_one( + pub async fn store_one( &self, path: impl Into + std::fmt::Debug, content: impl Into>, @@ -883,7 +882,7 @@ impl AsyncStorage { } #[instrument(skip(self))] - pub(crate) async fn store_path( + pub async fn store_path( &self, target_path: impl Into + std::fmt::Debug, source_path: impl AsRef + std::fmt::Debug, @@ -914,17 +913,14 @@ impl AsyncStorage { } } - pub(super) async fn list_prefix<'a>( - &'a self, - prefix: &'a str, - ) -> BoxStream<'a, Result> { + pub async fn list_prefix<'a>(&'a self, prefix: &'a str) -> BoxStream<'a, Result> { match &self.backend { StorageBackend::Database(db) => Box::pin(db.list_prefix(prefix).await), StorageBackend::S3(s3) => Box::pin(s3.list_prefix(prefix).await), } } - pub(crate) async fn delete_prefix(&self, prefix: &str) -> Result<()> { + pub async fn delete_prefix(&self, prefix: &str) -> Result<()> { match &self.backend { StorageBackend::Database(db) => db.delete_prefix(prefix).await, StorageBackend::S3(s3) => s3.delete_prefix(prefix).await, @@ -935,7 +931,7 @@ impl AsyncStorage { // we leak the web server, and Drop isn't executed in that case (since the leaked web server // still holds a reference to the storage). #[cfg(test)] - pub(crate) async fn cleanup_after_test(&self) -> Result<()> { + pub async fn cleanup_after_test(&self) -> Result<()> { if let StorageBackend::S3(s3) = &self.backend { s3.cleanup_after_test().await?; } @@ -964,11 +960,11 @@ impl Storage { Self { inner, runtime } } - pub(crate) fn exists(&self, path: &str) -> Result { + pub fn exists(&self, path: &str) -> Result { self.runtime.block_on(self.inner.exists(path)) } - pub(crate) fn fetch_source_file( + pub fn fetch_source_file( &self, name: &str, version: &Version, @@ -985,7 +981,7 @@ impl Storage { )) } - pub(crate) fn rustdoc_file_exists( + pub fn rustdoc_file_exists( &self, name: &str, version: &Version, @@ -1002,7 +998,7 @@ impl Storage { )) } - pub(crate) fn exists_in_archive( + pub fn exists_in_archive( &self, archive_path: &str, latest_build_id: Option, @@ -1014,11 +1010,11 @@ impl Storage { ) } - pub(crate) fn get(&self, path: &str, max_size: usize) -> Result { + pub fn get(&self, path: &str, max_size: usize) -> Result { self.runtime.block_on(self.inner.get(path, max_size)) } - pub(super) fn get_range( + pub fn get_range( &self, path: &str, max_size: usize, @@ -1029,7 +1025,7 @@ impl Storage { .block_on(self.inner.get_range(path, max_size, range, compression)) } - pub(crate) fn get_from_archive( + pub fn get_from_archive( &self, archive_path: &str, latest_build_id: Option, @@ -1044,7 +1040,7 @@ impl Storage { )) } - pub(crate) fn store_all_in_archive( + pub fn store_all_in_archive( &self, archive_path: &str, root_dir: &Path, @@ -1053,7 +1049,7 @@ impl Storage { .block_on(self.inner.store_all_in_archive(archive_path, root_dir)) } - pub(crate) fn store_all( + pub fn store_all( &self, prefix: &Path, root_dir: &Path, @@ -1063,14 +1059,14 @@ impl Storage { } #[cfg(test)] - pub(crate) fn store_blobs(&self, blobs: Vec) -> Result<()> { + pub fn store_blobs(&self, blobs: Vec) -> Result<()> { self.runtime.block_on(self.inner.store_blobs(blobs)) } // Store file into the backend at the given path, uncompressed. // The path will also be used to determine the mime type. #[instrument(skip(self, content))] - pub(crate) fn store_one_uncompressed( + pub fn store_one_uncompressed( &self, path: impl Into + std::fmt::Debug, content: impl Into>, @@ -1082,7 +1078,7 @@ impl Storage { // Store file into the backend at the given path (also used to detect mime type), returns the // chosen compression algorithm #[instrument(skip(self, content))] - pub(crate) fn store_one( + pub fn store_one( &self, path: impl Into + std::fmt::Debug, content: impl Into>, @@ -1093,7 +1089,7 @@ impl Storage { // Store file into the backend at the given path (also used to detect mime type), returns the // chosen compression algorithm #[instrument(skip(self))] - pub(crate) fn store_path( + pub fn store_path( &self, target_path: impl Into + std::fmt::Debug, source_path: impl AsRef + std::fmt::Debug, @@ -1105,7 +1101,7 @@ impl Storage { /// sync wrapper for the list_prefix function /// purely for testing purposes since it collects all files into a Vec. #[cfg(test)] - pub(crate) fn list_prefix(&self, prefix: &str) -> impl Iterator> { + pub fn list_prefix(&self, prefix: &str) -> impl Iterator> { use futures_util::stream::StreamExt; self.runtime .block_on(async { @@ -1119,7 +1115,7 @@ impl Storage { } #[instrument(skip(self))] - pub(crate) fn delete_prefix(&self, prefix: &str) -> Result<()> { + pub fn delete_prefix(&self, prefix: &str) -> Result<()> { self.runtime.block_on(self.inner.delete_prefix(prefix)) } @@ -1127,7 +1123,7 @@ impl Storage { // we leak the web server, and Drop isn't executed in that case (since the leaked web server // still holds a reference to the storage). #[cfg(test)] - pub(crate) async fn cleanup_after_test(&self) -> Result<()> { + pub async fn cleanup_after_test(&self) -> Result<()> { self.inner.cleanup_after_test().await } } @@ -1138,13 +1134,13 @@ impl std::fmt::Debug for Storage { } } -pub(crate) fn rustdoc_archive_path(name: &str, version: &Version) -> String { +pub fn rustdoc_archive_path(name: &str, version: &Version) -> String { format!("rustdoc/{name}/{version}.zip") } #[derive(strum::Display, Debug, PartialEq, Eq, Clone, Copy)] #[strum(serialize_all = "snake_case")] -pub(crate) enum RustdocJsonFormatVersion { +pub enum RustdocJsonFormatVersion { #[strum(serialize = "{0}")] Version(u16), Latest, @@ -1161,7 +1157,7 @@ impl FromStr for RustdocJsonFormatVersion { } } -pub(crate) fn rustdoc_json_path( +pub fn rustdoc_json_path( name: &str, version: &Version, target: &str, @@ -1180,882 +1176,882 @@ pub(crate) fn rustdoc_json_path( path } -pub(crate) fn source_archive_path(name: &str, version: &Version) -> String { +pub fn source_archive_path(name: &str, version: &Version) -> String { format!("sources/{name}/{version}.zip") } -#[cfg(test)] -mod test { - use super::*; - use crate::{test::TestEnvironment, web::headers::compute_etag}; - use std::env; - use test_case::test_case; - - const ZSTD_EOF_BYTES: [u8; 3] = [0x01, 0x00, 0x00]; - - fn streaming_blob( - content: impl Into>, - alg: Option, - ) -> StreamingBlob { - let content = content.into(); - StreamingBlob { - path: "some_path.db".into(), - mime: mime::APPLICATION_OCTET_STREAM, - date_updated: Utc::now(), - compression: alg, - etag: Some(compute_etag(&content)), - content_length: content.len(), - content: Box::new(io::Cursor::new(content)), - } - } - - #[tokio::test] - async fn test_streaming_blob_uncompressed() -> Result<()> { - const CONTENT: &[u8] = b"Hello, world!"; - - // without decompression - { - let stream = streaming_blob(CONTENT, None); - let blob = stream.materialize(usize::MAX).await?; - assert_eq!(blob.content, CONTENT); - assert!(blob.compression.is_none()); - } - - // with decompression, does nothing - { - let stream = streaming_blob(CONTENT, None); - let blob = stream.decompress().await?.materialize(usize::MAX).await?; - assert_eq!(blob.content, CONTENT); - assert!(blob.compression.is_none()); - } - - Ok(()) - } - - #[tokio::test] - async fn test_streaming_broken_zstd_blob() -> Result<()> { - const NOT_ZSTD: &[u8] = b"Hello, world!"; - let alg = CompressionAlgorithm::Zstd; - - // without decompression - // Doesn't fail because we don't call `.decompress` - { - let stream = streaming_blob(NOT_ZSTD, Some(alg)); - let blob = stream.materialize(usize::MAX).await?; - assert_eq!(blob.content, NOT_ZSTD); - assert_eq!(blob.compression, Some(alg)); - } - - // with decompression - // should fail in the `.decompress` call, - // not later when materializing / streaming. - { - let err = streaming_blob(NOT_ZSTD, Some(alg)) - .decompress() - .await - .unwrap_err(); - - assert_eq!(err.kind(), io::ErrorKind::Other); - - assert_eq!( - err.to_string(), - "Unknown frame descriptor", - "unexpected error: {}", - err - ); - } - - Ok(()) - } - - #[tokio::test] - async fn test_streaming_blob_zstd() -> Result<()> { - const CONTENT: &[u8] = b"Hello, world!"; - let mut compressed_content = Vec::new(); - let alg = CompressionAlgorithm::Zstd; - compress_async( - &mut io::Cursor::new(CONTENT.to_vec()), - &mut compressed_content, - alg, - ) - .await?; - - // without decompression - { - let stream = streaming_blob(compressed_content.clone(), Some(alg)); - let blob = stream.materialize(usize::MAX).await?; - assert_eq!(blob.content, compressed_content); - assert_eq!(blob.content.last_chunk::<3>().unwrap(), &ZSTD_EOF_BYTES); - assert_eq!(blob.compression, Some(alg)); - } - - // with decompression - { - let blob = streaming_blob(compressed_content.clone(), Some(alg)) - .decompress() - .await? - .materialize(usize::MAX) - .await?; - assert_eq!(blob.content, CONTENT); - assert!(blob.compression.is_none()); - } - - Ok(()) - } - - #[tokio::test] - #[test_case(CompressionAlgorithm::Zstd)] - #[test_case(CompressionAlgorithm::Bzip2)] - #[test_case(CompressionAlgorithm::Gzip)] - async fn test_async_compression(alg: CompressionAlgorithm) -> Result<()> { - const CONTENT: &[u8] = b"Hello, world! Hello, world! Hello, world! Hello, world!"; - - let compressed_index_content = { - let mut buf: Vec = Vec::new(); - compress_async(&mut io::Cursor::new(CONTENT.to_vec()), &mut buf, alg).await?; - buf - }; - - { - // try low-level async decompression - let mut decompressed_buf: Vec = Vec::new(); - let mut reader = wrap_reader_for_decompression( - io::Cursor::new(compressed_index_content.clone()), - alg, - ); - - tokio::io::copy(&mut reader, &mut io::Cursor::new(&mut decompressed_buf)).await?; - - assert_eq!(decompressed_buf, CONTENT); - } - - { - // try sync decompression - let decompressed_buf: Vec = decompress( - io::Cursor::new(compressed_index_content.clone()), - alg, - usize::MAX, - )?; - - assert_eq!(decompressed_buf, CONTENT); - } - - // try decompress via storage API - let blob = StreamingBlob { - path: "some_path.db".into(), - mime: mime::APPLICATION_OCTET_STREAM, - date_updated: Utc::now(), - etag: None, - compression: Some(alg), - content_length: compressed_index_content.len(), - content: Box::new(io::Cursor::new(compressed_index_content)), - } - .decompress() - .await? - .materialize(usize::MAX) - .await?; - - assert_eq!(blob.compression, None); - assert_eq!(blob.content, CONTENT); - - Ok(()) - } - - #[test_case("latest", RustdocJsonFormatVersion::Latest)] - #[test_case("42", RustdocJsonFormatVersion::Version(42))] - fn test_json_format_version(input: &str, expected: RustdocJsonFormatVersion) { - // test Display - assert_eq!(expected.to_string(), input); - // test FromStr - assert_eq!(expected, input.parse().unwrap()); - } - - #[test] - fn test_get_file_list() -> Result<()> { - crate::test::init_logger(); - let dir = env::current_dir().unwrap(); - - let files: Vec<_> = get_file_list(&dir).collect::>>()?; - assert!(!files.is_empty()); - - let files: Vec<_> = get_file_list(dir.join("Cargo.toml")).collect::>>()?; - assert_eq!(files[0], std::path::Path::new("Cargo.toml")); - - Ok(()) - } - - #[test] - fn test_mime_types() { - check_mime(".gitignore", "text/plain"); - check_mime("hello.toml", "text/toml"); - check_mime("hello.css", "text/css"); - check_mime("hello.js", "text/javascript"); - check_mime("hello.html", "text/html"); - check_mime("hello.hello.md", "text/markdown"); - check_mime("hello.markdown", "text/markdown"); - check_mime("hello.json", "application/json"); - check_mime("hello.txt", "text/plain"); - check_mime("file.rs", "text/rust"); - check_mime("important.svg", "image/svg+xml"); - } - - fn check_mime(path: &str, expected_mime: &str) { - let detected_mime = detect_mime(Path::new(&path)); - assert_eq!(detected_mime, expected_mime); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_outdated_local_archive_index_gets_redownloaded() -> Result<()> { - use tokio::fs; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .storage_backend(StorageKind::S3) - .build()?, - ) - .await?; - - let storage = env.async_storage(); - - // virtual latest build id, used for local caching of the index files - const LATEST_BUILD_ID: Option = Some(BuildId(42)); - let cache_root = env.config().local_archive_cache_path.clone(); - - let cache_filename = |archive_name: &str| { - cache_root.join(format!( - "{}.{}.{}", - archive_name, - LATEST_BUILD_ID.unwrap(), - ARCHIVE_INDEX_FILE_EXTENSION - )) - }; - - /// dummy archives, files will contain their name as content - async fn create_archive( - storage: &AsyncStorage, - archive_name: &str, - filenames: &[&str], - ) -> Result<()> { - let dir = tempfile::Builder::new() - .prefix("docs.rs-upload-archive-test") - .tempdir()?; - for &file in filenames.iter() { - let path = dir.path().join(file); - fs::write(path, file).await?; - } - storage - .store_all_in_archive(archive_name, dir.path()) - .await?; - - Ok(()) - } - - // create two archives with indexes that contain the same filename - create_archive( - storage, - "test1.zip", - &["file1.txt", "file2.txt", "important.txt"], - ) - .await?; - - create_archive( - storage, - "test2.zip", - &["important.txt", "another_file_1.txt", "another_file_2.txt"], - ) - .await?; - - for archive_name in &["test1.zip", "test2.zip"] { - assert!(storage.exists(archive_name).await?); - - assert!( - storage - .exists(&format!("{}.{ARCHIVE_INDEX_FILE_EXTENSION}", archive_name)) - .await? - ); - // local index cache doesn't exist yet - let local_index_file = cache_filename(archive_name); - assert!(!fs::try_exists(&local_index_file).await?); - - // this will then create the cache - assert!( - storage - .exists_in_archive(archive_name, LATEST_BUILD_ID, "important.txt") - .await? - ); - assert!(fs::try_exists(&local_index_file).await?); - - // fetching the content out of the archive also works - assert_eq!( - storage - .get_from_archive(archive_name, LATEST_BUILD_ID, "important.txt", usize::MAX) - .await? - .content, - b"important.txt" - ); - } - - // validate if the positions are really different in the archvies, - // for the same filename. - let pos_in_test1_zip = storage - .find_in_archive_index("test1.zip", LATEST_BUILD_ID, "important.txt") - .await? - .unwrap(); - let pos_in_test2_zip = storage - .find_in_archive_index("test2.zip", LATEST_BUILD_ID, "important.txt") - .await? - .unwrap(); - - assert_ne!(pos_in_test1_zip.range(), pos_in_test2_zip.range()); - - // now I'm swapping the local index files. - // This should simulate hat I have an outdated byte-range for a file - - let local_index_file_1 = cache_filename("test1.zip"); - let local_index_file_2 = cache_filename("test2.zip"); - - { - let temp_path = cache_root.join("temp_index_swap.tmp"); - fs::rename(&local_index_file_1, &temp_path).await?; - fs::rename(&local_index_file_2, &local_index_file_1).await?; - fs::rename(&temp_path, &local_index_file_2).await?; - } - - // now try to fetch the files inside the archives again, the local files - // should be removed, refetched, and all should be fine. - // Without our fallback / delete mechanism, this would fail. - - for archive_name in &["test1.zip", "test2.zip"] { - assert_eq!( - storage - .get_from_archive(archive_name, LATEST_BUILD_ID, "important.txt", usize::MAX) - .await? - .content, - b"important.txt" - ); - } - - Ok(()) - } -} - -/// Backend tests are a set of tests executed on all the supported storage backends. They ensure -/// docs.rs behaves the same no matter the storage backend currently used. -/// -/// To add a new test create the function without adding the `#[test]` attribute, and add the -/// function name to the `backend_tests!` macro at the bottom of the module. -/// -/// This is the preferred way to test whether backends work. -#[cfg(test)] -mod backend_tests { - use super::*; - use crate::{test::TestEnvironment, web::headers::compute_etag}; - - fn get_file_info(files: &[FileEntry], path: impl AsRef) -> Option<&FileEntry> { - let path = path.as_ref(); - files.iter().find(|info| info.path == path) - } - - fn test_exists(storage: &Storage) -> Result<()> { - assert!(!storage.exists("path/to/file.txt").unwrap()); - let blob = BlobUpload { - path: "path/to/file.txt".into(), - mime: mime::TEXT_PLAIN, - content: "Hello world!".into(), - compression: None, - }; - storage.store_blobs(vec![blob])?; - assert!(storage.exists("path/to/file.txt")?); - - Ok(()) - } - - fn test_get_object(storage: &Storage) -> Result<()> { - let path: &str = "foo/bar.txt"; - let blob = BlobUpload { - path: path.into(), - mime: mime::TEXT_PLAIN, - compression: None, - content: b"test content\n".to_vec(), - }; - - storage.store_blobs(vec![blob.clone()])?; - - let found = storage.get(path, usize::MAX)?; - assert_eq!(blob.mime, found.mime); - assert_eq!(blob.content, found.content); - // while our db backend just does MD5, - // it seems like minio does it too :) - assert_eq!(found.etag, Some(compute_etag(&blob.content))); - - for path in &["bar.txt", "baz.txt", "foo/baz.txt"] { - assert!( - storage - .get(path, usize::MAX) - .unwrap_err() - .downcast_ref::() - .is_some() - ); - } - - Ok(()) - } - - fn test_get_range(storage: &Storage) -> Result<()> { - let blob = BlobUpload { - path: "foo/bar.txt".into(), - mime: mime::TEXT_PLAIN, - compression: None, - content: b"test content\n".to_vec(), - }; - - let full_etag = compute_etag(&blob.content); - - storage.store_blobs(vec![blob.clone()])?; - - let mut etags = Vec::new(); - - for range in [0..=4, 5..=12] { - let partial_blob = storage.get_range("foo/bar.txt", usize::MAX, range.clone(), None)?; - let range = (*range.start() as usize)..=(*range.end() as usize); - assert_eq!(blob.content[range], partial_blob.content); - - etags.push(partial_blob.etag.unwrap()); - } - if let [etag1, etag2] = &etags[..] { - assert_ne!(etag1, etag2); - assert_ne!(etag1, &full_etag); - assert_ne!(etag2, &full_etag); - } else { - panic!("expected two etags"); - } - - for path in &["bar.txt", "baz.txt", "foo/baz.txt"] { - assert!( - storage - .get_range(path, usize::MAX, 0..=4, None) - .unwrap_err() - .downcast_ref::() - .is_some() - ); - } - - Ok(()) - } - - fn test_list_prefix(storage: &Storage) -> Result<()> { - static FILENAMES: &[&str] = &["baz.txt", "some/bar.txt"]; - - storage.store_blobs( - FILENAMES - .iter() - .map(|&filename| BlobUpload { - path: filename.into(), - mime: mime::TEXT_PLAIN, - compression: None, - content: b"test content\n".to_vec(), - }) - .collect(), - )?; - - assert_eq!( - storage.list_prefix("").collect::>>()?, - FILENAMES - ); - - assert_eq!( - storage - .list_prefix("some/") - .collect::>>()?, - &["some/bar.txt"] - ); - - Ok(()) - } - - fn test_too_long_filename(storage: &Storage) -> Result<()> { - // minio returns ErrKeyTooLongError when the key is over 1024 bytes long. - // When testing, minio just gave me `XMinioInvalidObjectName`, so I'll check that too. - let long_filename = "ATCG".repeat(512); - - assert!( - storage - .get(&long_filename, 42) - .unwrap_err() - .is::() - ); - - Ok(()) - } - - fn test_get_too_big(storage: &Storage) -> Result<()> { - const MAX_SIZE: usize = 1024; - - let small_blob = BlobUpload { - path: "small-blob.bin".into(), - mime: mime::TEXT_PLAIN, - content: vec![0; MAX_SIZE], - compression: None, - }; - let big_blob = BlobUpload { - path: "big-blob.bin".into(), - mime: mime::TEXT_PLAIN, - content: vec![0; MAX_SIZE * 2], - compression: None, - }; - - storage.store_blobs(vec![small_blob.clone(), big_blob])?; - - let blob = storage.get("small-blob.bin", MAX_SIZE)?; - assert_eq!(blob.content.len(), small_blob.content.len()); - - assert!( - storage - .get("big-blob.bin", MAX_SIZE) - .unwrap_err() - .downcast_ref::() - .and_then(|io| io.get_ref()) - .and_then(|err| err.downcast_ref::()) - .is_some() - ); - - Ok(()) - } - - fn test_store_blobs(env: &TestEnvironment, storage: &Storage) -> Result<()> { - const NAMES: &[&str] = &[ - "a", - "b", - "a_very_long_file_name_that_has_an.extension", - "parent/child", - "h/i/g/h/l/y/_/n/e/s/t/e/d/_/d/i/r/e/c/t/o/r/i/e/s", - ]; - - let blobs = NAMES - .iter() - .map(|&path| BlobUpload { - path: path.into(), - mime: mime::TEXT_PLAIN, - compression: None, - content: b"Hello world!\n".to_vec(), - }) - .collect::>(); - - storage.store_blobs(blobs.clone()).unwrap(); - - for blob in &blobs { - let actual = storage.get(&blob.path, usize::MAX)?; - assert_eq!(blob.path, actual.path); - assert_eq!(blob.mime, actual.mime); - } - - let collected_metrics = env.collected_metrics(); - - assert_eq!( - collected_metrics - .get_metric("storage", "docsrs.storage.uploaded_files")? - .get_u64_counter() - .value(), - NAMES.len() as u64, - ); - - Ok(()) - } - - fn test_exists_without_remote_archive(storage: &Storage) -> Result<()> { - // when remote and local index don't exist, any `exists_in_archive` should - // return `false` - assert!(!storage.exists_in_archive("some_archive_name", None, "some_file_name")?); - Ok(()) - } - - fn test_store_all_in_archive(env: &TestEnvironment, storage: &Storage) -> Result<()> { - let dir = tempfile::Builder::new() - .prefix("docs.rs-upload-archive-test") - .tempdir()?; - let files = ["Cargo.toml", "src/main.rs"]; - for &file in &files { - let path = dir.path().join(file); - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, "data")?; - } - - let local_index_location = storage - .inner - .config - .local_archive_cache_path - .join(format!("folder/test.zip.0.{ARCHIVE_INDEX_FILE_EXTENSION}")); - - let (stored_files, compression_alg) = - storage.store_all_in_archive("folder/test.zip", dir.path())?; - - assert!(storage.exists(&format!("folder/test.zip.{ARCHIVE_INDEX_FILE_EXTENSION}"))?); - - assert_eq!(compression_alg, CompressionAlgorithm::Bzip2); - assert_eq!(stored_files.len(), files.len()); - for name in &files { - assert!(get_file_info(&stored_files, name).is_some()); - } - assert_eq!( - get_file_info(&stored_files, "Cargo.toml").unwrap().mime(), - "text/toml" - ); - assert_eq!( - get_file_info(&stored_files, "src/main.rs").unwrap().mime(), - "text/rust" - ); - - // delete the existing index to test the download of it - if local_index_location.exists() { - fs::remove_file(&local_index_location)?; - } - - // the first exists-query will download and store the index - assert!(!local_index_location.exists()); - assert!(storage.exists_in_archive("folder/test.zip", None, "Cargo.toml",)?); - - // the second one will use the local index - assert!(local_index_location.exists()); - assert!(storage.exists_in_archive("folder/test.zip", None, "src/main.rs",)?); - - let file = storage.get_from_archive("folder/test.zip", None, "Cargo.toml", usize::MAX)?; - assert_eq!(file.content, b"data"); - assert_eq!(file.mime, "text/toml"); - assert_eq!(file.path, "folder/test.zip/Cargo.toml"); - - let file = storage.get_from_archive("folder/test.zip", None, "src/main.rs", usize::MAX)?; - assert_eq!(file.content, b"data"); - assert_eq!(file.mime, "text/rust"); - assert_eq!(file.path, "folder/test.zip/src/main.rs"); - - let collected_metrics = env.collected_metrics(); - - assert_eq!( - collected_metrics - .get_metric("storage", "docsrs.storage.uploaded_files")? - .get_u64_counter() - .value(), - 2, - ); - - Ok(()) - } - - fn test_store_all(env: &TestEnvironment, storage: &Storage) -> Result<()> { - let dir = tempfile::Builder::new() - .prefix("docs.rs-upload-test") - .tempdir()?; - let files = ["Cargo.toml", "src/main.rs"]; - for &file in &files { - let path = dir.path().join(file); - if let Some(parent) = path.parent() { - fs::create_dir_all(parent)?; - } - fs::write(path, "data")?; - } - - let (stored_files, algs) = storage.store_all(Path::new("prefix"), dir.path())?; - assert_eq!(stored_files.len(), files.len()); - for name in &files { - assert!(get_file_info(&stored_files, name).is_some()); - } - assert_eq!( - get_file_info(&stored_files, "Cargo.toml").unwrap().mime(), - "text/toml" - ); - assert_eq!( - get_file_info(&stored_files, "src/main.rs").unwrap().mime(), - "text/rust" - ); - - let file = storage.get("prefix/Cargo.toml", usize::MAX)?; - assert_eq!(file.content, b"data"); - assert_eq!(file.mime, "text/toml"); - assert_eq!(file.path, "prefix/Cargo.toml"); - - let file = storage.get("prefix/src/main.rs", usize::MAX)?; - assert_eq!(file.content, b"data"); - assert_eq!(file.mime, "text/rust"); - assert_eq!(file.path, "prefix/src/main.rs"); - - assert_eq!(algs, CompressionAlgorithm::default()); - - let collected_metrics = env.collected_metrics(); - assert_eq!( - collected_metrics - .get_metric("storage", "docsrs.storage.uploaded_files")? - .get_u64_counter() - .value(), - 2, - ); - - Ok(()) - } - - fn test_batched_uploads(storage: &Storage) -> Result<()> { - let uploads: Vec<_> = (0..=100) - .map(|i| { - let content = format!("const IDX: usize = {i};").as_bytes().to_vec(); - BlobUpload { - mime: mimes::TEXT_RUST.clone(), - content, - path: format!("{i}.rs"), - compression: None, - } - }) - .collect(); - - storage.store_blobs(uploads.clone())?; - - for blob in &uploads { - let stored = storage.get(&blob.path, usize::MAX)?; - assert_eq!(&stored.content, &blob.content); - } - - Ok(()) - } - - fn test_delete_prefix_without_matches(storage: &Storage) -> Result<()> { - storage.delete_prefix("prefix_without_objects") - } - - fn test_delete_prefix(storage: &Storage) -> Result<()> { - test_deletion( - storage, - "foo/bar/", - &[ - "foo.txt", - "foo/bar.txt", - "foo/bar/baz.txt", - "foo/bar/foobar.txt", - "bar.txt", - ], - &["foo.txt", "foo/bar.txt", "bar.txt"], - &["foo/bar/baz.txt", "foo/bar/foobar.txt"], - ) - } - - fn test_delete_percent(storage: &Storage) -> Result<()> { - // PostgreSQL treats "%" as a special char when deleting a prefix. Make sure any "%" in the - // provided prefix is properly escaped. - test_deletion( - storage, - "foo/%/", - &["foo/bar.txt", "foo/%/bar.txt"], - &["foo/bar.txt"], - &["foo/%/bar.txt"], - ) - } - - fn test_deletion( - storage: &Storage, - prefix: &str, - start: &[&str], - present: &[&str], - missing: &[&str], - ) -> Result<()> { - storage.store_blobs( - start - .iter() - .map(|path| BlobUpload { - path: (*path).to_string(), - content: b"foo\n".to_vec(), - compression: None, - mime: mime::TEXT_PLAIN, - }) - .collect(), - )?; - - storage.delete_prefix(prefix)?; - - for existing in present { - assert!(storage.get(existing, usize::MAX).is_ok()); - } - for missing in missing { - assert!( - storage - .get(missing, usize::MAX) - .unwrap_err() - .downcast_ref::() - .is_some() - ); - } - - Ok(()) - } - - // Remember to add the test name to the macro below when adding a new one. - - macro_rules! backend_tests { - ( - backends { $($backend:ident => $config:expr,)* } - tests $tests:tt - tests_with_metrics $tests_with_metrics:tt - ) => { - $( - mod $backend { - use crate::test::TestEnvironment; - use crate::storage::{StorageKind}; - - fn get_env() -> anyhow::Result { - crate::test::TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .storage_backend($config) - .build()? - ) - } - - backend_tests!(@tests $tests); - backend_tests!(@tests_with_metrics $tests_with_metrics); - } - )* - }; - (@tests { $($test:ident,)* }) => { - $( - #[test] - fn $test() -> anyhow::Result<()> { - let env = get_env()?; - super::$test(&*env.storage()) - } - )* - }; - (@tests_with_metrics { $($test:ident,)* }) => { - $( - #[test] - fn $test() -> anyhow::Result<()> { - let env = get_env()?; - super::$test(&env, &*env.storage()) - } - )* - }; - } - - backend_tests! { - backends { - s3 => StorageKind::S3, - database => StorageKind::Database, - } - - tests { - test_batched_uploads, - test_exists, - test_get_object, - test_get_range, - test_get_too_big, - test_too_long_filename, - test_list_prefix, - test_delete_prefix, - test_delete_prefix_without_matches, - test_delete_percent, - test_exists_without_remote_archive, - } - - tests_with_metrics { - test_store_blobs, - test_store_all, - test_store_all_in_archive, - } - } -} +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::{test::TestEnvironment, web::headers::compute_etag}; +// use std::env; +// use test_case::test_case; + +// const ZSTD_EOF_BYTES: [u8; 3] = [0x01, 0x00, 0x00]; + +// fn streaming_blob( +// content: impl Into>, +// alg: Option, +// ) -> StreamingBlob { +// let content = content.into(); +// StreamingBlob { +// path: "some_path.db".into(), +// mime: mime::APPLICATION_OCTET_STREAM, +// date_updated: Utc::now(), +// compression: alg, +// etag: Some(compute_etag(&content)), +// content_length: content.len(), +// content: Box::new(io::Cursor::new(content)), +// } +// } + +// #[tokio::test] +// async fn test_streaming_blob_uncompressed() -> Result<()> { +// const CONTENT: &[u8] = b"Hello, world!"; + +// // without decompression +// { +// let stream = streaming_blob(CONTENT, None); +// let blob = stream.materialize(usize::MAX).await?; +// assert_eq!(blob.content, CONTENT); +// assert!(blob.compression.is_none()); +// } + +// // with decompression, does nothing +// { +// let stream = streaming_blob(CONTENT, None); +// let blob = stream.decompress().await?.materialize(usize::MAX).await?; +// assert_eq!(blob.content, CONTENT); +// assert!(blob.compression.is_none()); +// } + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_streaming_broken_zstd_blob() -> Result<()> { +// const NOT_ZSTD: &[u8] = b"Hello, world!"; +// let alg = CompressionAlgorithm::Zstd; + +// // without decompression +// // Doesn't fail because we don't call `.decompress` +// { +// let stream = streaming_blob(NOT_ZSTD, Some(alg)); +// let blob = stream.materialize(usize::MAX).await?; +// assert_eq!(blob.content, NOT_ZSTD); +// assert_eq!(blob.compression, Some(alg)); +// } + +// // with decompression +// // should fail in the `.decompress` call, +// // not later when materializing / streaming. +// { +// let err = streaming_blob(NOT_ZSTD, Some(alg)) +// .decompress() +// .await +// .unwrap_err(); + +// assert_eq!(err.kind(), io::ErrorKind::Other); + +// assert_eq!( +// err.to_string(), +// "Unknown frame descriptor", +// "unexpected error: {}", +// err +// ); +// } + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_streaming_blob_zstd() -> Result<()> { +// const CONTENT: &[u8] = b"Hello, world!"; +// let mut compressed_content = Vec::new(); +// let alg = CompressionAlgorithm::Zstd; +// compress_async( +// &mut io::Cursor::new(CONTENT.to_vec()), +// &mut compressed_content, +// alg, +// ) +// .await?; + +// // without decompression +// { +// let stream = streaming_blob(compressed_content.clone(), Some(alg)); +// let blob = stream.materialize(usize::MAX).await?; +// assert_eq!(blob.content, compressed_content); +// assert_eq!(blob.content.last_chunk::<3>().unwrap(), &ZSTD_EOF_BYTES); +// assert_eq!(blob.compression, Some(alg)); +// } + +// // with decompression +// { +// let blob = streaming_blob(compressed_content.clone(), Some(alg)) +// .decompress() +// .await? +// .materialize(usize::MAX) +// .await?; +// assert_eq!(blob.content, CONTENT); +// assert!(blob.compression.is_none()); +// } + +// Ok(()) +// } + +// #[tokio::test] +// #[test_case(CompressionAlgorithm::Zstd)] +// #[test_case(CompressionAlgorithm::Bzip2)] +// #[test_case(CompressionAlgorithm::Gzip)] +// async fn test_async_compression(alg: CompressionAlgorithm) -> Result<()> { +// const CONTENT: &[u8] = b"Hello, world! Hello, world! Hello, world! Hello, world!"; + +// let compressed_index_content = { +// let mut buf: Vec = Vec::new(); +// compress_async(&mut io::Cursor::new(CONTENT.to_vec()), &mut buf, alg).await?; +// buf +// }; + +// { +// // try low-level async decompression +// let mut decompressed_buf: Vec = Vec::new(); +// let mut reader = wrap_reader_for_decompression( +// io::Cursor::new(compressed_index_content.clone()), +// alg, +// ); + +// tokio::io::copy(&mut reader, &mut io::Cursor::new(&mut decompressed_buf)).await?; + +// assert_eq!(decompressed_buf, CONTENT); +// } + +// { +// // try sync decompression +// let decompressed_buf: Vec = decompress( +// io::Cursor::new(compressed_index_content.clone()), +// alg, +// usize::MAX, +// )?; + +// assert_eq!(decompressed_buf, CONTENT); +// } + +// // try decompress via storage API +// let blob = StreamingBlob { +// path: "some_path.db".into(), +// mime: mime::APPLICATION_OCTET_STREAM, +// date_updated: Utc::now(), +// etag: None, +// compression: Some(alg), +// content_length: compressed_index_content.len(), +// content: Box::new(io::Cursor::new(compressed_index_content)), +// } +// .decompress() +// .await? +// .materialize(usize::MAX) +// .await?; + +// assert_eq!(blob.compression, None); +// assert_eq!(blob.content, CONTENT); + +// Ok(()) +// } + +// #[test_case("latest", RustdocJsonFormatVersion::Latest)] +// #[test_case("42", RustdocJsonFormatVersion::Version(42))] +// fn test_json_format_version(input: &str, expected: RustdocJsonFormatVersion) { +// // test Display +// assert_eq!(expected.to_string(), input); +// // test FromStr +// assert_eq!(expected, input.parse().unwrap()); +// } + +// #[test] +// fn test_get_file_list() -> Result<()> { +// crate::test::init_logger(); +// let dir = env::current_dir().unwrap(); + +// let files: Vec<_> = get_file_list(&dir).collect::>>()?; +// assert!(!files.is_empty()); + +// let files: Vec<_> = get_file_list(dir.join("Cargo.toml")).collect::>>()?; +// assert_eq!(files[0], std::path::Path::new("Cargo.toml")); + +// Ok(()) +// } + +// #[test] +// fn test_mime_types() { +// check_mime(".gitignore", "text/plain"); +// check_mime("hello.toml", "text/toml"); +// check_mime("hello.css", "text/css"); +// check_mime("hello.js", "text/javascript"); +// check_mime("hello.html", "text/html"); +// check_mime("hello.hello.md", "text/markdown"); +// check_mime("hello.markdown", "text/markdown"); +// check_mime("hello.json", "application/json"); +// check_mime("hello.txt", "text/plain"); +// check_mime("file.rs", "text/rust"); +// check_mime("important.svg", "image/svg+xml"); +// } + +// fn check_mime(path: &str, expected_mime: &str) { +// let detected_mime = detect_mime(Path::new(&path)); +// assert_eq!(detected_mime, expected_mime); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_outdated_local_archive_index_gets_redownloaded() -> Result<()> { +// use tokio::fs; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .storage_backend(StorageKind::S3) +// .build()?, +// ) +// .await?; + +// let storage = env.async_storage(); + +// // virtual latest build id, used for local caching of the index files +// const LATEST_BUILD_ID: Option = Some(BuildId(42)); +// let cache_root = env.config().local_archive_cache_path.clone(); + +// let cache_filename = |archive_name: &str| { +// cache_root.join(format!( +// "{}.{}.{}", +// archive_name, +// LATEST_BUILD_ID.unwrap(), +// ARCHIVE_INDEX_FILE_EXTENSION +// )) +// }; + +// /// dummy archives, files will contain their name as content +// async fn create_archive( +// storage: &AsyncStorage, +// archive_name: &str, +// filenames: &[&str], +// ) -> Result<()> { +// let dir = tempfile::Builder::new() +// .prefix("docs.rs-upload-archive-test") +// .tempdir()?; +// for &file in filenames.iter() { +// let path = dir.path().join(file); +// fs::write(path, file).await?; +// } +// storage +// .store_all_in_archive(archive_name, dir.path()) +// .await?; + +// Ok(()) +// } + +// // create two archives with indexes that contain the same filename +// create_archive( +// storage, +// "test1.zip", +// &["file1.txt", "file2.txt", "important.txt"], +// ) +// .await?; + +// create_archive( +// storage, +// "test2.zip", +// &["important.txt", "another_file_1.txt", "another_file_2.txt"], +// ) +// .await?; + +// for archive_name in &["test1.zip", "test2.zip"] { +// assert!(storage.exists(archive_name).await?); + +// assert!( +// storage +// .exists(&format!("{}.{ARCHIVE_INDEX_FILE_EXTENSION}", archive_name)) +// .await? +// ); +// // local index cache doesn't exist yet +// let local_index_file = cache_filename(archive_name); +// assert!(!fs::try_exists(&local_index_file).await?); + +// // this will then create the cache +// assert!( +// storage +// .exists_in_archive(archive_name, LATEST_BUILD_ID, "important.txt") +// .await? +// ); +// assert!(fs::try_exists(&local_index_file).await?); + +// // fetching the content out of the archive also works +// assert_eq!( +// storage +// .get_from_archive(archive_name, LATEST_BUILD_ID, "important.txt", usize::MAX) +// .await? +// .content, +// b"important.txt" +// ); +// } + +// // validate if the positions are really different in the archvies, +// // for the same filename. +// let pos_in_test1_zip = storage +// .find_in_archive_index("test1.zip", LATEST_BUILD_ID, "important.txt") +// .await? +// .unwrap(); +// let pos_in_test2_zip = storage +// .find_in_archive_index("test2.zip", LATEST_BUILD_ID, "important.txt") +// .await? +// .unwrap(); + +// assert_ne!(pos_in_test1_zip.range(), pos_in_test2_zip.range()); + +// // now I'm swapping the local index files. +// // This should simulate hat I have an outdated byte-range for a file + +// let local_index_file_1 = cache_filename("test1.zip"); +// let local_index_file_2 = cache_filename("test2.zip"); + +// { +// let temp_path = cache_root.join("temp_index_swap.tmp"); +// fs::rename(&local_index_file_1, &temp_path).await?; +// fs::rename(&local_index_file_2, &local_index_file_1).await?; +// fs::rename(&temp_path, &local_index_file_2).await?; +// } + +// // now try to fetch the files inside the archives again, the local files +// // should be removed, refetched, and all should be fine. +// // Without our fallback / delete mechanism, this would fail. + +// for archive_name in &["test1.zip", "test2.zip"] { +// assert_eq!( +// storage +// .get_from_archive(archive_name, LATEST_BUILD_ID, "important.txt", usize::MAX) +// .await? +// .content, +// b"important.txt" +// ); +// } + +// Ok(()) +// } +// } + +// Backend tests are a set of tests executed on all the supported storage backends. They ensure +// docs.rs behaves the same no matter the storage backend currently used. +// +// To add a new test create the function without adding the `#[test]` attribute, and add the +// function name to the `backend_tests!` macro at the bottom of the module. +// +// This is the preferred way to test whether backends work. +// #[cfg(test)] +// mod backend_tests { +// use super::*; +// use crate::{test::TestEnvironment, web::headers::compute_etag}; + +// fn get_file_info(files: &[FileEntry], path: impl AsRef) -> Option<&FileEntry> { +// let path = path.as_ref(); +// files.iter().find(|info| info.path == path) +// } + +// fn test_exists(storage: &Storage) -> Result<()> { +// assert!(!storage.exists("path/to/file.txt").unwrap()); +// let blob = BlobUpload { +// path: "path/to/file.txt".into(), +// mime: mime::TEXT_PLAIN, +// content: "Hello world!".into(), +// compression: None, +// }; +// storage.store_blobs(vec![blob])?; +// assert!(storage.exists("path/to/file.txt")?); + +// Ok(()) +// } + +// fn test_get_object(storage: &Storage) -> Result<()> { +// let path: &str = "foo/bar.txt"; +// let blob = BlobUpload { +// path: path.into(), +// mime: mime::TEXT_PLAIN, +// compression: None, +// content: b"test content\n".to_vec(), +// }; + +// storage.store_blobs(vec![blob.clone()])?; + +// let found = storage.get(path, usize::MAX)?; +// assert_eq!(blob.mime, found.mime); +// assert_eq!(blob.content, found.content); +// // while our db backend just does MD5, +// // it seems like minio does it too :) +// assert_eq!(found.etag, Some(compute_etag(&blob.content))); + +// for path in &["bar.txt", "baz.txt", "foo/baz.txt"] { +// assert!( +// storage +// .get(path, usize::MAX) +// .unwrap_err() +// .downcast_ref::() +// .is_some() +// ); +// } + +// Ok(()) +// } + +// fn test_get_range(storage: &Storage) -> Result<()> { +// let blob = BlobUpload { +// path: "foo/bar.txt".into(), +// mime: mime::TEXT_PLAIN, +// compression: None, +// content: b"test content\n".to_vec(), +// }; + +// let full_etag = compute_etag(&blob.content); + +// storage.store_blobs(vec![blob.clone()])?; + +// let mut etags = Vec::new(); + +// for range in [0..=4, 5..=12] { +// let partial_blob = storage.get_range("foo/bar.txt", usize::MAX, range.clone(), None)?; +// let range = (*range.start() as usize)..=(*range.end() as usize); +// assert_eq!(blob.content[range], partial_blob.content); + +// etags.push(partial_blob.etag.unwrap()); +// } +// if let [etag1, etag2] = &etags[..] { +// assert_ne!(etag1, etag2); +// assert_ne!(etag1, &full_etag); +// assert_ne!(etag2, &full_etag); +// } else { +// panic!("expected two etags"); +// } + +// for path in &["bar.txt", "baz.txt", "foo/baz.txt"] { +// assert!( +// storage +// .get_range(path, usize::MAX, 0..=4, None) +// .unwrap_err() +// .downcast_ref::() +// .is_some() +// ); +// } + +// Ok(()) +// } + +// fn test_list_prefix(storage: &Storage) -> Result<()> { +// static FILENAMES: &[&str] = &["baz.txt", "some/bar.txt"]; + +// storage.store_blobs( +// FILENAMES +// .iter() +// .map(|&filename| BlobUpload { +// path: filename.into(), +// mime: mime::TEXT_PLAIN, +// compression: None, +// content: b"test content\n".to_vec(), +// }) +// .collect(), +// )?; + +// assert_eq!( +// storage.list_prefix("").collect::>>()?, +// FILENAMES +// ); + +// assert_eq!( +// storage +// .list_prefix("some/") +// .collect::>>()?, +// &["some/bar.txt"] +// ); + +// Ok(()) +// } + +// fn test_too_long_filename(storage: &Storage) -> Result<()> { +// // minio returns ErrKeyTooLongError when the key is over 1024 bytes long. +// // When testing, minio just gave me `XMinioInvalidObjectName`, so I'll check that too. +// let long_filename = "ATCG".repeat(512); + +// assert!( +// storage +// .get(&long_filename, 42) +// .unwrap_err() +// .is::() +// ); + +// Ok(()) +// } + +// fn test_get_too_big(storage: &Storage) -> Result<()> { +// const MAX_SIZE: usize = 1024; + +// let small_blob = BlobUpload { +// path: "small-blob.bin".into(), +// mime: mime::TEXT_PLAIN, +// content: vec![0; MAX_SIZE], +// compression: None, +// }; +// let big_blob = BlobUpload { +// path: "big-blob.bin".into(), +// mime: mime::TEXT_PLAIN, +// content: vec![0; MAX_SIZE * 2], +// compression: None, +// }; + +// storage.store_blobs(vec![small_blob.clone(), big_blob])?; + +// let blob = storage.get("small-blob.bin", MAX_SIZE)?; +// assert_eq!(blob.content.len(), small_blob.content.len()); + +// assert!( +// storage +// .get("big-blob.bin", MAX_SIZE) +// .unwrap_err() +// .downcast_ref::() +// .and_then(|io| io.get_ref()) +// .and_then(|err| err.downcast_ref::()) +// .is_some() +// ); + +// Ok(()) +// } + +// fn test_store_blobs(env: &TestEnvironment, storage: &Storage) -> Result<()> { +// const NAMES: &[&str] = &[ +// "a", +// "b", +// "a_very_long_file_name_that_has_an.extension", +// "parent/child", +// "h/i/g/h/l/y/_/n/e/s/t/e/d/_/d/i/r/e/c/t/o/r/i/e/s", +// ]; + +// let blobs = NAMES +// .iter() +// .map(|&path| BlobUpload { +// path: path.into(), +// mime: mime::TEXT_PLAIN, +// compression: None, +// content: b"Hello world!\n".to_vec(), +// }) +// .collect::>(); + +// storage.store_blobs(blobs.clone()).unwrap(); + +// for blob in &blobs { +// let actual = storage.get(&blob.path, usize::MAX)?; +// assert_eq!(blob.path, actual.path); +// assert_eq!(blob.mime, actual.mime); +// } + +// let collected_metrics = env.collected_metrics(); + +// assert_eq!( +// collected_metrics +// .get_metric("storage", "docsrs.storage.uploaded_files")? +// .get_u64_counter() +// .value(), +// NAMES.len() as u64, +// ); + +// Ok(()) +// } + +// fn test_exists_without_remote_archive(storage: &Storage) -> Result<()> { +// // when remote and local index don't exist, any `exists_in_archive` should +// // return `false` +// assert!(!storage.exists_in_archive("some_archive_name", None, "some_file_name")?); +// Ok(()) +// } + +// fn test_store_all_in_archive(env: &TestEnvironment, storage: &Storage) -> Result<()> { +// let dir = tempfile::Builder::new() +// .prefix("docs.rs-upload-archive-test") +// .tempdir()?; +// let files = ["Cargo.toml", "src/main.rs"]; +// for &file in &files { +// let path = dir.path().join(file); +// if let Some(parent) = path.parent() { +// fs::create_dir_all(parent)?; +// } +// fs::write(path, "data")?; +// } + +// let local_index_location = storage +// .inner +// .config +// .local_archive_cache_path +// .join(format!("folder/test.zip.0.{ARCHIVE_INDEX_FILE_EXTENSION}")); + +// let (stored_files, compression_alg) = +// storage.store_all_in_archive("folder/test.zip", dir.path())?; + +// assert!(storage.exists(&format!("folder/test.zip.{ARCHIVE_INDEX_FILE_EXTENSION}"))?); + +// assert_eq!(compression_alg, CompressionAlgorithm::Bzip2); +// assert_eq!(stored_files.len(), files.len()); +// for name in &files { +// assert!(get_file_info(&stored_files, name).is_some()); +// } +// assert_eq!( +// get_file_info(&stored_files, "Cargo.toml").unwrap().mime(), +// "text/toml" +// ); +// assert_eq!( +// get_file_info(&stored_files, "src/main.rs").unwrap().mime(), +// "text/rust" +// ); + +// // delete the existing index to test the download of it +// if local_index_location.exists() { +// fs::remove_file(&local_index_location)?; +// } + +// // the first exists-query will download and store the index +// assert!(!local_index_location.exists()); +// assert!(storage.exists_in_archive("folder/test.zip", None, "Cargo.toml",)?); + +// // the second one will use the local index +// assert!(local_index_location.exists()); +// assert!(storage.exists_in_archive("folder/test.zip", None, "src/main.rs",)?); + +// let file = storage.get_from_archive("folder/test.zip", None, "Cargo.toml", usize::MAX)?; +// assert_eq!(file.content, b"data"); +// assert_eq!(file.mime, "text/toml"); +// assert_eq!(file.path, "folder/test.zip/Cargo.toml"); + +// let file = storage.get_from_archive("folder/test.zip", None, "src/main.rs", usize::MAX)?; +// assert_eq!(file.content, b"data"); +// assert_eq!(file.mime, "text/rust"); +// assert_eq!(file.path, "folder/test.zip/src/main.rs"); + +// let collected_metrics = env.collected_metrics(); + +// assert_eq!( +// collected_metrics +// .get_metric("storage", "docsrs.storage.uploaded_files")? +// .get_u64_counter() +// .value(), +// 2, +// ); + +// Ok(()) +// } + +// fn test_store_all(env: &TestEnvironment, storage: &Storage) -> Result<()> { +// let dir = tempfile::Builder::new() +// .prefix("docs.rs-upload-test") +// .tempdir()?; +// let files = ["Cargo.toml", "src/main.rs"]; +// for &file in &files { +// let path = dir.path().join(file); +// if let Some(parent) = path.parent() { +// fs::create_dir_all(parent)?; +// } +// fs::write(path, "data")?; +// } + +// let (stored_files, algs) = storage.store_all(Path::new("prefix"), dir.path())?; +// assert_eq!(stored_files.len(), files.len()); +// for name in &files { +// assert!(get_file_info(&stored_files, name).is_some()); +// } +// assert_eq!( +// get_file_info(&stored_files, "Cargo.toml").unwrap().mime(), +// "text/toml" +// ); +// assert_eq!( +// get_file_info(&stored_files, "src/main.rs").unwrap().mime(), +// "text/rust" +// ); + +// let file = storage.get("prefix/Cargo.toml", usize::MAX)?; +// assert_eq!(file.content, b"data"); +// assert_eq!(file.mime, "text/toml"); +// assert_eq!(file.path, "prefix/Cargo.toml"); + +// let file = storage.get("prefix/src/main.rs", usize::MAX)?; +// assert_eq!(file.content, b"data"); +// assert_eq!(file.mime, "text/rust"); +// assert_eq!(file.path, "prefix/src/main.rs"); + +// assert_eq!(algs, CompressionAlgorithm::default()); + +// let collected_metrics = env.collected_metrics(); +// assert_eq!( +// collected_metrics +// .get_metric("storage", "docsrs.storage.uploaded_files")? +// .get_u64_counter() +// .value(), +// 2, +// ); + +// Ok(()) +// } + +// fn test_batched_uploads(storage: &Storage) -> Result<()> { +// let uploads: Vec<_> = (0..=100) +// .map(|i| { +// let content = format!("const IDX: usize = {i};").as_bytes().to_vec(); +// BlobUpload { +// mime: mimes::TEXT_RUST.clone(), +// content, +// path: format!("{i}.rs"), +// compression: None, +// } +// }) +// .collect(); + +// storage.store_blobs(uploads.clone())?; + +// for blob in &uploads { +// let stored = storage.get(&blob.path, usize::MAX)?; +// assert_eq!(&stored.content, &blob.content); +// } + +// Ok(()) +// } + +// fn test_delete_prefix_without_matches(storage: &Storage) -> Result<()> { +// storage.delete_prefix("prefix_without_objects") +// } + +// fn test_delete_prefix(storage: &Storage) -> Result<()> { +// test_deletion( +// storage, +// "foo/bar/", +// &[ +// "foo.txt", +// "foo/bar.txt", +// "foo/bar/baz.txt", +// "foo/bar/foobar.txt", +// "bar.txt", +// ], +// &["foo.txt", "foo/bar.txt", "bar.txt"], +// &["foo/bar/baz.txt", "foo/bar/foobar.txt"], +// ) +// } + +// fn test_delete_percent(storage: &Storage) -> Result<()> { +// // PostgreSQL treats "%" as a special char when deleting a prefix. Make sure any "%" in the +// // provided prefix is properly escaped. +// test_deletion( +// storage, +// "foo/%/", +// &["foo/bar.txt", "foo/%/bar.txt"], +// &["foo/bar.txt"], +// &["foo/%/bar.txt"], +// ) +// } + +// fn test_deletion( +// storage: &Storage, +// prefix: &str, +// start: &[&str], +// present: &[&str], +// missing: &[&str], +// ) -> Result<()> { +// storage.store_blobs( +// start +// .iter() +// .map(|path| BlobUpload { +// path: (*path).to_string(), +// content: b"foo\n".to_vec(), +// compression: None, +// mime: mime::TEXT_PLAIN, +// }) +// .collect(), +// )?; + +// storage.delete_prefix(prefix)?; + +// for existing in present { +// assert!(storage.get(existing, usize::MAX).is_ok()); +// } +// for missing in missing { +// assert!( +// storage +// .get(missing, usize::MAX) +// .unwrap_err() +// .downcast_ref::() +// .is_some() +// ); +// } + +// Ok(()) +// } + +// // Remember to add the test name to the macro below when adding a new one. + +// macro_rules! backend_tests { +// ( +// backends { $($backend:ident => $config:expr,)* } +// tests $tests:tt +// tests_with_metrics $tests_with_metrics:tt +// ) => { +// $( +// mod $backend { +// use crate::test::TestEnvironment; +// use crate::storage::{StorageKind}; + +// fn get_env() -> anyhow::Result { +// crate::test::TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .storage_backend($config) +// .build()? +// ) +// } + +// backend_tests!(@tests $tests); +// backend_tests!(@tests_with_metrics $tests_with_metrics); +// } +// )* +// }; +// (@tests { $($test:ident,)* }) => { +// $( +// #[test] +// fn $test() -> anyhow::Result<()> { +// let env = get_env()?; +// super::$test(&*env.storage()) +// } +// )* +// }; +// (@tests_with_metrics { $($test:ident,)* }) => { +// $( +// #[test] +// fn $test() -> anyhow::Result<()> { +// let env = get_env()?; +// super::$test(&env, &*env.storage()) +// } +// )* +// }; +// } + +// backend_tests! { +// backends { +// s3 => StorageKind::S3, +// database => StorageKind::Database, +// } + +// tests { +// test_batched_uploads, +// test_exists, +// test_get_object, +// test_get_range, +// test_get_too_big, +// test_too_long_filename, +// test_list_prefix, +// test_delete_prefix, +// test_delete_prefix_without_matches, +// test_delete_percent, +// test_exists_without_remote_archive, +// } + +// tests_with_metrics { +// test_store_blobs, +// test_store_all, +// test_store_all_in_archive, +// } +// } +// } diff --git a/src/storage/s3.rs b/crates/docs_rs_storage/src/s3.rs similarity index 98% rename from src/storage/s3.rs rename to crates/docs_rs_storage/src/s3.rs index c1c84f5d7..1ae474c92 100644 --- a/src/storage/s3.rs +++ b/crates/docs_rs_storage/src/s3.rs @@ -1,5 +1,5 @@ use super::{BlobUpload, FileRange, StorageMetrics, StreamingBlob}; -use crate::{Config, web::headers::compute_etag}; +use crate::Config; use anyhow::{Context as _, Error}; use async_stream::try_stream; use aws_config::BehaviorVersion; @@ -10,8 +10,8 @@ use aws_sdk_s3::{ types::{Delete, ObjectIdentifier}, }; use aws_smithy_types_convert::date_time::DateTimeExt; -use axum_extra::headers; use chrono::Utc; +use docs_rs_headers::{ETag, etag::compute_etag}; use futures_util::{ future::TryFutureExt, pin_mut, @@ -180,7 +180,7 @@ impl S3Backend { range.end() ))) } else { - match s3_etag.parse::() { + match s3_etag.parse::() { Ok(etag) => Some(etag), Err(err) => { error!(?err, s3_etag, "Failed to parse ETag from S3"); diff --git a/crates/docs_rs_storage/src/utils/mod.rs b/crates/docs_rs_storage/src/utils/mod.rs new file mode 100644 index 000000000..055a8ccaf --- /dev/null +++ b/crates/docs_rs_storage/src/utils/mod.rs @@ -0,0 +1 @@ +pub mod sized_buffer; diff --git a/src/utils/sized_buffer.rs b/crates/docs_rs_storage/src/utils/sized_buffer.rs similarity index 90% rename from src/utils/sized_buffer.rs rename to crates/docs_rs_storage/src/utils/sized_buffer.rs index 2c71b12ea..3e7c9e968 100644 --- a/src/utils/sized_buffer.rs +++ b/crates/docs_rs_storage/src/utils/sized_buffer.rs @@ -5,20 +5,22 @@ use std::{ }; use tokio::io::AsyncWrite; -pub(crate) struct SizedBuffer { +use crate::errors::SizeLimitReached; + +pub struct SizedBuffer { inner: Vec, limit: usize, } impl SizedBuffer { - pub(crate) fn new(limit: usize) -> Self { + pub fn new(limit: usize) -> Self { SizedBuffer { inner: Vec::new(), limit, } } - pub(crate) fn reserve(&mut self, amount: usize) { + pub fn reserve(&mut self, amount: usize) { if self.inner.len() + amount > self.limit { self.inner.reserve_exact(self.limit - self.inner.len()); } else { @@ -26,7 +28,7 @@ impl SizedBuffer { } } - pub(crate) fn into_inner(self) -> Vec { + pub fn into_inner(self) -> Vec { self.inner } } @@ -34,7 +36,7 @@ impl SizedBuffer { impl Write for SizedBuffer { fn write(&mut self, buf: &[u8]) -> io::Result { if self.inner.len() + buf.len() > self.limit { - Err(io::Error::other(crate::error::SizeLimitReached)) + Err(io::Error::other(SizeLimitReached)) } else { self.inner.write(buf) } diff --git a/crates/docs_rs_utils/Cargo.toml b/crates/docs_rs_utils/Cargo.toml new file mode 100644 index 000000000..ac337eb9e --- /dev/null +++ b/crates/docs_rs_utils/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "docs_rs_utils" +version = "0.1.0" +edition = "2024" +build = "build.rs" + +[dependencies] +anyhow = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } + +[build-dependencies] +anyhow = { workspace = true } +chrono = { workspace = true } +time = "0.3" diff --git a/crates/docs_rs_utils/build.rs b/crates/docs_rs_utils/build.rs new file mode 100644 index 000000000..c6bd4cd03 --- /dev/null +++ b/crates/docs_rs_utils/build.rs @@ -0,0 +1,51 @@ +use anyhow::Result; +use std::env; + +fn main() -> Result<()> { + read_git_version()?; + Ok(()) +} + +fn read_git_version() -> Result<()> { + if let Ok(v) = env::var("GIT_SHA") { + // first try to read an externally provided git SAH, e.g., from CI + println!("cargo:rustc-env=GIT_SHA={v}"); + } else { + // then try to read the git repo. + let maybe_hash = get_git_hash()?; + let git_hash = maybe_hash.as_deref().unwrap_or("???????"); + println!("cargo:rustc-env=GIT_SHA={git_hash}"); + } + + println!( + "cargo:rustc-env=BUILD_DATE={}", + time::OffsetDateTime::now_utc().date(), + ); + + Ok(()) +} + +fn get_git_hash() -> Result> { + use std::process::Command; + + let output = Command::new("git") + .args(["rev-parse", "--short", "HEAD"]) + .output(); + + match output { + Ok(output) if output.status.success() => { + let hash = String::from_utf8(output.stdout)?.trim().to_string(); + + Ok(Some(hash)) + } + Ok(output) => { + let err = String::from_utf8_lossy(&output.stderr); + eprintln!("failed to get git repo: {}", err.trim()); + Ok(None) + } + Err(err) => { + eprintln!("failed to execute git: {err}"); + Ok(None) + } + } +} diff --git a/crates/docs_rs_utils/src/lib.rs b/crates/docs_rs_utils/src/lib.rs new file mode 100644 index 000000000..790981521 --- /dev/null +++ b/crates/docs_rs_utils/src/lib.rs @@ -0,0 +1,102 @@ +use anyhow::Result; +use std::{panic, thread, time::Duration}; +use tracing::{Span, warn}; + +pub const APP_USER_AGENT: &str = concat!( + env!("CARGO_PKG_NAME"), + " ", + " (", + env!("GIT_SHA"), + " ", + env!("BUILD_DATE"), + " )" +); + +/// a wrapper around tokio's `spawn_blocking` that +/// enables us to write nicer code when the closure +/// returns an `anyhow::Result`. +/// +/// The join-error will also be converted into an `anyhow::Error`. +/// +/// with standard `tokio::task::spawn_blocking`: +/// ```text,ignore +/// let data = spawn_blocking(move || -> anyhow::Result<_> { +/// let data = get_the_data()?; +/// Ok(data) +/// }) +/// .await +/// .context("failed to join thread")??; +/// ``` +/// +/// with this helper function: +/// ```text,ignore +/// let data = spawn_blocking(move || { +/// let data = get_the_data()?; +/// Ok(data) +/// }) +/// .await? +/// ``` +pub async fn spawn_blocking(f: F) -> Result +where + F: FnOnce() -> Result + Send + 'static, + R: Send + 'static, +{ + let span = Span::current(); + + let result = tokio::task::spawn_blocking(move || { + let _guard = span.enter(); + f() + }) + .await; + + match result { + Ok(result) => result, + Err(err) if err.is_panic() => panic::resume_unwind(err.into_panic()), + Err(err) => Err(err.into()), + } +} + +pub fn retry(mut f: impl FnMut() -> Result, max_attempts: u32) -> Result { + for attempt in 1.. { + match f() { + Ok(result) => return Ok(result), + Err(err) => { + if attempt > max_attempts { + return Err(err); + } else { + let sleep_for = 2u32.pow(attempt); + warn!( + "got error on attempt {}, will try again after {}s:\n{:?}", + attempt, sleep_for, err + ); + thread::sleep(Duration::from_secs(sleep_for as u64)); + } + } + } + } + unreachable!() +} + +pub async fn retry_async Fut>(mut f: F, max_attempts: u32) -> Result +where + Fut: Future>, +{ + for attempt in 1.. { + match f().await { + Ok(result) => return Ok(result), + Err(err) => { + if attempt > max_attempts { + return Err(err); + } else { + let sleep_for = 2u32.pow(attempt); + warn!( + "got error on attempt {}, will try again after {}s:\n{:?}", + attempt, sleep_for, err + ); + tokio::time::sleep(Duration::from_secs(sleep_for as u64)).await; + } + } + } + } + unreachable!(); +} diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index bca1a13bd..6b224b134 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -16,3 +16,15 @@ url = { workspace = true } itertools = { workspace = true } sqlx = { workspace = true } rayon = { workspace = true } +reqwest = { workspace = true } +thiserror = { workspace = true } +chrono = { workspace = true } +serde = { workspace = true } +async-trait = "0.1.83" +futures-util = { workspace = true } +regex = { workspace = true } +serde_json = { workspace = true } +docs_rs_utils = { path = "../docs_rs_utils" } + +[dev-dependencies] +mockito = { workspace = true } diff --git a/crates/docs_rs_watcher/src/config.rs b/crates/docs_rs_watcher/src/config.rs index 647ca3cc7..9a516dd18 100644 --- a/crates/docs_rs_watcher/src/config.rs +++ b/crates/docs_rs_watcher/src/config.rs @@ -4,15 +4,22 @@ use url::Url; #[derive(Debug)] pub struct Config { - pub(crate) registry_index_path: PathBuf, - pub(crate) registry_url: Option, - pub(crate) registry_api_host: Url, + pub registry_index_path: PathBuf, + pub registry_url: Option, + pub registry_api_host: Url, /// How long to wait between registry checks - pub(crate) delay_between_registry_fetches: Duration, + pub delay_between_registry_fetches: Duration, // Time between 'git gc --auto' calls in seconds - pub(crate) registry_gc_interval: u64, + pub registry_gc_interval: u64, + + // Github authentication + pub github_accesstoken: Option, + pub github_updater_min_rate_limit: u32, + + // GitLab authentication + pub gitlab_accesstoken: Option, } impl Config { @@ -30,6 +37,9 @@ impl Config { 60, )?), registry_gc_interval: env("DOCSRS_REGISTRY_GC_INTERVAL", 60 * 60)?, + github_accesstoken: maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?, + github_updater_min_rate_limit: env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500u32)?, + gitlab_accesstoken: maybe_env("DOCSRS_GITLAB_ACCESSTOKEN")?, }) } } diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index e0ef24673..93bbd0026 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -1,6 +1,7 @@ mod config; mod consistency; mod index; +mod repositories; mod utils; use anyhow::Error; diff --git a/crates/docs_rs_watcher/src/repositories/github.rs b/crates/docs_rs_watcher/src/repositories/github.rs index e37a73a20..d53fcfcab 100644 --- a/crates/docs_rs_watcher/src/repositories/github.rs +++ b/crates/docs_rs_watcher/src/repositories/github.rs @@ -1,7 +1,14 @@ -use crate::Config; -use crate::error::Result; +use crate::{ + config::Config, + repositories::{ + RateLimitReached, + updater::{FetchRepositoriesResult, Repository, RepositoryForge, RepositoryName}, + }, +}; +use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; +use docs_rs_utils::APP_USER_AGENT; use reqwest::{ Client as HttpClient, header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue, USER_AGENT}, @@ -9,13 +16,6 @@ use reqwest::{ use serde::Deserialize; use tracing::{trace, warn}; -use crate::{ - APP_USER_AGENT, - repositories::{ - FetchRepositoriesResult, RateLimitReached, Repository, RepositoryForge, RepositoryName, - }, -}; - const GRAPHQL_UPDATE: &str = "query($ids: [ID!]!) { nodes(ids: $ids) { ... on Repository { diff --git a/crates/docs_rs_watcher/src/repositories/gitlab.rs b/crates/docs_rs_watcher/src/repositories/gitlab.rs index c09c7c280..637ad4110 100644 --- a/crates/docs_rs_watcher/src/repositories/gitlab.rs +++ b/crates/docs_rs_watcher/src/repositories/gitlab.rs @@ -1,6 +1,7 @@ -use crate::error::Result; +use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; +use docs_rs_utils::APP_USER_AGENT; use reqwest::{ Client as HttpClient, header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue, USER_AGENT}, @@ -10,11 +11,9 @@ use std::collections::HashSet; use std::str::FromStr; use tracing::warn; -use crate::{ - APP_USER_AGENT, - repositories::{ - FetchRepositoriesResult, RateLimitReached, Repository, RepositoryForge, RepositoryName, - }, +use crate::repositories::{ + RateLimitReached, + updater::{FetchRepositoriesResult, Repository, RepositoryForge, RepositoryName}, }; const GRAPHQL_UPDATE: &str = "query($ids: [ID!]!) { diff --git a/crates/docs_rs_watcher/src/repositories/mod.rs b/crates/docs_rs_watcher/src/repositories/mod.rs index 2376e33cf..28a48d9ce 100644 --- a/crates/docs_rs_watcher/src/repositories/mod.rs +++ b/crates/docs_rs_watcher/src/repositories/mod.rs @@ -1,9 +1,5 @@ pub use self::github::GitHub; pub use self::gitlab::GitLab; -pub(crate) use self::updater::RepositoryName; -pub use self::updater::{ - FetchRepositoriesResult, Repository, RepositoryForge, RepositoryStatsUpdater, -}; #[derive(Debug, thiserror::Error)] #[error("rate limit reached")] diff --git a/crates/docs_rs_watcher/src/repositories/updater.rs b/crates/docs_rs_watcher/src/repositories/updater.rs index 43d29acd0..5ae873d0f 100644 --- a/crates/docs_rs_watcher/src/repositories/updater.rs +++ b/crates/docs_rs_watcher/src/repositories/updater.rs @@ -1,9 +1,11 @@ -use crate::error::Result; -use crate::repositories::{GitHub, GitLab, RateLimitReached}; -use crate::utils::MetadataPackage; -use crate::{Config, db::Pool}; +use crate::{ + config::Config, + repositories::{GitHub, GitLab, RateLimitReached}, +}; +use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; +use docs_rs_database::Pool; use futures_util::stream::TryStreamExt; use regex::Regex; use std::collections::{HashMap, HashSet}; @@ -81,7 +83,7 @@ impl RepositoryStatsUpdater { Self { updaters, pool } } - pub(crate) async fn load_repository(&self, metadata: &MetadataPackage) -> Result> { + pub async fn load_repository(&self, metadata: &MetadataPackage) -> Result> { let url = match &metadata.repository { Some(url) => url, None => { @@ -308,7 +310,7 @@ impl RepositoryStatsUpdater { } } -pub(crate) fn repository_name(url: &str) -> Option> { +pub fn repository_name(url: &str) -> Option> { static RE: LazyLock = LazyLock::new(|| { Regex::new(r"https?://(?P[^/]+)/(?P[\w\._/-]+)/(?P[\w\._-]+)").unwrap() }); diff --git a/crates/docs_rs_watcher/src/utils.rs b/crates/docs_rs_watcher/src/utils.rs index dfdc7d075..d2721ac41 100644 --- a/crates/docs_rs_watcher/src/utils.rs +++ b/crates/docs_rs_watcher/src/utils.rs @@ -19,7 +19,7 @@ use std::thread; /// /// Generally speaking, using tokio's `spawn_blocking` is also ok-ish, if the work is sporadic. /// But then I wouldn't get thread-names. -pub(crate) async fn run_blocking(name: N, f: F) -> Result +pub async fn run_blocking(name: N, f: F) -> Result where N: Into + fmt::Display, F: FnOnce() -> Result + Send + 'static, diff --git a/src/config.rs b/src/config.rs index 87fc52b4e..7958437cc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,9 +1,8 @@ -use crate::storage::StorageKind; use anyhow::{Result, bail}; use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{ io, - path::{self, Path, PathBuf}, + path::{self, PathBuf}, time::Duration, }; use url::Url; @@ -12,42 +11,6 @@ use url::Url; #[builder(pattern = "owned")] pub struct Config { pub prefix: PathBuf, - pub registry_index_path: PathBuf, - pub registry_url: Option, - pub registry_api_host: Url, - - /// How long to wait between registry checks - pub(crate) delay_between_registry_fetches: Duration, - - // Database connection params - pub(crate) database_url: String, - pub(crate) max_pool_size: u32, - pub(crate) min_pool_idle: u32, - - // Storage params - pub(crate) storage_backend: StorageKind, - - // AWS SDK configuration - pub(crate) aws_sdk_max_retries: u32, - - // S3 params - pub(crate) s3_bucket: String, - pub(crate) s3_region: String, - pub(crate) s3_endpoint: Option, - - // DO NOT CONFIGURE THIS THROUGH AN ENVIRONMENT VARIABLE! - // Accidentally turning this on outside of the test suite might cause data loss in the - // production environment. - #[cfg(test)] - #[builder(default)] - pub(crate) s3_bucket_is_temporary: bool, - - // Github authentication - pub(crate) github_accesstoken: Option, - pub(crate) github_updater_min_rate_limit: u32, - - // GitLab authentication - pub(crate) gitlab_accesstoken: Option, // Access token for APIs for crates.io (careful: use // constant_time_eq for comparisons!) @@ -60,13 +23,8 @@ pub struct Config { pub(crate) request_timeout: Option, pub(crate) report_request_timeouts: bool, - // Max size of the files served by the docs.rs frontend - pub(crate) max_file_size: usize, - pub(crate) max_file_size_html: usize, // The most memory that can be used to parse an HTML file pub(crate) max_parse_memory: usize, - // Time between 'git gc --auto' calls in seconds - pub(crate) registry_gc_interval: u64, /// amount of threads for CPU intensive rendering pub(crate) render_threads: usize, @@ -80,25 +38,6 @@ pub struct Config { // For unit-tests the number has to be higher. pub(crate) random_crate_search_view_size: u32, - // where do we want to store the locally cached index files - // for the remote archives? - pub(crate) local_archive_cache_path: PathBuf, - - // expected number of entries in the local archive cache. - // Makes server restarts faster by preallocating some data structures. - // General numbers (as of 2025-12): - // * we have ~1.5 mio releases with archive storage (and 400k without) - // * each release has on average 2 archive files (rustdoc, source) - // so, over all, 3 mio archive index files in S3. - // - // While due to crawlers we will download _all_ of them over time, the old - // metric "releases accessed in the last 10 minutes" was around 50k, if I - // recall correctly. - // We're using a local DashMap to store some locks for these indexes, - // and we already know in advance we need these 50k entries. - // So we can preallocate the DashMap with this number to avoid resizes. - pub(crate) local_archive_cache_expected_count: usize, - // Where to collect metrics for the metrics initiative. // When empty, we won't collect metrics. pub(crate) compiler_metrics_collection_path: Option, @@ -176,10 +115,6 @@ impl Config { "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", 60, )?)) - .delay_between_registry_fetches(Duration::from_secs(env::( - "DOCSRS_DELAY_BETWEEN_REGISTRY_FETCHES", - 60, - )?)) .crates_io_api_call_retries(env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?) .registry_index_path(env("REGISTRY_INDEX_PATH", prefix.join("crates.io-index"))?) .registry_url(maybe_env("REGISTRY_URL")?) @@ -192,17 +127,7 @@ impl Config { .database_url(require_env("DOCSRS_DATABASE_URL")?) .max_pool_size(env("DOCSRS_MAX_POOL_SIZE", 90u32)?) .min_pool_idle(env("DOCSRS_MIN_POOL_IDLE", 10u32)?) - .storage_backend(env("DOCSRS_STORAGE_BACKEND", StorageKind::Database)?) - .aws_sdk_max_retries(env("DOCSRS_AWS_SDK_MAX_RETRIES", 6u32)?) - .s3_bucket(env("DOCSRS_S3_BUCKET", "rust-docs-rs".to_string())?) - .s3_region(env("S3_REGION", "us-west-1".to_string())?) - .s3_endpoint(maybe_env("S3_ENDPOINT")?) - .github_accesstoken(maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?) - .github_updater_min_rate_limit(env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500u32)?) - .gitlab_accesstoken(maybe_env("DOCSRS_GITLAB_ACCESSTOKEN")?) .cratesio_token(maybe_env("DOCSRS_CRATESIO_TOKEN")?) - .max_file_size(env("DOCSRS_MAX_FILE_SIZE", 50 * 1024 * 1024)?) - .max_file_size_html(env("DOCSRS_MAX_FILE_SIZE_HTML", 50 * 1024 * 1024)?) // LOL HTML only uses as much memory as the size of the start tag! // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 .max_parse_memory(env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?) @@ -222,14 +147,6 @@ impl Config { )?) .fastly_api_token(maybe_env("DOCSRS_FASTLY_API_TOKEN")?) .fastly_service_sid(maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?) - .local_archive_cache_path(ensure_absolute_path(env( - "DOCSRS_ARCHIVE_INDEX_CACHE_PATH", - prefix.join("archive_cache"), - )?)?) - .local_archive_cache_expected_count(env( - "DOCSRS_ARCHIVE_INDEX_EXPECTED_COUNT", - 100_000usize, - )?) .compiler_metrics_collection_path(maybe_env("DOCSRS_COMPILER_METRICS_PATH")?) .temp_dir(temp_dir) .rustwide_workspace(env( @@ -250,24 +167,4 @@ impl Config { )?)) .max_queued_rebuilds(maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?)) } - - pub fn max_file_size_for(&self, path: impl AsRef) -> usize { - static HTML: &str = "html"; - - if let Some(ext) = path.as_ref().extension() - && ext == HTML - { - self.max_file_size_html - } else { - self.max_file_size - } - } -} - -fn ensure_absolute_path(path: PathBuf) -> io::Result { - if path.is_absolute() { - Ok(path) - } else { - Ok(path::absolute(&path)?) - } } diff --git a/src/context.rs b/src/context.rs index 9fc819d10..be21c6711 100644 --- a/src/context.rs +++ b/src/context.rs @@ -63,8 +63,8 @@ impl Context { let storage = Arc::new(Storage::new(async_storage.clone(), runtime.clone())); Ok(Self { - async_build_queue, - build_queue, + // async_build_queue, + // build_queue, storage, async_storage, cdn_metrics, @@ -73,7 +73,7 @@ impl Context { config.registry_api_host.clone(), config.crates_io_api_call_retries, )?), - repository_stats_updater: Arc::new(RepositoryStatsUpdater::new(&config, pool)), + // repository_stats_updater: Arc::new(RepositoryStatsUpdater::new(&config, pool)), runtime, config, meter_provider, diff --git a/src/db/mimes.rs b/src/db/mimes.rs deleted file mode 100644 index d59d25b03..000000000 --- a/src/db/mimes.rs +++ /dev/null @@ -1,20 +0,0 @@ -use mime::Mime; -use std::sync::LazyLock; - -macro_rules! mime { - ($id:ident, $mime:expr) => { - pub(crate) static $id: LazyLock = LazyLock::new(|| $mime.parse().unwrap()); - }; -} - -mime!(APPLICATION_ZIP, "application/zip"); -mime!(APPLICATION_ZSTD, "application/zstd"); -mime!(APPLICATION_GZIP, "application/gzip"); -mime!( - APPLICATION_OPENSEARCH_XML, - "application/opensearchdescription+xml" -); -mime!(APPLICATION_XML, "application/xml"); -mime!(TEXT_MARKDOWN, "text/markdown"); -mime!(TEXT_RUST, "text/rust"); -mime!(TEXT_TOML, "text/toml"); diff --git a/src/db/types/mod.rs b/src/db/types/mod.rs index e08784ada..f28d5c8d3 100644 --- a/src/db/types/mod.rs +++ b/src/db/types/mod.rs @@ -1,21 +1,8 @@ -use derive_more::{Display, FromStr}; use serde::{Deserialize, Serialize}; pub mod dependencies; pub mod krate_name; -#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] -#[sqlx(transparent)] -pub struct CrateId(pub i32); - -#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, FromStr, Serialize, sqlx::Type)] -#[sqlx(transparent)] -pub struct ReleaseId(pub i32); - -#[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] -#[sqlx(transparent)] -pub struct BuildId(pub i32); - #[derive(Debug, Clone, PartialEq, Eq, Serialize, sqlx::Type)] #[sqlx(type_name = "feature")] pub struct Feature { diff --git a/src/error.rs b/src/error.rs index 2c47bb94f..a76e2cb7f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,7 +1,3 @@ //! Errors used in docs.rs pub(crate) use anyhow::Result; - -#[derive(Debug, Copy, Clone, thiserror::Error)] -#[error("the size limit for the buffer was reached")] -pub(crate) struct SizeLimitReached; diff --git a/src/lib.rs b/src/lib.rs index 5e52e4252..c9839d811 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,16 +50,6 @@ pub const BUILD_VERSION: &str = concat!( " )" ); -pub const APP_USER_AGENT: &str = concat!( - env!("CARGO_PKG_NAME"), - " ", - " (", - env!("GIT_SHA"), - " ", - env!("BUILD_DATE"), - " )" -); - /// Where rustdoc's static files are stored in S3. /// Since the prefix starts with `/`, it needs to be referenced with a double slash in /// API & AWS CLI. diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e0f44d507..46ba8c97b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -24,10 +24,7 @@ pub(crate) mod queue_builder; pub(crate) mod rustc_version; pub(crate) mod sized_buffer; -use anyhow::Result; -use serde::{Serialize, de::DeserializeOwned}; -use std::{future::Future, panic, thread, time::Duration}; -use tracing::{Span, error, warn}; +use tracing::error; pub(crate) fn report_error(err: &anyhow::Error) { // Debug-format for anyhow errors includes context & backtrace @@ -39,195 +36,63 @@ pub(crate) fn report_error(err: &anyhow::Error) { } } -#[derive(strum::IntoStaticStr)] -#[strum(serialize_all = "snake_case")] -pub enum ConfigName { - RustcVersion, - LastSeenIndexReference, - QueueLocked, - Toolchain, -} - -pub async fn set_config( - conn: &mut sqlx::PgConnection, - name: ConfigName, - value: impl Serialize, -) -> anyhow::Result<()> { - let name: &'static str = name.into(); - sqlx::query!( - "INSERT INTO config (name, value) - VALUES ($1, $2) - ON CONFLICT (name) DO UPDATE SET value = $2;", - name, - &serde_json::to_value(value)?, - ) - .execute(conn) - .await?; - Ok(()) -} - -pub async fn get_config(conn: &mut sqlx::PgConnection, name: ConfigName) -> Result> -where - T: DeserializeOwned, -{ - let name: &'static str = name.into(); - Ok( - match sqlx::query!("SELECT value FROM config WHERE name = $1;", name) - .fetch_optional(conn) - .await? - { - Some(row) => serde_json::from_value(row.value)?, - None => None, - }, - ) -} - -/// a wrapper around tokio's `spawn_blocking` that -/// enables us to write nicer code when the closure -/// returns an `anyhow::Result`. -/// -/// The join-error will also be converted into an `anyhow::Error`. -/// -/// with standard `tokio::task::spawn_blocking`: -/// ```text,ignore -/// let data = spawn_blocking(move || -> anyhow::Result<_> { -/// let data = get_the_data()?; -/// Ok(data) -/// }) -/// .await -/// .context("failed to join thread")??; -/// ``` -/// -/// with this helper function: -/// ```text,ignore -/// let data = spawn_blocking(move || { -/// let data = get_the_data()?; -/// Ok(data) -/// }) -/// .await? -/// ``` -pub(crate) async fn spawn_blocking(f: F) -> Result -where - F: FnOnce() -> Result + Send + 'static, - R: Send + 'static, -{ - let span = Span::current(); - - let result = tokio::task::spawn_blocking(move || { - let _guard = span.enter(); - f() - }) - .await; - - match result { - Ok(result) => result, - Err(err) if err.is_panic() => panic::resume_unwind(err.into_panic()), - Err(err) => Err(err.into()), - } -} - -pub(crate) fn retry(mut f: impl FnMut() -> Result, max_attempts: u32) -> Result { - for attempt in 1.. { - match f() { - Ok(result) => return Ok(result), - Err(err) => { - if attempt > max_attempts { - return Err(err); - } else { - let sleep_for = 2u32.pow(attempt); - warn!( - "got error on attempt {}, will try again after {}s:\n{:?}", - attempt, sleep_for, err - ); - thread::sleep(Duration::from_secs(sleep_for as u64)); - } - } - } - } - unreachable!() -} - -pub(crate) async fn retry_async Fut>(mut f: F, max_attempts: u32) -> Result -where - Fut: Future>, -{ - for attempt in 1.. { - match f().await { - Ok(result) => return Ok(result), - Err(err) => { - if attempt > max_attempts { - return Err(err); - } else { - let sleep_for = 2u32.pow(attempt); - warn!( - "got error on attempt {}, will try again after {}s:\n{:?}", - attempt, sleep_for, err - ); - tokio::time::sleep(Duration::from_secs(sleep_for as u64)).await; - } - } - } - } - unreachable!(); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::async_wrapper; - use serde_json::Value; - use test_case::test_case; - - #[test_case(ConfigName::RustcVersion, "rustc_version")] - #[test_case(ConfigName::QueueLocked, "queue_locked")] - #[test_case(ConfigName::LastSeenIndexReference, "last_seen_index_reference")] - fn test_configname_variants(variant: ConfigName, expected: &'static str) { - let name: &'static str = variant.into(); - assert_eq!(name, expected); - } - - #[test] - fn test_get_config_empty() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - sqlx::query!("DELETE FROM config") - .execute(&mut *conn) - .await?; - - assert!( - get_config::(&mut conn, ConfigName::RustcVersion) - .await? - .is_none() - ); - Ok(()) - }); - } - - #[test] - fn test_set_and_get_config_() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - sqlx::query!("DELETE FROM config") - .execute(&mut *conn) - .await?; - - assert!( - get_config::(&mut conn, ConfigName::RustcVersion) - .await? - .is_none() - ); - - set_config( - &mut conn, - ConfigName::RustcVersion, - Value::String("some value".into()), - ) - .await?; - assert_eq!( - get_config(&mut conn, ConfigName::RustcVersion).await?, - Some("some value".to_string()) - ); - Ok(()) - }); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::async_wrapper; +// use serde_json::Value; +// use test_case::test_case; + +// #[test_case(ConfigName::RustcVersion, "rustc_version")] +// #[test_case(ConfigName::QueueLocked, "queue_locked")] +// #[test_case(ConfigName::LastSeenIndexReference, "last_seen_index_reference")] +// fn test_configname_variants(variant: ConfigName, expected: &'static str) { +// let name: &'static str = variant.into(); +// assert_eq!(name, expected); +// } + +// #[test] +// fn test_get_config_empty() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!("DELETE FROM config") +// .execute(&mut *conn) +// .await?; + +// assert!( +// get_config::(&mut conn, ConfigName::RustcVersion) +// .await? +// .is_none() +// ); +// Ok(()) +// }); +// } + +// #[test] +// fn test_set_and_get_config_() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!("DELETE FROM config") +// .execute(&mut *conn) +// .await?; + +// assert!( +// get_config::(&mut conn, ConfigName::RustcVersion) +// .await? +// .is_none() +// ); + +// set_config( +// &mut conn, +// ConfigName::RustcVersion, +// Value::String("some value".into()), +// ) +// .await?; +// assert_eq!( +// get_config(&mut conn, ConfigName::RustcVersion).await?, +// Some("some value".to_string()) +// ); +// Ok(()) +// }); +// } +// } diff --git a/src/web/headers/mod.rs b/src/web/headers/mod.rs index b51b292e2..7a19e4bc8 100644 --- a/src/web/headers/mod.rs +++ b/src/web/headers/mod.rs @@ -16,43 +16,3 @@ pub static SURROGATE_CONTROL: HeaderName = HeaderName::from_static("surrogate-co /// X-Robots-Tag header for search engines. pub static X_ROBOTS_TAG: HeaderName = HeaderName::from_static("x-robots-tag"); - -/// compute our etag header value from some content -/// -/// Has to match the implementation in our build-script. -pub fn compute_etag>(content: T) -> ETag { - let mut computer = ETagComputer::new(); - computer.write_all(content.as_ref()).unwrap(); - computer.finalize() -} - -/// Helper type to compute ETag values. -/// -/// Works the same way as the inner `md5::Context`, -/// but produces an `ETag` when finalized. -pub(crate) struct ETagComputer(md5::Context); - -impl ETagComputer { - pub fn new() -> Self { - Self(md5::Context::new()) - } - - pub fn consume>(&mut self, data: T) { - self.0.consume(data.as_ref()); - } - - pub fn finalize(self) -> ETag { - let digest = self.0.finalize(); - format!("\"{:x}\"", digest).parse().unwrap() - } -} - -impl io::Write for ETagComputer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} From 11ca4c2f8e78388a4a46cb985a5a632391b3fa86 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 5 Dec 2025 18:35:51 +0100 Subject: [PATCH 04/46] kaklajsdf --- Cargo.lock | 14 ++++ Cargo.toml | 3 +- crates/docs_rs_cargo_metadata/Cargo.toml | 13 ++++ .../docs_rs_cargo_metadata/src/lib.rs | 70 +++++++++---------- crates/docs_rs_watcher/Cargo.toml | 23 +++--- .../src/repositories/updater.rs | 1 + src/utils/mod.rs | 3 - 7 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 crates/docs_rs_cargo_metadata/Cargo.toml rename src/utils/cargo_metadata.rs => crates/docs_rs_cargo_metadata/src/lib.rs (73%) diff --git a/Cargo.lock b/Cargo.lock index c449fb4a8..634b8a308 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2037,6 +2037,19 @@ dependencies = [ "tracing", ] +[[package]] +name = "docs_rs_cargo_metadata" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode 2.0.1", + "docs_rs_database", + "rustwide", + "semver", + "serde", + "serde_json", +] + [[package]] name = "docs_rs_database" version = "0.1.0" @@ -2150,6 +2163,7 @@ dependencies = [ "crates-index", "crates-index-diff", "docs_rs_build_queue", + "docs_rs_cargo_metadata", "docs_rs_database", "docs_rs_env_vars", "docs_rs_utils", diff --git a/Cargo.toml b/Cargo.toml index b86ebdc78..e88700c4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ async-stream = "0.3.5" tempfile = "3.1.0" http = "1.0.0" reqwest = { version = "0.12", features = ["json", "gzip"] } +rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } # dev dependencies test-case = "3.0.0" @@ -87,7 +88,7 @@ phf = "0.13.1" rayon = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } -rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } +rustwide = { workspace = true } semver = { workspace = true } sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } serde = { workspace = true } diff --git a/crates/docs_rs_cargo_metadata/Cargo.toml b/crates/docs_rs_cargo_metadata/Cargo.toml new file mode 100644 index 000000000..0b6c68073 --- /dev/null +++ b/crates/docs_rs_cargo_metadata/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "docs_rs_cargo_metadata" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +bincode = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } +rustwide = { workspace = true } +semver = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } diff --git a/src/utils/cargo_metadata.rs b/crates/docs_rs_cargo_metadata/src/lib.rs similarity index 73% rename from src/utils/cargo_metadata.rs rename to crates/docs_rs_cargo_metadata/src/lib.rs index 370dccd13..3409e610c 100644 --- a/src/utils/cargo_metadata.rs +++ b/crates/docs_rs_cargo_metadata/src/lib.rs @@ -1,17 +1,17 @@ -use crate::{db::types::version::Version, error::Result}; -use anyhow::{Context, bail}; +use anyhow::{Context, Result, bail}; +use docs_rs_database::types::version::Version; use rustwide::{Toolchain, Workspace, cmd::Command}; use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::path::Path; -pub(crate) struct CargoMetadata { +pub struct CargoMetadata { root: Package, } impl CargoMetadata { - pub(crate) fn load_from_rustwide( + pub fn load_from_rustwide( workspace: &Workspace, toolchain: &Toolchain, source_dir: &Path, @@ -28,7 +28,7 @@ impl CargoMetadata { } #[cfg(test)] - pub(crate) fn load_from_host_path(source_dir: &Path) -> Result { + pub fn load_from_host_path(source_dir: &Path) -> Result { let res = std::process::Command::new("cargo") .args(["metadata", "--format-version", "1", "--offline"]) .current_dir(source_dir) @@ -41,7 +41,7 @@ impl CargoMetadata { Self::load_from_metadata(std::str::from_utf8(&res.stdout)?) } - pub(crate) fn load_from_metadata(metadata: &str) -> Result { + pub fn load_from_metadata(metadata: &str) -> Result { let metadata = serde_json::from_str::(metadata)?; let root = metadata.resolve.root; Ok(CargoMetadata { @@ -53,26 +53,26 @@ impl CargoMetadata { }) } - pub(crate) fn root(&self) -> &Package { + pub fn root(&self) -> &Package { &self.root } } #[derive(Debug, Deserialize, Serialize)] -pub(crate) struct Package { - pub(crate) id: String, - pub(crate) name: String, - pub(crate) version: Version, - pub(crate) license: Option, - pub(crate) repository: Option, - pub(crate) homepage: Option, - pub(crate) description: Option, - pub(crate) documentation: Option, - pub(crate) dependencies: Vec, - pub(crate) targets: Vec, - pub(crate) readme: Option, - pub(crate) keywords: Vec, - pub(crate) features: HashMap>, +pub struct Package { + pub id: String, + pub name: String, + pub version: Version, + pub license: Option, + pub repository: Option, + pub homepage: Option, + pub description: Option, + pub documentation: Option, + pub dependencies: Vec, + pub targets: Vec, + pub readme: Option, + pub keywords: Vec, + pub features: HashMap>, } impl Package { @@ -82,7 +82,7 @@ impl Package { .find(|target| target.crate_types.iter().any(|kind| kind != "bin")) } - pub(crate) fn is_library(&self) -> bool { + pub fn is_library(&self) -> bool { self.library_target().is_some() } @@ -90,7 +90,7 @@ impl Package { name.replace('-', "_") } - pub(crate) fn package_name(&self) -> String { + pub fn package_name(&self) -> String { self.library_name().unwrap_or_else(|| { self.targets .first() @@ -99,25 +99,25 @@ impl Package { }) } - pub(crate) fn library_name(&self) -> Option { + pub fn library_name(&self) -> Option { self.library_target() .map(|target| self.normalize_package_name(&target.name)) } } #[derive(Debug, Deserialize, Serialize)] -pub(crate) struct Target { - pub(crate) name: String, +pub struct Target { + pub name: String, #[cfg(not(test))] crate_types: Vec, #[cfg(test)] - pub(crate) crate_types: Vec, - pub(crate) src_path: Option, + pub crate_types: Vec, + pub src_path: Option, } impl Target { #[cfg(test)] - pub(crate) fn dummy_lib(name: String, src_path: Option) -> Self { + pub fn dummy_lib(name: String, src_path: Option) -> Self { Target { name, crate_types: vec!["lib".into()], @@ -127,12 +127,12 @@ impl Target { } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq)] -pub(crate) struct Dependency { - pub(crate) name: String, - pub(crate) req: VersionReq, - pub(crate) kind: Option, - pub(crate) rename: Option, - pub(crate) optional: bool, +pub struct Dependency { + pub name: String, + pub req: VersionReq, + pub kind: Option, + pub rename: Option, + pub optional: bool, } impl bincode::Encode for Dependency { diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index 6b224b134..862fff58a 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -5,26 +5,27 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +async-trait = "0.1.83" +chrono = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} -docs_rs_database = { path = "../docs_rs_database" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } +docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } +docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } -tokio = { workspace = true } -tracing = { workspace = true } -url = { workspace = true } +docs_rs_utils = { path = "../docs_rs_utils" } +futures-util = { workspace = true } itertools = { workspace = true } -sqlx = { workspace = true } rayon = { workspace = true } +regex = { workspace = true } reqwest = { workspace = true } -thiserror = { workspace = true } -chrono = { workspace = true } serde = { workspace = true } -async-trait = "0.1.83" -futures-util = { workspace = true } -regex = { workspace = true } serde_json = { workspace = true } -docs_rs_utils = { path = "../docs_rs_utils" } +sqlx = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } +url = { workspace = true } [dev-dependencies] mockito = { workspace = true } diff --git a/crates/docs_rs_watcher/src/repositories/updater.rs b/crates/docs_rs_watcher/src/repositories/updater.rs index 5ae873d0f..08e1e5d98 100644 --- a/crates/docs_rs_watcher/src/repositories/updater.rs +++ b/crates/docs_rs_watcher/src/repositories/updater.rs @@ -5,6 +5,7 @@ use crate::{ use anyhow::Result; use async_trait::async_trait; use chrono::{DateTime, Utc}; +use docs_rs_cargo_metadata::Package as MetadataPackage; use docs_rs_database::Pool; use futures_util::stream::TryStreamExt; use regex::Regex; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 46ba8c97b..e98122938 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,6 @@ //! Various utilities for docs.rs pub(crate) use self::{ - cargo_metadata::{CargoMetadata, Dependency, Package as MetadataPackage}, copy::copy_dir_all, html::rewrite_rustdoc_html_stream, rustc_version::{get_correct_docsrs_style_file, parse_rustc_version}, @@ -15,14 +14,12 @@ pub use self::{ queue_builder::queue_builder, }; -pub(crate) mod cargo_metadata; mod copy; pub mod daemon; mod html; mod queue; pub(crate) mod queue_builder; pub(crate) mod rustc_version; -pub(crate) mod sized_buffer; use tracing::error; From 8f2f5353490a3551d162a76a1f2b0889e458608d Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 5 Dec 2025 18:54:54 +0100 Subject: [PATCH 05/46] kk --- Cargo.lock | 1 + Cargo.toml | 1 + crates/docs_rs_utils/src/lib.rs | 11 +++++++++++ src/cdn/fastly.rs | 1 - src/context.rs | 2 +- src/db/mod.rs | 2 -- src/lib.rs | 13 ------------- 7 files changed, 14 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 634b8a308..7a12be961 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1962,6 +1962,7 @@ dependencies = [ "docs_rs_database", "docs_rs_env_vars", "docs_rs_opentelemetry", + "docs_rs_utils", "docsrs-metadata", "fn-error-context", "font-awesome-as-a-crate", diff --git a/Cargo.toml b/Cargo.toml index e88700c4f..d55e376bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,7 @@ constant_time_eq = "0.4.2" derive_builder = "0.20.2" derive_more = { workspace = true } docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } +docs_rs_utils = { path = "crates/docs_rs_utils" } docs_rs_database = { path = "crates/docs_rs_database" } docs_rs_build_queue = { path = "crates/docs_rs_build_queue" } docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } diff --git a/crates/docs_rs_utils/src/lib.rs b/crates/docs_rs_utils/src/lib.rs index 790981521..ecab125f6 100644 --- a/crates/docs_rs_utils/src/lib.rs +++ b/crates/docs_rs_utils/src/lib.rs @@ -2,6 +2,17 @@ use anyhow::Result; use std::{panic, thread, time::Duration}; use tracing::{Span, warn}; +/// Version string generated at build time contains last git +/// commit hash and build date +pub const BUILD_VERSION: &str = concat!( + env!("CARGO_PKG_VERSION"), + " (", + env!("GIT_SHA"), + " ", + env!("BUILD_DATE"), + " )" +); + pub const APP_USER_AGENT: &str = concat!( env!("CARGO_PKG_NAME"), " ", diff --git a/src/cdn/fastly.rs b/src/cdn/fastly.rs index f78cde3b0..5a1b33b17 100644 --- a/src/cdn/fastly.rs +++ b/src/cdn/fastly.rs @@ -1,5 +1,4 @@ use crate::{ - APP_USER_AGENT, cdn::CdnMetrics, config::Config, web::headers::{SURROGATE_KEY, SurrogateKey, SurrogateKeys}, diff --git a/src/context.rs b/src/context.rs index be21c6711..3cdd8f7c2 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use crate::{AsyncStorage, Config, RegistryApi, Storage, cdn::CdnMetrics}; +use crate::{Config, RegistryApi, cdn::CdnMetrics}; use anyhow::Result; use docs_rs_database::Pool; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; diff --git a/src/db/mod.rs b/src/db/mod.rs index 61d33a6da..c74157247 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -18,8 +18,6 @@ pub use self::{ mod add_package; pub mod blacklist; pub mod delete; -pub(crate) mod file; -pub(crate) mod mimes; mod overrides; pub mod types; diff --git a/src/lib.rs b/src/lib.rs index c9839d811..9c2b27cd7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,6 @@ pub use self::context::Context; pub use self::docbuilder::PackageKind; pub use self::docbuilder::{BuildPackageSummary, RustwideBuilder}; pub use self::registry_api::RegistryApi; -pub use self::storage::{AsyncStorage, Storage}; pub use self::web::start_web_server; pub use font_awesome_as_a_crate::icons; @@ -20,7 +19,6 @@ mod docbuilder; mod error; pub mod metrics; mod registry_api; -pub mod storage; #[cfg(test)] mod test; pub mod utils; @@ -39,17 +37,6 @@ pub(crate) static GLOBAL_ALERT: Option = Some(GlobalAlert { }); */ -/// Version string generated at build time contains last git -/// commit hash and build date -pub const BUILD_VERSION: &str = concat!( - env!("CARGO_PKG_VERSION"), - " (", - env!("GIT_SHA"), - " ", - env!("BUILD_DATE"), - " )" -); - /// Where rustdoc's static files are stored in S3. /// Since the prefix starts with `/`, it needs to be referenced with a double slash in /// API & AWS CLI. From 41152fed5a7f6d19ff86ca5b356ebe9181904dc0 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 5 Dec 2025 20:19:38 +0100 Subject: [PATCH 06/46] kk --- crates/docs_rs_watcher/src/repositories/github.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/crates/docs_rs_watcher/src/repositories/github.rs b/crates/docs_rs_watcher/src/repositories/github.rs index d53fcfcab..19241cd64 100644 --- a/crates/docs_rs_watcher/src/repositories/github.rs +++ b/crates/docs_rs_watcher/src/repositories/github.rs @@ -271,16 +271,14 @@ mod tests { use super::{Config, GitHub}; use crate::repositories::RateLimitReached; use crate::repositories::updater::{RepositoryForge, repository_name}; - use crate::test::TestEnvironment; use anyhow::Result; const TEST_TOKEN: &str = "qsjdnfqdq"; fn github_config() -> anyhow::Result { - TestEnvironment::base_config() - .github_accesstoken(Some(TEST_TOKEN.to_owned())) - .build() - .map_err(Into::into) + let mut cfg = Config::from_environment()?; + cfg.github_accesstoken = Some(TEST_TOKEN.to_owned()); + Ok(cfg) } async fn mock_server_and_github(config: &Config) -> (mockito::ServerGuard, GitHub) { From 7cf67d6c22e3baa69373b2026426893e4c3cbfea Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sun, 7 Dec 2025 07:46:09 +0100 Subject: [PATCH 07/46] WIP --- Cargo.lock | 3 ++ Cargo.toml | 3 ++ crates/docs_rs_storage/src/database.rs | 4 +-- crates/docs_rs_storage/src/errors.rs | 4 +++ crates/docs_rs_storage/src/lib.rs | 12 +++----- crates/docs_rs_storage/src/s3.rs | 6 ++-- src/cdn/fastly.rs | 1 + src/context.rs | 2 ++ src/db/add_package.rs | 11 ++++---- src/db/delete.rs | 11 +++----- src/db/mod.rs | 2 -- src/db/types/dependencies.rs | 2 +- src/docbuilder/rustwide_builder.rs | 38 ++++++++++++++------------ src/registry_api.rs | 5 ++-- src/utils/queue.rs | 2 +- src/web/build_details.rs | 8 ++++-- src/web/builds.rs | 10 +++---- src/web/crate_details.rs | 15 ++++------ src/web/error.rs | 8 ++---- src/web/extractors/context.rs | 6 ++-- src/web/extractors/path.rs | 6 ++-- src/web/extractors/rustdoc.rs | 5 ++-- src/web/file.rs | 7 ++--- src/web/metrics.rs | 3 +- src/web/mod.rs | 8 ++---- src/web/releases.rs | 8 ++---- src/web/rustdoc.rs | 25 ++++++++++------- src/web/sitemap.rs | 7 +++-- src/web/source.rs | 12 ++++---- src/web/statics.rs | 2 +- templates/base.html | 4 +-- templates/core/about/index.html | 2 +- templates/crate/source.html | 2 +- templates/releases/feed.xml | 2 +- templates/rustdoc/body.html | 2 +- templates/rustdoc/head.html | 2 +- templates/rustdoc/vendored.html | 2 +- 37 files changed, 127 insertions(+), 125 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7a12be961..45aba3d25 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1959,9 +1959,12 @@ dependencies = [ "derive_builder", "derive_more 2.0.1", "docs_rs_build_queue", + "docs_rs_cargo_metadata", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_headers", "docs_rs_opentelemetry", + "docs_rs_storage", "docs_rs_utils", "docsrs-metadata", "fn-error-context", diff --git a/Cargo.toml b/Cargo.toml index d55e376bf..dc4481220 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,9 @@ docs_rs_utils = { path = "crates/docs_rs_utils" } docs_rs_database = { path = "crates/docs_rs_database" } docs_rs_build_queue = { path = "crates/docs_rs_build_queue" } docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } +docs_rs_cargo_metadata = { path = "crates/docs_rs_cargo_metadata" } +docs_rs_storage = { path = "crates/docs_rs_storage" } +docs_rs_headers = { path = "crates/docs_rs_headers" } docsrs-metadata = { path = "crates/metadata" } fn-error-context = "0.2.0" font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } diff --git a/crates/docs_rs_storage/src/database.rs b/crates/docs_rs_storage/src/database.rs index 216249756..8cc29c05a 100644 --- a/crates/docs_rs_storage/src/database.rs +++ b/crates/docs_rs_storage/src/database.rs @@ -55,7 +55,7 @@ impl DatabaseBackend { ) .fetch_optional(&self.pool) .await? - .ok_or(super::PathNotFoundError)? + .ok_or(super::errors::PathNotFoundError)? } else { // The size limit is checked at the database level, to avoid receiving data altogether if // the limit is exceeded. @@ -73,7 +73,7 @@ impl DatabaseBackend { ) .fetch_optional(&self.pool) .await? - .ok_or(super::PathNotFoundError)? + .ok_or(super::errors::PathNotFoundError)? }; let compression = result.compression.map(|i| { diff --git a/crates/docs_rs_storage/src/errors.rs b/crates/docs_rs_storage/src/errors.rs index 2dc94ac14..d8413f2a7 100644 --- a/crates/docs_rs_storage/src/errors.rs +++ b/crates/docs_rs_storage/src/errors.rs @@ -1,3 +1,7 @@ #[derive(Debug, Copy, Clone, thiserror::Error)] #[error("the size limit for the buffer was reached")] pub struct SizeLimitReached; + +#[derive(Debug, thiserror::Error)] +#[error("path not found")] +pub struct PathNotFoundError; diff --git a/crates/docs_rs_storage/src/lib.rs b/crates/docs_rs_storage/src/lib.rs index 731861326..7ed00891a 100644 --- a/crates/docs_rs_storage/src/lib.rs +++ b/crates/docs_rs_storage/src/lib.rs @@ -2,8 +2,8 @@ mod archive_index; pub mod compression; mod config; mod database; -mod errors; -mod file; +pub mod errors; +pub mod file; mod s3; mod utils; @@ -54,10 +54,6 @@ const ARCHIVE_INDEX_FILE_EXTENSION: &str = "index"; type FileRange = RangeInclusive; -#[derive(Debug, thiserror::Error)] -#[error("path not found")] -pub struct PathNotFoundError; - /// represents a blob to be uploaded to storage. #[derive(Clone, Debug, PartialEq, Eq)] pub struct BlobUpload { @@ -387,7 +383,7 @@ impl AsyncStorage { { Ok(file_info) => Ok(file_info.is_some()), Err(err) => { - if err.downcast_ref::().is_some() { + if err.downcast_ref::().is_some() { Ok(false) } else { Err(err) @@ -630,7 +626,7 @@ impl AsyncStorage { let info = self .find_in_archive_index(archive_path, latest_build_id, path) .await? - .ok_or(PathNotFoundError)?; + .ok_or(errors::PathNotFoundError)?; match self .get_range_stream(archive_path, info.range(), Some(info.compression())) diff --git a/crates/docs_rs_storage/src/s3.rs b/crates/docs_rs_storage/src/s3.rs index 1ae474c92..d25d5c4c2 100644 --- a/crates/docs_rs_storage/src/s3.rs +++ b/crates/docs_rs_storage/src/s3.rs @@ -51,13 +51,13 @@ where if let Some(err_code) = err.code() && NOT_FOUND_ERROR_CODES.contains(&err_code) { - return Err(super::PathNotFoundError.into()); + return Err(super::errors::PathNotFoundError.into()); } if let SdkError::ServiceError(err) = &err && err.raw().status().as_u16() == http::StatusCode::NOT_FOUND.as_u16() { - return Err(super::PathNotFoundError.into()); + return Err(super::errors::PathNotFoundError.into()); } Err(err.into()) @@ -123,7 +123,7 @@ impl S3Backend { .convert_errors() { Ok(_) => Ok(true), - Err(err) if err.is::() => Ok(false), + Err(err) if err.is::() => Ok(false), Err(other) => Err(other), } } diff --git a/src/cdn/fastly.rs b/src/cdn/fastly.rs index 5a1b33b17..4eb063326 100644 --- a/src/cdn/fastly.rs +++ b/src/cdn/fastly.rs @@ -5,6 +5,7 @@ use crate::{ }; use anyhow::{Result, anyhow, bail}; use chrono::{DateTime, TimeZone as _, Utc}; +use docs_rs_utils::APP_USER_AGENT; use http::{ HeaderMap, HeaderName, HeaderValue, header::{ACCEPT, USER_AGENT}, diff --git a/src/context.rs b/src/context.rs index 3cdd8f7c2..f625dbe4a 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,7 +1,9 @@ use crate::{Config, RegistryApi, cdn::CdnMetrics}; use anyhow::Result; +use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::Pool; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; +use docs_rs_storage::{AsyncStorage, Storage}; use std::sync::Arc; use tokio::runtime; diff --git a/src/db/add_package.rs b/src/db/add_package.rs index 4f97c0e2b..3f6ad421c 100644 --- a/src/db/add_package.rs +++ b/src/db/add_package.rs @@ -1,16 +1,15 @@ use crate::{ - db::types::{ - BuildId, BuildStatus, CrateId, Feature, ReleaseId, dependencies::ReleaseDependencyList, - version::Version, - }, + db::types::{BuildStatus, Feature, dependencies::ReleaseDependencyList}, docbuilder::DocCoverage, error::Result, registry_api::{CrateData, CrateOwner, ReleaseData}, - storage::CompressionAlgorithm, - utils::{MetadataPackage, rustc_version::parse_rustc_date}, + utils::rustc_version::parse_rustc_date, web::crate_details::{latest_release, releases_for_crate}, }; use anyhow::{Context, anyhow}; +use docs_rs_cargo_metadata::Package as MetadataPackage; +use docs_rs_database::types::{BuildId, CrateId, ReleaseId, version::Version}; +use docs_rs_storage::CompressionAlgorithm; use futures_util::stream::TryStreamExt; use serde_json::Value; use slug::slugify; diff --git a/src/db/delete.rs b/src/db/delete.rs index 14efcf917..e3fc07f73 100644 --- a/src/db/delete.rs +++ b/src/db/delete.rs @@ -1,14 +1,11 @@ -use crate::{ - Config, - db::types::version::Version, - error::Result, - storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}, -}; +use crate::{Config, error::Result}; use anyhow::Context as _; +use docs_rs_database::types::{CrateId, version::Version}; +use docs_rs_storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}; use fn_error_context::context; use sqlx::Connection; -use super::{CrateId, update_latest_version_id}; +use super::update_latest_version_id; /// List of directories in docs.rs's underlying storage (either the database or S3) containing a /// subdirectory named after the crate. Those subdirectories will be deleted. diff --git a/src/db/mod.rs b/src/db/mod.rs index c74157247..6a9367da8 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -10,9 +10,7 @@ pub(crate) use self::add_package::{ pub use self::{ add_package::{update_build_status, update_crate_data_in_database}, delete::{delete_crate, delete_version}, - file::{add_path_into_database, add_path_into_remote_archive}, overrides::Overrides, - types::{BuildId, CrateId, ReleaseId}, }; mod add_package; diff --git a/src/db/types/dependencies.rs b/src/db/types/dependencies.rs index c80c8d55f..966b13479 100644 --- a/src/db/types/dependencies.rs +++ b/src/db/types/dependencies.rs @@ -1,5 +1,5 @@ -use crate::utils::Dependency; use derive_more::Deref; +use docs_rs_cargo_metadata::Dependency; use semver::VersionReq; use serde::{Deserialize, Serialize}; diff --git a/src/docbuilder/rustwide_builder.rs b/src/docbuilder/rustwide_builder.rs index cd068992f..2d2109e07 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/src/docbuilder/rustwide_builder.rs @@ -1,28 +1,30 @@ use crate::{ - AsyncStorage, Config, Context, RUSTDOC_STATIC_STORAGE_PREFIX, RegistryApi, Storage, + Config, Context, RUSTDOC_STATIC_STORAGE_PREFIX, RegistryApi, db::{ - BuildId, CrateId, ReleaseId, add_doc_coverage, add_path_into_remote_archive, - blacklist::is_blacklisted, - file::{add_path_into_database, file_list_to_json}, - finish_build, finish_release, initialize_build, initialize_crate, initialize_release, - types::BuildStatus, + add_doc_coverage, blacklist::is_blacklisted, finish_build, finish_release, + initialize_build, initialize_crate, initialize_release, types::BuildStatus, update_build_with_error, update_crate_data_in_database, }, docbuilder::Limits, error::Result, metrics::{BUILD_TIME_HISTOGRAM_BUCKETS, DOCUMENTATION_SIZE_BUCKETS}, - storage::{ - CompressionAlgorithm, RustdocJsonFormatVersion, compress, get_file_list, - rustdoc_archive_path, rustdoc_json_path, source_archive_path, - }, - utils::{ - CargoMetadata, ConfigName, MetadataPackage, copy_dir_all, get_config, parse_rustc_version, - report_error, retry, set_config, - }, + utils::{copy_dir_all, parse_rustc_version, report_error}, }; use anyhow::{Context as _, Error, anyhow, bail}; -use docs_rs_database::{Pool, types::version::Version}; +use docs_rs_cargo_metadata::{CargoMetadata, Package as MetadataPackage}; +use docs_rs_database::{ + Pool, + service_config::{ConfigName, get_config, set_config}, + types::{BuildId, CrateId, ReleaseId, version::Version}, +}; use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_storage::{ + AsyncStorage, RustdocJsonFormatVersion, Storage, compress, + compression::CompressionAlgorithm, + file::{add_path_into_database, add_path_into_remote_archive, file_list_to_json}, + get_file_list, rustdoc_archive_path, rustdoc_json_path, source_archive_path, +}; +use docs_rs_utils::{BUILD_VERSION, retry}; use docsrs_metadata::{BuildTargets, DEFAULT_TARGETS, HOST_TARGET, Metadata}; use itertools::Itertools as _; use opentelemetry::metrics::{Counter, Histogram}; @@ -191,9 +193,9 @@ impl RustwideBuilder { storage: context.storage.clone(), async_storage: context.async_storage.clone(), registry_api: context.registry_api.clone(), - repository_stats_updater: context.repository_stats_updater.clone(), + // repository_stats_updater: context.repository_stats_updater.clone(), workspace_initialize_time: Instant::now(), - builder_metrics: context.async_build_queue.builder_metrics(), + // builder_metrics: context.async_build_queue.builder_metrics(), }) } @@ -1248,7 +1250,7 @@ impl RustwideBuilder { Ok(FullBuildResult { result: BuildResult { rustc_version: self.rustc_version()?, - docsrs_version: format!("docsrs {}", crate::BUILD_VERSION), + docsrs_version: format!("docsrs {}", BUILD_VERSION), successful, }, doc_coverage, diff --git a/src/registry_api.rs b/src/registry_api.rs index d3371d068..7b1e0871a 100644 --- a/src/registry_api.rs +++ b/src/registry_api.rs @@ -1,6 +1,7 @@ -use crate::{APP_USER_AGENT, db::types::version::Version, error::Result, utils::retry_async}; -use anyhow::{Context, anyhow, bail}; +use anyhow::{Context, Result, anyhow, bail}; use chrono::{DateTime, Utc}; +use docs_rs_database::types::version::Version; +use docs_rs_utils::{APP_USER_AGENT, retry_async}; use reqwest::header::{ACCEPT, HeaderValue, USER_AGENT}; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/src/utils/queue.rs b/src/utils/queue.rs index 92d697ed6..6c246fffa 100644 --- a/src/utils/queue.rs +++ b/src/utils/queue.rs @@ -1,6 +1,6 @@ //! Utilities for interacting with the build queue -use crate::build_queue::PRIORITY_DEFAULT; use crate::error::Result; +use docs_rs_build_queue::PRIORITY_DEFAULT; use futures_util::stream::TryStreamExt; /// Get the build queue priority for a crate, returns the matching pattern too diff --git a/src/web/build_details.rs b/src/web/build_details.rs index 25e81824d..2a6a84317 100644 --- a/src/web/build_details.rs +++ b/src/web/build_details.rs @@ -1,6 +1,6 @@ use crate::{ - AsyncStorage, Config, - db::{BuildId, types::BuildStatus}, + Config, + db::types::BuildStatus, impl_axum_webpage, web::{ MetaData, @@ -16,6 +16,10 @@ use anyhow::Context as _; use askama::Template; use axum::{extract::Extension, response::IntoResponse}; use chrono::{DateTime, Utc}; +use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; +use docs_rs_database::types::{BuildId, version::Version}; +use docs_rs_storage::AsyncStorage; +use docs_rs_utils::BUILD_VERSION; use futures_util::TryStreamExt; use serde::Deserialize; use std::sync::Arc; diff --git a/src/web/builds.rs b/src/web/builds.rs index b400aca3a..40d9ad279 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -1,10 +1,6 @@ -use crate::build_queue::PRIORITY_MANUAL_FROM_CRATES_IO; use crate::{ - AsyncBuildQueue, Config, - db::{ - BuildId, - types::{BuildStatus, version::Version}, - }, + Config, + db::types::BuildStatus, docbuilder::Limits, impl_axum_webpage, web::{ @@ -27,6 +23,8 @@ use axum_extra::{ }; use chrono::{DateTime, Utc}; use constant_time_eq::constant_time_eq; +use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; +use docs_rs_database::types::{BuildId, version::Version}; use http::StatusCode; use std::sync::Arc; diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 6822c51d9..85408d3dc 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -1,16 +1,8 @@ use crate::{ - AsyncStorage, - db::{ - BuildId, CrateId, ReleaseId, - types::{ - BuildStatus, dependencies::ReleaseDependencyList, krate_name::KrateName, - version::Version, - }, - }, + db::types::{BuildStatus, dependencies::ReleaseDependencyList, krate_name::KrateName}, impl_axum_webpage, registry_api::OwnerKind, - storage::PathNotFoundError, - utils::{Dependency, get_correct_docsrs_style_file}, + utils::get_correct_docsrs_style_file, web::{ MatchedRelease, MetaData, ReqVersion, cache::CachePolicy, @@ -31,6 +23,9 @@ use axum::{ response::{IntoResponse, Response as AxumResponse}, }; use chrono::{DateTime, Utc}; +use docs_rs_cargo_metadata::Dependency; +use docs_rs_database::types::{BuildId, CrateId, ReleaseId, version::Version}; +use docs_rs_storage::{AsyncStorage, errors::PathNotFoundError}; use futures_util::stream::TryStreamExt; use log::warn; use serde_json::Value; diff --git a/src/web/error.rs b/src/web/error.rs index 70dec0844..6946409d9 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -1,14 +1,12 @@ -use crate::{ - db::PoolError, - storage::PathNotFoundError, - web::{AxumErrorPage, cache::CachePolicy, escaped_uri::EscapedURI, releases::Search}, -}; +use crate::web::{AxumErrorPage, cache::CachePolicy, escaped_uri::EscapedURI, releases::Search}; use anyhow::{Result, anyhow}; use axum::{ Json, http::StatusCode, response::{IntoResponse, Response as AxumResponse}, }; +use docs_rs_database::PoolError; +use docs_rs_storage::errors::PathNotFoundError; use std::borrow::Cow; use tracing::error; diff --git a/src/web/extractors/context.rs b/src/web/extractors/context.rs index a0594c04b..3e7675646 100644 --- a/src/web/extractors/context.rs +++ b/src/web/extractors/context.rs @@ -1,15 +1,13 @@ //! a collection of custom extractors related to our app-context (context::Context) -use crate::{ - db::{AsyncPoolClient, Pool}, - web::error::AxumNope, -}; +use crate::web::error::AxumNope; use anyhow::Context as _; use axum::{ RequestPartsExt, extract::{Extension, FromRequestParts}, http::request::Parts, }; +use docs_rs_database::{AsyncPoolClient, Pool}; use std::ops::{Deref, DerefMut}; /// Extractor for a async sqlx database connection. diff --git a/src/web/extractors/path.rs b/src/web/extractors/path.rs index 44a9a47aa..dfa2b4fc4 100644 --- a/src/web/extractors/path.rs +++ b/src/web/extractors/path.rs @@ -1,8 +1,5 @@ //! custom axum extractors for path parameters -use crate::{ - storage::{CompressionAlgorithm, compression::compression_from_file_extension}, - web::error::AxumNope, -}; +use crate::web::error::AxumNope; use anyhow::anyhow; use axum::{ RequestPartsExt, @@ -10,6 +7,7 @@ use axum::{ http::request::Parts, }; use derive_more::Deref; +use docs_rs_storage::{CompressionAlgorithm, compression::compression_from_file_extension}; /// custom axum `Path` extractor that uses our own AxumNope::BadRequest /// as error response instead of a plain text "bad request" diff --git a/src/web/extractors/rustdoc.rs b/src/web/extractors/rustdoc.rs index fe430cb5e..e839d8b2f 100644 --- a/src/web/extractors/rustdoc.rs +++ b/src/web/extractors/rustdoc.rs @@ -1,8 +1,7 @@ //! special rustdoc extractors use crate::{ - db::{BuildId, types::krate_name::KrateName}, - storage::CompressionAlgorithm, + db::types::krate_name::KrateName, web::{ MatchedRelease, MetaData, ReqVersion, error::AxumNope, escaped_uri::EscapedURI, extractors::Path, url_decode, @@ -14,6 +13,8 @@ use axum::{ extract::{FromRequestParts, MatchedPath}, http::{Uri, request::Parts}, }; +use docs_rs_database::types::BuildId; +use docs_rs_storage::CompressionAlgorithm; use itertools::Itertools as _; use serde::Deserialize; diff --git a/src/web/file.rs b/src/web/file.rs index 915be15f6..d03031d76 100644 --- a/src/web/file.rs +++ b/src/web/file.rs @@ -1,11 +1,7 @@ //! Database based file handler use super::{cache::CachePolicy, headers::IfNoneMatch}; -use crate::{ - Config, - error::Result, - storage::{AsyncStorage, Blob, StreamingBlob}, -}; +use crate::{Config, error::Result}; use axum::{ body::Body, extract::Extension, @@ -16,6 +12,7 @@ use axum_extra::{ TypedHeader, headers::{ContentType, LastModified}, }; +use docs_rs_storage::{AsyncStorage, Blob, StreamingBlob}; use std::time::SystemTime; use tokio_util::io::ReaderStream; diff --git a/src/web/metrics.rs b/src/web/metrics.rs index d42603af9..d1dc67020 100644 --- a/src/web/metrics.rs +++ b/src/web/metrics.rs @@ -1,10 +1,11 @@ -use crate::metrics::{RESPONSE_TIME_HISTOGRAM_BUCKETS, otel::AnyMeterProvider}; +use crate::metrics::RESPONSE_TIME_HISTOGRAM_BUCKETS; use axum::{ extract::{MatchedPath, Request as AxumRequest}, http::StatusCode, middleware::Next, response::IntoResponse, }; +use docs_rs_opentelemetry::AnyMeterProvider; use opentelemetry::{ KeyValue, metrics::{Counter, Histogram}, diff --git a/src/web/mod.rs b/src/web/mod.rs index 940428a15..a0ae7a1f3 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -3,10 +3,7 @@ pub mod page; use crate::{ - db::{ - CrateId, - types::{BuildStatus, krate_name::KrateName, version::Version}, - }, + db::types::{BuildStatus, krate_name::KrateName}, utils::get_correct_docsrs_style_file, web::{ metrics::WebMetrics, @@ -16,6 +13,7 @@ use crate::{ use anyhow::{Context as _, Result, anyhow, bail}; use askama::Template; use axum_extra::middleware::option_layer; +use docs_rs_database::types::{CrateId, version::Version}; use serde::Serialize; use serde_json::Value; use tracing::{info, instrument}; @@ -499,7 +497,7 @@ async fn apply_middleware( TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, to) }))) .layer(Extension(context.pool.clone())) - .layer(Extension(context.async_build_queue.clone())) + // .layer(Extension(context.async_build_queue.clone())) .layer(Extension(web_metrics)) .layer(Extension(context.config.clone())) .layer(Extension(context.registry_api.clone())) diff --git a/src/web/releases.rs b/src/web/releases.rs index 735bf2492..bc5d434ab 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -1,12 +1,8 @@ //! Releases web handlersrelease use super::cache::CachePolicy; -use crate::build_queue::PRIORITY_CONTINUOUS; use crate::{ - AsyncBuildQueue, Config, RegistryApi, - build_queue::QueuedCrate, - db::types::version::Version, - impl_axum_webpage, + Config, RegistryApi, impl_axum_webpage, utils::report_error, web::{ ReqVersion, axum_redirect, encode_url_path, @@ -26,6 +22,8 @@ use axum::{ }; use base64::{Engine, engine::general_purpose::STANDARD as b64}; use chrono::{DateTime, Utc}; +use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS, QueuedCrate}; +use docs_rs_database::types::version::Version; use futures_util::stream::TryStreamExt; use itertools::Itertools; use serde::{Deserialize, Serialize}; diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index 098ff008b..c1f694ca2 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -1,13 +1,9 @@ //! rustdoc handlerr use crate::{ - AsyncStorage, BUILD_VERSION, Config, RUSTDOC_STATIC_STORAGE_PREFIX, + Config, RUSTDOC_STATIC_STORAGE_PREFIX, registry_api::OwnerKind, - storage::{ - CompressionAlgorithm, RustdocJsonFormatVersion, StreamingBlob, rustdoc_archive_path, - rustdoc_json_path, - }, - utils::{self, Dependency}, + utils, web::{ MetaData, ReqVersion, axum_cached_redirect, cache::CachePolicy, @@ -20,7 +16,7 @@ use crate::{ rustdoc::{PageKind, RustdocParams}, }, file::StreamingFile, - headers::{ETagComputer, IfNoneMatch, X_ROBOTS_TAG}, + headers::{IfNoneMatch, X_ROBOTS_TAG}, licenses, match_version, metrics::WebMetrics, page::{ @@ -41,6 +37,15 @@ use axum_extra::{ headers::{ContentType, ETag, Header as _, HeaderMapExt as _}, typed_header::TypedHeader, }; +use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS, QueuedCrate}; +use docs_rs_cargo_metadata::Dependency; +use docs_rs_database::types::version::Version; +use docs_rs_headers::etag::ETagComputer; +use docs_rs_storage::{ + AsyncStorage, CompressionAlgorithm, RustdocJsonFormatVersion, StreamingBlob, + errors::PathNotFoundError, rustdoc_archive_path, rustdoc_json_path, +}; +use docs_rs_utils::BUILD_VERSION; use http::{HeaderMap, HeaderValue, Uri, header::CONTENT_DISPOSITION, uri::Authority}; use serde::Deserialize; use std::{ @@ -345,7 +350,7 @@ pub(crate) async fn rustdoc_redirector_handler( Ok(blob) => Ok(StreamingFile(blob).into_response(if_none_match.as_deref())), Err(err) => { if !matches!(err.downcast_ref(), Some(AxumNope::ResourceNotFound)) - && !matches!(err.downcast_ref(), Some(crate::storage::PathNotFoundError)) + && !matches!(err.downcast_ref(), Some(PathNotFoundError)) { error!(inner_path, ?err, "got error serving file"); } @@ -679,7 +684,7 @@ pub(crate) async fn rustdoc_html_server_handler( Ok(file) => file, Err(err) => { if !matches!(err.downcast_ref(), Some(AxumNope::ResourceNotFound)) - && !matches!(err.downcast_ref(), Some(crate::storage::PathNotFoundError)) + && !matches!(err.downcast_ref(), Some(PathNotFoundError)) { error!("got error serving {}: {}", storage_path, err); } @@ -973,7 +978,7 @@ pub(crate) async fn json_download_handler( StreamingFile(file).into_response(if_none_match.as_deref()), None, ), - Err(err) if matches!(err.downcast_ref(), Some(crate::storage::PathNotFoundError)) => { + Err(err) if matches!(err.downcast_ref(), Some(PathNotFoundError)) => { // we have old files on the bucket where we stored zstd compressed files, // with content-encoding=zstd & just a `.json` file extension. // As a fallback, we redirect to that, if zstd was requested (which is also the default). diff --git a/src/web/sitemap.rs b/src/web/sitemap.rs index 3c26f8e13..d02c2d555 100644 --- a/src/web/sitemap.rs +++ b/src/web/sitemap.rs @@ -1,9 +1,8 @@ use crate::{ Config, - db::mimes, docbuilder::Limits, impl_axum_webpage, - utils::{ConfigName, get_config, report_error}, + utils::report_error, web::{ AxumErrorPage, error::{AxumNope, AxumResult}, @@ -22,6 +21,10 @@ use axum::{ }; use axum_extra::{TypedHeader, headers::ContentType}; use chrono::{TimeZone, Utc}; +use docs_rs_database::{ + mimes, + service_config::{ConfigName, get_config}, +}; use futures_util::{StreamExt as _, pin_mut}; use std::sync::Arc; use tracing::{Span, error}; diff --git a/src/web/source.rs b/src/web/source.rs index 815ff1e53..334374eab 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -1,8 +1,5 @@ use crate::{ - AsyncStorage, Config, - db::{BuildId, types::version::Version}, - impl_axum_webpage, - storage::PathNotFoundError, + Config, impl_axum_webpage, web::{ MetaData, ReqVersion, cache::CachePolicy, @@ -22,6 +19,11 @@ use anyhow::{Context as _, Result}; use askama::Template; use axum::{Extension, response::IntoResponse}; use axum_extra::{TypedHeader, headers::HeaderMapExt}; +use docs_rs_database::types::{BuildId, version::Version}; +use docs_rs_storage::{ + AsyncStorage, + errors::{PathNotFoundError, SizeLimitReached}, +}; use mime::Mime; use std::{cmp::Ordering, sync::Arc}; use tracing::instrument; @@ -303,7 +305,7 @@ pub(crate) async fn source_browser_handler( // if file is too large, set is_file_too_large to true if err.downcast_ref::().is_some_and(|err| { err.get_ref() - .map(|err| err.is::()) + .map(|err| err.is::()) .unwrap_or(false) }) => { diff --git a/src/web/statics.rs b/src/web/statics.rs index 9acc706ac..1719c9ee5 100644 --- a/src/web/statics.rs +++ b/src/web/statics.rs @@ -1,7 +1,6 @@ use super::{ cache::CachePolicy, headers::IfNoneMatch, metrics::request_recorder, routes::get_static, }; -use crate::db::mimes::APPLICATION_OPENSEARCH_XML; use axum::{ Router as AxumRouter, extract::{Extension, Request}, @@ -13,6 +12,7 @@ use axum_extra::{ headers::{ContentType, ETag, HeaderMapExt as _}, typed_header::TypedHeader, }; +use docs_rs_database::mimes::APPLICATION_OPENSEARCH_XML; use http::{StatusCode, Uri}; use tower_http::services::ServeDir; diff --git a/templates/base.html b/templates/base.html index 8085c8b39..5eb54cd1c 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,11 +4,11 @@ - + {%- block meta -%}{%- endblock meta -%} {#- Docs.rs styles -#} - {%- set build_slug = slug::slugify(crate::BUILD_VERSION) -%} + {%- set build_slug = slug::slugify(docs_rs_utils::BUILD_VERSION) -%} diff --git a/templates/core/about/index.html b/templates/core/about/index.html index cb3bdfdd5..45819c450 100644 --- a/templates/core/about/index.html +++ b/templates/core/about/index.html @@ -35,7 +35,7 @@

More about Docs.rs

Version

-

Currently running Docs.rs version is: {{ crate::BUILD_VERSION }}

+

Currently running Docs.rs version is: {{ docs_rs_utils::BUILD_VERSION }}

Builds

Summaries of the documentation build processes are available at /releases/.

diff --git a/templates/crate/source.html b/templates/crate/source.html index 3e1670bfe..24fe8ac76 100644 --- a/templates/crate/source.html +++ b/templates/crate/source.html @@ -136,6 +136,6 @@ {%- block javascript -%} {% if file_content.is_some() %} - + {% endif %} {%- endblock javascript -%} diff --git a/templates/releases/feed.xml b/templates/releases/feed.xml index 59dd65f76..73b796ee0 100644 --- a/templates/releases/feed.xml +++ b/templates/releases/feed.xml @@ -8,7 +8,7 @@ - urn:docs-rs:{{ crate::BUILD_VERSION }} + urn:docs-rs:{{ docs_rs_utils::BUILD_VERSION }} {%- if let Some(first_release) = recent_releases.get(0) -%} {%- if let Some(build_time) = first_release.build_time -%} diff --git a/templates/rustdoc/body.html b/templates/rustdoc/body.html index 365be5086..e2e48ba10 100644 --- a/templates/rustdoc/body.html +++ b/templates/rustdoc/body.html @@ -1,4 +1,4 @@ -{%- set build_slug = slug::slugify(crate::BUILD_VERSION) -%} +{%- set build_slug = slug::slugify(docs_rs_utils::BUILD_VERSION) -%} {# see comment in ../storage-change-detection.html for details #} diff --git a/templates/rustdoc/head.html b/templates/rustdoc/head.html index de9df7021..82ca75332 100644 --- a/templates/rustdoc/head.html +++ b/templates/rustdoc/head.html @@ -3,7 +3,7 @@ {%- if build_slug is defined -%} {% let build_slug2 = build_slug -%} {%- else -%} - {%- let build_slug2 = slug::slugify(crate::BUILD_VERSION) -%} + {%- let build_slug2 = slug::slugify(docs_rs_utils::BUILD_VERSION) -%} {%- endif -%} {%- if let Some(css_file) = rustdoc_css_file -%} diff --git a/templates/rustdoc/vendored.html b/templates/rustdoc/vendored.html index 4f6d400d3..3ad52a721 100644 --- a/templates/rustdoc/vendored.html +++ b/templates/rustdoc/vendored.html @@ -1 +1 @@ - + From 38688821b63a4c5518e8a3d7f858bd8054b5ff33 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sun, 7 Dec 2025 09:50:09 +0100 Subject: [PATCH 08/46] IWP --- Cargo.lock | 92 +++---------- Cargo.toml | 12 +- crates/docs_rs_build_queue/Cargo.toml | 1 + crates/docs_rs_build_queue/src/lib.rs | 64 ++++++++- crates/docs_rs_database/src/lib.rs | 2 + crates/docs_rs_logging/Cargo.toml | 11 ++ crates/docs_rs_logging/src/lib.rs | 79 +++++++++++ crates/docs_rs_opentelemetry/src/config.rs | 1 + crates/docs_rs_opentelemetry/src/lib.rs | 1 + crates/docs_rs_storage/src/lib.rs | 5 +- crates/docs_rs_watcher/Cargo.toml | 3 + crates/docs_rs_watcher/src/lib.rs | 72 +--------- crates/docs_rs_watcher/src/main.rs | 46 +++++++ .../docs_rs_watcher/src/repositories/mod.rs | 1 + src/bin/cratesfyi.rs | 116 ++-------------- src/config.rs | 128 +++++++----------- src/context.rs | 27 ++-- src/db/delete.rs | 4 +- src/docbuilder/rustwide_builder.rs | 5 +- src/utils/daemon.rs | 66 +++++++++ src/utils/queue_builder.rs | 91 +++++++------ src/web/file.rs | 12 +- src/web/source.rs | 2 +- 23 files changed, 430 insertions(+), 411 deletions(-) create mode 100644 crates/docs_rs_logging/Cargo.toml create mode 100644 crates/docs_rs_logging/src/lib.rs create mode 100644 crates/docs_rs_watcher/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 45aba3d25..83b94a9f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1686,38 +1686,14 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "darling" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" -dependencies = [ - "darling_core 0.20.11", - "darling_macro 0.20.11", -] - [[package]] name = "darling" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core 0.21.3", - "darling_macro 0.21.3", -] - -[[package]] -name = "darling_core" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 2.0.111", + "darling_core", + "darling_macro", ] [[package]] @@ -1734,24 +1710,13 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "darling_macro" -version = "0.20.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" -dependencies = [ - "darling_core 0.20.11", - "quote", - "syn 2.0.111", -] - [[package]] name = "darling_macro" version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core 0.21.3", + "darling_core", "quote", "syn 2.0.111", ] @@ -1828,37 +1793,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling 0.20.11", - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn 2.0.111", -] - [[package]] name = "derive_more" version = "0.99.20" @@ -1956,16 +1890,17 @@ dependencies = [ "comrak", "constant_time_eq", "criterion", - "derive_builder", "derive_more 2.0.1", "docs_rs_build_queue", "docs_rs_cargo_metadata", "docs_rs_database", "docs_rs_env_vars", "docs_rs_headers", + "docs_rs_logging", "docs_rs_opentelemetry", "docs_rs_storage", "docs_rs_utils", + "docs_rs_watcher", "docsrs-metadata", "fn-error-context", "font-awesome-as-a-crate", @@ -2020,7 +1955,6 @@ dependencies = [ "tracing", "tracing-futures", "tracing-log", - "tracing-subscriber", "url", "walkdir", ] @@ -2038,6 +1972,7 @@ dependencies = [ "opentelemetry", "serde", "sqlx", + "tokio", "tracing", ] @@ -2094,6 +2029,17 @@ dependencies = [ "md5", ] +[[package]] +name = "docs_rs_logging" +version = "0.1.0" +dependencies = [ + "anyhow", + "docs_rs_utils", + "sentry", + "tracing", + "tracing-subscriber", +] + [[package]] name = "docs_rs_opentelemetry" version = "0.1.0" @@ -2164,12 +2110,14 @@ dependencies = [ "anyhow", "async-trait", "chrono", + "clap", "crates-index", "crates-index-diff", "docs_rs_build_queue", "docs_rs_cargo_metadata", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_logging", "docs_rs_utils", "futures-util", "itertools 0.14.0", @@ -7076,7 +7024,7 @@ version = "3.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" dependencies = [ - "darling 0.21.3", + "darling", "proc-macro2", "quote", "syn 2.0.111", diff --git a/Cargo.toml b/Cargo.toml index dc4481220..e07ab85e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,6 +42,8 @@ tempfile = "3.1.0" http = "1.0.0" reqwest = { version = "0.12", features = ["json", "gzip"] } rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } +sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } +clap = { version = "4.0.22", features = [ "derive" ] } # dev dependencies test-case = "3.0.0" @@ -56,10 +58,9 @@ axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middl base64 = "0.22" bincode = { workspace = true } chrono = { workspace = true } -clap = { version = "4.0.22", features = [ "derive" ] } +clap = { workspace = true } comrak = { version = "0.48.0", default-features = false } constant_time_eq = "0.4.2" -derive_builder = "0.20.2" derive_more = { workspace = true } docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } docs_rs_utils = { path = "crates/docs_rs_utils" } @@ -69,6 +70,8 @@ docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } docs_rs_cargo_metadata = { path = "crates/docs_rs_cargo_metadata" } docs_rs_storage = { path = "crates/docs_rs_storage" } docs_rs_headers = { path = "crates/docs_rs_headers" } +docs_rs_watcher = { path = "crates/docs_rs_watcher" } +docs_rs_logging = { path = "crates/docs_rs_logging" } docsrs-metadata = { path = "crates/metadata" } fn-error-context = "0.2.0" font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } @@ -94,7 +97,7 @@ regex = { workspace = true } reqwest = { workspace = true } rustwide = { workspace = true } semver = { workspace = true } -sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } +sentry = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_with = { workspace = true } @@ -111,9 +114,8 @@ toml = "0.9.2" tower = "0.5.1" tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] } tracing = { workspace = true } -tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } tracing-log = "0.2.0" -tracing-subscriber = { version = "0.3.20", default-features = false, features = ["ansi", "fmt", "json", "env-filter", "tracing-log"] } +tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } url = { workspace = true } walkdir = { workspace = true } diff --git a/crates/docs_rs_build_queue/Cargo.toml b/crates/docs_rs_build_queue/Cargo.toml index ad75e1b2d..e6324db7d 100644 --- a/crates/docs_rs_build_queue/Cargo.toml +++ b/crates/docs_rs_build_queue/Cargo.toml @@ -14,3 +14,4 @@ serde = { workspace = true } sqlx = { workspace = true } tracing = { workspace = true } chrono = { workspace = true } +tokio = { workspace = true } diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 5d3ee2954..53adb1441 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -1,20 +1,19 @@ mod config; mod metrics; -mod rebuilds; +pub mod rebuilds; pub use config::Config; use anyhow::Result; -use chrono::NaiveDate; use docs_rs_database::{ Pool, service_config::{ConfigName, get_config, set_config}, types::version::Version, }; use docs_rs_opentelemetry::AnyMeterProvider; -use futures_util::{StreamExt as _, TryStreamExt as _}; +use futures_util::TryStreamExt as _; use std::{collections::HashMap, sync::Arc}; -use tracing::{info, instrument}; +use tokio::runtime; pub const PRIORITY_DEFAULT: i32 = 0; /// Used for workspaces to avoid blocking the queue (done through the cratesfyi CLI, not used in code) @@ -44,8 +43,6 @@ pub struct QueuedCrate { pub struct AsyncBuildQueue { pub db: Pool, queue_metrics: metrics::BuildQueueMetrics, - // builder_metrics: Arc, - // cdn_metrics: Arc, max_attempts: i32, } @@ -230,6 +227,61 @@ impl AsyncBuildQueue { } } +#[derive(Debug)] +pub struct BuildQueue { + runtime: runtime::Handle, + inner: Arc, +} + +/// sync versions of async methods +impl BuildQueue { + pub fn new(runtime: runtime::Handle, inner: Arc) -> Self { + Self { runtime, inner } + } + + pub fn add_crate( + &self, + name: &str, + version: &Version, + priority: i32, + registry: Option<&str>, + ) -> Result<()> { + self.runtime + .block_on(self.inner.add_crate(name, version, priority, registry)) + } + + pub fn is_locked(&self) -> Result { + self.runtime.block_on(self.inner.is_locked()) + } + pub fn lock(&self) -> Result<()> { + self.runtime.block_on(self.inner.lock()) + } + pub fn unlock(&self) -> Result<()> { + self.runtime.block_on(self.inner.unlock()) + } + // #[cfg(test)] + // pub(crate) fn pending_count(&self) -> Result { + // self.runtime.block_on(self.inner.pending_count()) + // } + // #[cfg(test)] + // pub(crate) fn prioritized_count(&self) -> Result { + // self.runtime.block_on(self.inner.prioritized_count()) + // } + // #[cfg(test)] + // pub(crate) fn pending_count_by_priority(&self) -> Result> { + // self.runtime + // .block_on(self.inner.pending_count_by_priority()) + // } + // #[cfg(test)] + // pub(crate) fn failed_count(&self) -> Result { + // self.runtime.block_on(self.inner.failed_count()) + // } + // #[cfg(test)] + // pub(crate) fn queued_crates(&self) -> Result> { + // self.runtime.block_on(self.inner.queued_crates()) + // } +} + // #[cfg(test)] // mod tests { // use super::*; diff --git a/crates/docs_rs_database/src/lib.rs b/crates/docs_rs_database/src/lib.rs index fbd0473b2..89a6c76dd 100644 --- a/crates/docs_rs_database/src/lib.rs +++ b/crates/docs_rs_database/src/lib.rs @@ -3,6 +3,8 @@ pub mod mimes; pub mod service_config; pub mod types; +pub use config::Config; + use docs_rs_opentelemetry::AnyMeterProvider; use futures_util::{future::BoxFuture, stream::BoxStream}; use opentelemetry::metrics::{Counter, ObservableGauge}; diff --git a/crates/docs_rs_logging/Cargo.toml b/crates/docs_rs_logging/Cargo.toml new file mode 100644 index 000000000..834c6da55 --- /dev/null +++ b/crates/docs_rs_logging/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "docs_rs_logging" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +docs_rs_utils = { path = "../docs_rs_utils" } +sentry = { workspace = true } +tracing = { workspace = true } +tracing-subscriber = { version = "0.3.20", default-features = false, features = ["ansi", "fmt", "json", "env-filter", "tracing-log"] } diff --git a/crates/docs_rs_logging/src/lib.rs b/crates/docs_rs_logging/src/lib.rs new file mode 100644 index 000000000..49c0a02eb --- /dev/null +++ b/crates/docs_rs_logging/src/lib.rs @@ -0,0 +1,79 @@ +use sentry::{ + TransactionContext, integrations::panic as sentry_panic, + integrations::tracing as sentry_tracing, +}; +use std::{env, str::FromStr as _, sync::Arc}; +use tracing_subscriber::{EnvFilter, filter::Directive, prelude::*}; + +pub struct Guard { + sentry_guard: Option, +} + +#[must_use] +pub fn init() -> anyhow::Result { + let log_formatter = { + let log_format = env::var("DOCSRS_LOG_FORMAT").unwrap_or_default(); + + if log_format == "json" { + tracing_subscriber::fmt::layer().json().boxed() + } else { + tracing_subscriber::fmt::layer().boxed() + } + }; + + let tracing_registry = tracing_subscriber::registry().with(log_formatter).with( + EnvFilter::builder() + .with_default_directive(Directive::from_str("docs_rs=info")?) + .with_env_var("DOCSRS_LOG") + .from_env_lossy(), + ); + + let sentry_guard = if let Ok(sentry_dsn) = env::var("SENTRY_DSN") { + tracing::subscriber::set_global_default(tracing_registry.with( + sentry_tracing::layer().event_filter(|md| { + if md.fields().field("reported_to_sentry").is_some() { + sentry_tracing::EventFilter::Ignore + } else { + sentry_tracing::default_event_filter(md) + } + }), + ))?; + + let traces_sample_rate = env::var("SENTRY_TRACES_SAMPLE_RATE") + .ok() + .and_then(|v| v.parse().ok()) + .unwrap_or(0.0); + + let traces_sampler = move |ctx: &TransactionContext| -> f32 { + if let Some(sampled) = ctx.sampled() { + // if the transaction was already marked as "to be sampled" by + // the JS/frontend SDK, we want to sample it in the backend too. + return if sampled { 1.0 } else { 0.0 }; + } + + let op = ctx.operation(); + if op == "docbuilder.build_package" { + // record all transactions for builds + 1. + } else { + traces_sample_rate + } + }; + + Some(sentry::init(( + sentry_dsn, + sentry::ClientOptions { + release: Some(docs_rs_utils::BUILD_VERSION.into()), + attach_stacktrace: true, + traces_sampler: Some(Arc::new(traces_sampler)), + ..Default::default() + } + .add_integration(sentry_panic::PanicIntegration::default()), + ))) + } else { + tracing::subscriber::set_global_default(tracing_registry)?; + None + }; + + Ok(Guard { sentry_guard }) +} diff --git a/crates/docs_rs_opentelemetry/src/config.rs b/crates/docs_rs_opentelemetry/src/config.rs index e40a38d86..d0e50c17d 100644 --- a/crates/docs_rs_opentelemetry/src/config.rs +++ b/crates/docs_rs_opentelemetry/src/config.rs @@ -1,6 +1,7 @@ use docs_rs_env_vars::maybe_env; use url::Url; +#[derive(Debug)] pub struct Config { // opentelemetry endpoint to send OTLP to pub endpoint: Option, diff --git a/crates/docs_rs_opentelemetry/src/lib.rs b/crates/docs_rs_opentelemetry/src/lib.rs index d1e10a172..01d4503f7 100644 --- a/crates/docs_rs_opentelemetry/src/lib.rs +++ b/crates/docs_rs_opentelemetry/src/lib.rs @@ -1,4 +1,5 @@ mod config; +pub use config::Config; use anyhow::Result; use opentelemetry::{ diff --git a/crates/docs_rs_storage/src/lib.rs b/crates/docs_rs_storage/src/lib.rs index 7ed00891a..1d002cd84 100644 --- a/crates/docs_rs_storage/src/lib.rs +++ b/crates/docs_rs_storage/src/lib.rs @@ -7,14 +7,15 @@ pub mod file; mod s3; mod utils; -use crate::{config::Config, file::FileEntry}; - pub use self::compression::{CompressionAlgorithm, CompressionAlgorithms, compress, decompress}; +pub use config::Config; + use self::{ compression::{compress_async, wrap_reader_for_decompression}, database::DatabaseBackend, s3::S3Backend, }; +use crate::file::FileEntry; use anyhow::{Result, anyhow}; use chrono::{DateTime, Utc}; use dashmap::DashMap; diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index 862fff58a..f18d125dc 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -14,6 +14,7 @@ docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_utils = { path = "../docs_rs_utils" } +docs_rs_logging = { path = "../docs_rs_logging" } futures-util = { workspace = true } itertools = { workspace = true } rayon = { workspace = true } @@ -26,6 +27,8 @@ thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true } +clap = { workspace = true } + [dev-dependencies] mockito = { workspace = true } diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index 93bbd0026..71c65bb6c 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -1,9 +1,11 @@ mod config; mod consistency; mod index; -mod repositories; +pub mod repositories; mod utils; +pub use config::Config; + use anyhow::Error; use docs_rs_build_queue::AsyncBuildQueue; use std::time::Instant; @@ -42,71 +44,3 @@ pub async fn watch_registry( tokio::time::sleep(config.delay_between_registry_fetches).await; } } - -// async fn start_registry_watcher( -// build_queue: &AsyncBuildQueue, -// config: &config::Config, -// ) -> Result<(), Error> { -// tokio::spawn(async move { -// // space this out to prevent it from clashing against the queue-builder thread on launch -// tokio::time::sleep(Duration::from_secs(30)).await; - -// watch_registry(&build_queue, &config).await -// }); - -// Ok(()) -// } - -pub async fn start_background_repository_stats_updater() -> Result<(), Error> { - todo!(); - // // This call will still skip github repositories updates and continue if no token is provided - // // (gitlab doesn't require to have a token). The only time this can return an error is when - // // creating a pool or if config fails, which shouldn't happen here because this is run right at - // // startup. - // let updater = context.repository_stats_updater.clone(); - // let runtime = context.runtime.clone(); - // async_cron( - // &runtime, - // "repository stats updater", - // Duration::from_secs(60 * 60), - // move || { - // let updater = updater.clone(); - // async move { - // updater.update_all_crates().await?; - // Ok(()) - // } - // }, - // ); - // Ok(()) -} - -pub fn start_background_queue_rebuild() -> Result<(), Error> { - todo!() - - // let runtime = context.runtime.clone(); - // let pool = context.pool.clone(); - // let config = context.config.clone(); - // let build_queue = context.async_build_queue.clone(); - - // if config.max_queued_rebuilds.is_none() { - // info!("rebuild config incomplete, skipping rebuild queueing"); - // return Ok(()); - // } - - // async_cron( - // &runtime, - // "background queue rebuilder", - // Duration::from_secs(60 * 60), - // move || { - // let pool = pool.clone(); - // let build_queue = build_queue.clone(); - // let config = config.clone(); - // async move { - // let mut conn = pool.get_async().await?; - // queue_rebuilds(&mut conn, &config, &build_queue).await?; - // Ok(()) - // } - // }, - // ); - // Ok(()) -} diff --git a/crates/docs_rs_watcher/src/main.rs b/crates/docs_rs_watcher/src/main.rs new file mode 100644 index 000000000..44a4981b8 --- /dev/null +++ b/crates/docs_rs_watcher/src/main.rs @@ -0,0 +1,46 @@ +use anyhow::Context as _; +use clap::Parser; +use docs_rs_build_queue::AsyncBuildQueue; +use docs_rs_watcher::{Config, watch_registry}; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let _guard = docs_rs_logging::init().context("error initializing logging")?; + + let args = Cli::try_parse()?; + + let config = Config::from_environment()?; + + let build_queue = AsyncBuildQueue::new(); + + if args.repository_stats_updater { + // start_background_repository_stats_updater(&ctx)?; + } + if args.queue_rebuilds { + // start_background_queue_rebuild(&ctx)?; + } + + // When people run the services separately, we assume that we can collect service + // metrics from the registry watcher, which should only run once, and all the time. + start_background_service_metric_collector(&ctx)?; + + watch_registry(&async_build_queue, &config).await?; + + Ok(()) +} + +#[derive(Parser)] +#[command( + about = env!("CARGO_PKG_DESCRIPTION"), + version = docs_rs_utils::BUILD_VERSION, + rename_all = "kebab-case", +)] +struct Cli { + /// Enable or disable the repository stats updater + #[arg(long = "repository-stats-updater")] + repository_stats_updater: bool, + + /// Enable or disable rebuild queueing + #[arg(long = "queue-rebuilds")] + queue_rebuilds: bool, +} diff --git a/crates/docs_rs_watcher/src/repositories/mod.rs b/crates/docs_rs_watcher/src/repositories/mod.rs index 28a48d9ce..5f91bc297 100644 --- a/crates/docs_rs_watcher/src/repositories/mod.rs +++ b/crates/docs_rs_watcher/src/repositories/mod.rs @@ -1,5 +1,6 @@ pub use self::github::GitHub; pub use self::gitlab::GitLab; +pub use self::updater::RepositoryStatsUpdater; #[derive(Debug, thiserror::Error)] #[error("rate limit reached")] diff --git a/src/bin/cratesfyi.rs b/src/bin/cratesfyi.rs index 69cc2a3cc..de8725694 100644 --- a/src/bin/cratesfyi.rs +++ b/src/bin/cratesfyi.rs @@ -8,88 +8,20 @@ use docs_rs::{ utils::{ ConfigName, daemon::start_background_service_metric_collector, get_config, get_crate_pattern_and_priority, list_crate_priorities, queue_builder, - remove_crate_priority, set_config, set_crate_priority, + remove_crate_priority, set_crate_priority, }, }; +use docs_rs_database::service_config::set_config; use futures_util::StreamExt; -use sentry::{ - TransactionContext, integrations::panic as sentry_panic, - integrations::tracing as sentry_tracing, -}; -use std::{env, fmt::Write, net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc}; +use std::{env, fmt::Write, net::SocketAddr, path::PathBuf, sync::Arc}; use tokio::runtime; use tracing_log::LogTracer; -use tracing_subscriber::{EnvFilter, filter::Directive, prelude::*}; fn main() { // set the global log::logger for backwards compatibility // through rustwide. - rustwide::logging::init_with(LogTracer::new()); - - let log_formatter = { - let log_format = env::var("DOCSRS_LOG_FORMAT").unwrap_or_default(); - - if log_format == "json" { - tracing_subscriber::fmt::layer().json().boxed() - } else { - tracing_subscriber::fmt::layer().boxed() - } - }; - - let tracing_registry = tracing_subscriber::registry().with(log_formatter).with( - EnvFilter::builder() - .with_default_directive(Directive::from_str("docs_rs=info").unwrap()) - .with_env_var("DOCSRS_LOG") - .from_env_lossy(), - ); - - let _sentry_guard = if let Ok(sentry_dsn) = env::var("SENTRY_DSN") { - tracing::subscriber::set_global_default(tracing_registry.with( - sentry_tracing::layer().event_filter(|md| { - if md.fields().field("reported_to_sentry").is_some() { - sentry_tracing::EventFilter::Ignore - } else { - sentry_tracing::default_event_filter(md) - } - }), - )) - .unwrap(); - - let traces_sample_rate = env::var("SENTRY_TRACES_SAMPLE_RATE") - .ok() - .and_then(|v| v.parse().ok()) - .unwrap_or(0.0); - - let traces_sampler = move |ctx: &TransactionContext| -> f32 { - if let Some(sampled) = ctx.sampled() { - // if the transaction was already marked as "to be sampled" by - // the JS/frontend SDK, we want to sample it in the backend too. - return if sampled { 1.0 } else { 0.0 }; - } - - let op = ctx.operation(); - if op == "docbuilder.build_package" { - // record all transactions for builds - 1. - } else { - traces_sample_rate - } - }; - - Some(sentry::init(( - sentry_dsn, - sentry::ClientOptions { - release: Some(docs_rs::BUILD_VERSION.into()), - attach_stacktrace: true, - traces_sampler: Some(Arc::new(traces_sampler)), - ..Default::default() - } - .add_integration(sentry_panic::PanicIntegration::default()), - ))) - } else { - tracing::subscriber::set_global_default(tracing_registry).unwrap(); - None - }; + let _logging_guard = rustwide::logging::init_with(LogTracer::new()); + docs_rs_logging::init(); if let Err(err) = CommandLine::parse().handle_args() { let mut msg = format!("Error: {err}"); @@ -106,7 +38,7 @@ fn main() { // we need to drop the sentry guard here so all unsent // errors are sent to sentry before // process::exit kills everything. - drop(_sentry_guard); + drop(_logging_guard); std::process::exit(1); } } @@ -121,7 +53,7 @@ enum Toggle { #[derive(Debug, Clone, PartialEq, Eq, Parser)] #[command( about = env!("CARGO_PKG_DESCRIPTION"), - version = docs_rs::BUILD_VERSION, + version = docs_rs_utils::BUILD_VERSION, rename_all = "kebab-case", )] enum CommandLine { @@ -136,18 +68,6 @@ enum CommandLine { socket_addr: SocketAddr, }, - StartRegistryWatcher { - /// Enable or disable the repository stats updater - #[arg( - long = "repository-stats-updater", - default_value = "disabled", - value_enum - )] - repository_stats_updater: Toggle, - #[arg(long = "queue-rebuilds", default_value = "enabled", value_enum)] - queue_rebuilds: Toggle, - }, - StartBuildServer, /// Starts the daemon @@ -172,32 +92,12 @@ enum CommandLine { impl CommandLine { fn handle_args(self) -> Result<()> { - let config = Config::from_env()?.build()?; + let config = Config::from_env()?; let runtime = Arc::new(runtime::Builder::new_multi_thread().enable_all().build()?); let ctx = runtime.block_on(Context::from_config(config))?; match self { Self::Build { subcommand } => subcommand.handle_args(ctx)?, - Self::StartRegistryWatcher { - repository_stats_updater, - queue_rebuilds, - } => { - if repository_stats_updater == Toggle::Enabled { - docs_rs::utils::daemon::start_background_repository_stats_updater(&ctx)?; - } - if queue_rebuilds == Toggle::Enabled { - docs_rs::utils::daemon::start_background_queue_rebuild(&ctx)?; - } - - // When people run the services separately, we assume that we can collect service - // metrics from the registry watcher, which should only run once, and all the time. - start_background_service_metric_collector(&ctx)?; - - ctx.runtime.block_on(docs_rs::utils::watch_registry( - &ctx.async_build_queue, - &ctx.config, - ))?; - } Self::StartBuildServer => { queue_builder(&ctx, RustwideBuilder::init(&ctx)?)?; } diff --git a/src/config.rs b/src/config.rs index 7958437cc..1b786c0e3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,17 +1,17 @@ use anyhow::{Result, bail}; use docs_rs_env_vars::{env, maybe_env, require_env}; -use std::{ - io, - path::{self, PathBuf}, - time::Duration, -}; +use std::{path::PathBuf, sync::Arc, time::Duration}; use url::Url; -#[derive(Debug, derive_builder::Builder)] -#[builder(pattern = "owned")] +#[derive(Debug)] pub struct Config { pub prefix: PathBuf, + pub database: Arc, + pub watcher: Arc, + pub build_queue: Arc, + pub storage: Arc, + // Access token for APIs for crates.io (careful: use // constant_time_eq for comparisons!) pub(crate) cratesio_token: Option, @@ -78,93 +78,61 @@ pub struct Config { pub(crate) include_default_targets: bool, pub(crate) disable_memory_limit: bool, - // automatic rebuild configuration - pub(crate) max_queued_rebuilds: Option, - - // opentelemetry endpoint to send OTLP to - pub(crate) opentelemetry_endpoint: Option, + pub(crate) opentelemetry: docs_rs_opentelemetry::Config, } impl Config { - pub fn from_env() -> Result { - let old_vars = [ - ("CRATESFYI_PREFIX", "DOCSRS_PREFIX"), - ("CRATESFYI_DATABASE_URL", "DOCSRS_DATABASE_URL"), - ("CRATESFYI_GITHUB_ACCESSTOKEN", "DOCSRS_GITHUB_ACCESSTOKEN"), - ("CRATESFYI_RUSTWIDE_WORKSPACE", "DOCSRS_RUSTWIDE_WORKSPACE"), - ("DOCS_RS_DOCKER", "DOCSRS_DOCKER"), - ("DOCS_RS_LOCAL_DOCKER_IMAGE", "DOCSRS_DOCKER_IMAGE"), - ("DOCS_RS_BUILD_CPU_LIMIT", "DOCSRS_BUILD_CPU_LIMIT"), - ]; - for (old_var, new_var) in old_vars { - if std::env::var(old_var).is_ok() { - bail!( - "env variable {} is no longer accepted; use {} instead", - old_var, - new_var - ); - } - } - + pub fn from_env() -> Result { let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; let temp_dir = prefix.join("tmp"); - Ok(ConfigBuilder::default() - .build_attempts(env("DOCSRS_BUILD_ATTEMPTS", 5u16)?) - .delay_between_build_attempts(Duration::from_secs(env::( + Ok(Self { + prefix, + database: docs_rs_database::Config::from_environment()?.into(), + watcher: docs_rs_watcher::Config::from_environment()?.into(), + build_queue: docs_rs_build_queue::Config::from_environment()?.into(), + storage: docs_rs_storage::Config::from_environment()?.into(), + opentelemetry: docs_rs_opentelemetry::Config::from_environment()?.into(), + + build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, + delay_between_build_attempts: Duration::from_secs(env::( "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", 60, - )?)) - .crates_io_api_call_retries(env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?) - .registry_index_path(env("REGISTRY_INDEX_PATH", prefix.join("crates.io-index"))?) - .registry_url(maybe_env("REGISTRY_URL")?) - .registry_api_host(env( - "DOCSRS_REGISTRY_API_HOST", - "https://crates.io".parse().unwrap(), - )?) - .opentelemetry_endpoint(maybe_env("OTEL_EXPORTER_OTLP_ENDPOINT")?) - .prefix(prefix.clone()) - .database_url(require_env("DOCSRS_DATABASE_URL")?) - .max_pool_size(env("DOCSRS_MAX_POOL_SIZE", 90u32)?) - .min_pool_idle(env("DOCSRS_MIN_POOL_IDLE", 10u32)?) - .cratesio_token(maybe_env("DOCSRS_CRATESIO_TOKEN")?) + )?), + crates_io_api_call_retries: env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?, + cratesio_token: maybe_env("DOCSRS_CRATESIO_TOKEN")?, // LOL HTML only uses as much memory as the size of the start tag! // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 - .max_parse_memory(env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?) - .registry_gc_interval(env("DOCSRS_REGISTRY_GC_INTERVAL", 60 * 60)?) - .render_threads(env("DOCSRS_RENDER_THREADS", num_cpus::get())?) - .request_timeout(maybe_env::("DOCSRS_REQUEST_TIMEOUT")?.map(Duration::from_secs)) - .report_request_timeouts(env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?) - .random_crate_search_view_size(env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?) - .csp_report_only(env("DOCSRS_CSP_REPORT_ONLY", false)?) - .cache_control_stale_while_revalidate(maybe_env( + max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?, + render_threads: env("DOCSRS_RENDER_THREADS", num_cpus::get())?, + request_timeout: maybe_env::("DOCSRS_REQUEST_TIMEOUT")?.map(Duration::from_secs), + report_request_timeouts: env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?, + random_crate_search_view_size: env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?, + csp_report_only: env("DOCSRS_CSP_REPORT_ONLY", false)?, + cache_control_stale_while_revalidate: maybe_env( "CACHE_CONTROL_STALE_WHILE_REVALIDATE", - )?) - .cache_invalidatable_responses(env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?) - .fastly_api_host(env( + )?, + cache_invalidatable_responses: env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?, + fastly_api_host: env( "DOCSRS_FASTLY_API_HOST", "https://api.fastly.com".parse().unwrap(), - )?) - .fastly_api_token(maybe_env("DOCSRS_FASTLY_API_TOKEN")?) - .fastly_service_sid(maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?) - .compiler_metrics_collection_path(maybe_env("DOCSRS_COMPILER_METRICS_PATH")?) - .temp_dir(temp_dir) - .rustwide_workspace(env( - "DOCSRS_RUSTWIDE_WORKSPACE", - PathBuf::from(".workspace"), - )?) - .inside_docker(env("DOCSRS_DOCKER", false)?) - .docker_image( - maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")?.or(maybe_env("DOCSRS_DOCKER_IMAGE")?), - ) - .build_cpu_limit(maybe_env("DOCSRS_BUILD_CPU_LIMIT")?) - .build_default_memory_limit(maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?) - .include_default_targets(env("DOCSRS_INCLUDE_DEFAULT_TARGETS", true)?) - .disable_memory_limit(env("DOCSRS_DISABLE_MEMORY_LIMIT", false)?) - .build_workspace_reinitialization_interval(Duration::from_secs(env( + )?, + fastly_api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, + fastly_service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, + compiler_metrics_collection_path: maybe_env("DOCSRS_COMPILER_METRICS_PATH")?, + temp_dir: temp_dir, + rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, + inside_docker: env("DOCSRS_DOCKER", false)?, + docker_image: maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")? + .or(maybe_env("DOCSRS_DOCKER_IMAGE")?), + build_cpu_limit: maybe_env("DOCSRS_BUILD_CPU_LIMIT")?, + build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, + include_default_targets: env("DOCSRS_INCLUDE_DEFAULT_TARGETS", true)?, + disable_memory_limit: env("DOCSRS_DISABLE_MEMORY_LIMIT", false)?, + build_workspace_reinitialization_interval: Duration::from_secs(env( "DOCSRS_BUILD_WORKSPACE_REINITIALIZATION_INTERVAL", 86400, - )?)) - .max_queued_rebuilds(maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?)) + )?), + }) } } diff --git a/src/context.rs b/src/context.rs index f625dbe4a..595bfa404 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,19 +1,23 @@ use crate::{Config, RegistryApi, cdn::CdnMetrics}; use anyhow::Result; -use docs_rs_build_queue::AsyncBuildQueue; +use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; use docs_rs_database::Pool; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; use docs_rs_storage::{AsyncStorage, Storage}; +use docs_rs_watcher::repositories::RepositoryStatsUpdater; use std::sync::Arc; use tokio::runtime; pub struct Context { pub config: Arc, + pub async_build_queue: Arc, + pub build_queue: Arc, pub storage: Arc, pub async_storage: Arc, pub cdn_metrics: Arc, pub pool: Pool, pub registry_api: Arc, + pub repository_stats_updater: Arc, pub runtime: runtime::Handle, pub meter_provider: AnyMeterProvider, } @@ -21,8 +25,8 @@ pub struct Context { impl Context { /// Create a new context environment from the given configuration. pub async fn from_config(config: Config) -> Result { - let meter_provider = get_meter_provider(&config)?; - let pool = Pool::new(&config, &meter_provider).await?; + let meter_provider = get_meter_provider(&config.opentelemetry)?; + let pool = Pool::new(&config.database, &meter_provider).await?; Self::from_config_with_metrics_and_pool(config, meter_provider, pool).await } @@ -46,15 +50,14 @@ impl Context { ) -> Result { let config = Arc::new(config); - let async_storage = - Arc::new(AsyncStorage::new(pool.clone(), config.clone(), &meter_provider).await?); + let async_storage = Arc::new( + AsyncStorage::new(pool.clone(), config.storage.clone(), &meter_provider).await?, + ); let cdn_metrics = Arc::new(CdnMetrics::new(&meter_provider)); let async_build_queue = Arc::new(AsyncBuildQueue::new( pool.clone(), - config.clone(), - async_storage.clone(), - cdn_metrics.clone(), + config.build_queue.clone(), &meter_provider, )); @@ -65,17 +68,17 @@ impl Context { let storage = Arc::new(Storage::new(async_storage.clone(), runtime.clone())); Ok(Self { - // async_build_queue, - // build_queue, + async_build_queue, + build_queue, storage, async_storage, cdn_metrics, pool: pool.clone(), registry_api: Arc::new(RegistryApi::new( - config.registry_api_host.clone(), + config.watcher.registry_api_host.clone(), config.crates_io_api_call_retries, )?), - // repository_stats_updater: Arc::new(RepositoryStatsUpdater::new(&config, pool)), + repository_stats_updater: Arc::new(RepositoryStatsUpdater::new(&config.watcher, pool)), runtime, config, meter_provider, diff --git a/src/db/delete.rs b/src/db/delete.rs index e3fc07f73..bfadb7eab 100644 --- a/src/db/delete.rs +++ b/src/db/delete.rs @@ -38,7 +38,7 @@ pub async fn delete_crate( storage.delete_prefix(&remote_folder).await?; // remove existing local archive index files. - let local_index_folder = config.local_archive_cache_path.join(&remote_folder); + let local_index_folder = config.storage.local_archive_cache_path.join(&remote_folder); if local_index_folder.exists() { tokio::fs::remove_dir_all(&local_index_folder) .await @@ -79,7 +79,7 @@ pub async fn delete_version( .await?; } - let local_archive_cache = &config.local_archive_cache_path; + let local_archive_cache = &config.storage.local_archive_cache_path; let mut paths = vec![source_archive_path(name, version)]; if is_library { paths.push(rustdoc_archive_path(name, version)); diff --git a/src/docbuilder/rustwide_builder.rs b/src/docbuilder/rustwide_builder.rs index 2d2109e07..4312c5f16 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/src/docbuilder/rustwide_builder.rs @@ -25,6 +25,7 @@ use docs_rs_storage::{ get_file_list, rustdoc_archive_path, rustdoc_json_path, source_archive_path, }; use docs_rs_utils::{BUILD_VERSION, retry}; +use docs_rs_watcher::repositories::RepositoryStatsUpdater; use docsrs_metadata::{BuildTargets, DEFAULT_TARGETS, HOST_TARGET, Metadata}; use itertools::Itertools as _; use opentelemetry::metrics::{Counter, Histogram}; @@ -193,9 +194,9 @@ impl RustwideBuilder { storage: context.storage.clone(), async_storage: context.async_storage.clone(), registry_api: context.registry_api.clone(), - // repository_stats_updater: context.repository_stats_updater.clone(), + repository_stats_updater: context.repository_stats_updater.clone(), workspace_initialize_time: Instant::now(), - // builder_metrics: context.async_build_queue.builder_metrics(), + builder_metrics: BuilderMetrics::new(&context.meter_provider).into(), }) } diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs index 9c7ac2e80..34ce5f52a 100644 --- a/src/utils/daemon.rs +++ b/src/utils/daemon.rs @@ -9,6 +9,7 @@ use crate::{ web::start_web_server, }; use anyhow::{Context as _, Error, anyhow}; +use docs_rs_build_queue::rebuilds::queue_rebuilds; use std::future::Future; use std::sync::Arc; use std::thread; @@ -39,6 +40,71 @@ pub fn start_background_service_metric_collector(context: &Context) -> Result<() Ok(()) } +fn start_registry_watcher(context: &Context) -> Result<(), Error> { + let build_queue = context.async_build_queue.clone(); + let config = context.config.clone(); + + context.runtime.spawn(async move { + // space this out to prevent it from clashing against the queue-builder thread on launch + tokio::time::sleep(Duration::from_secs(30)).await; + + docs_rs_watcher::watch_registry(&build_queue, &config.watcher).await + }); + + Ok(()) +} + +pub fn start_background_queue_rebuild(context: &Context) -> Result<(), Error> { + let runtime = context.runtime.clone(); + let pool = context.pool.clone(); + let config = context.config.clone(); + let build_queue = context.async_build_queue.clone(); + + if config.build_queue.max_queued_rebuilds.is_none() { + info!("rebuild config incomplete, skipping rebuild queueing"); + return Ok(()); + } + + async_cron( + &runtime, + "background queue rebuilder", + Duration::from_secs(60 * 60), + move || { + let pool = pool.clone(); + let build_queue = build_queue.clone(); + let config = config.clone(); + async move { + let mut conn = pool.get_async().await?; + queue_rebuilds(&mut conn, &config.build_queue, &build_queue).await?; + Ok(()) + } + }, + ); + Ok(()) +} + +pub fn start_background_repository_stats_updater(context: &Context) -> Result<(), Error> { + // This call will still skip github repositories updates and continue if no token is provided + // (gitlab doesn't require to have a token). The only time this can return an error is when + // creating a pool or if config fails, which shouldn't happen here because this is run right at + // startup. + let updater = context.repository_stats_updater.clone(); + let runtime = context.runtime.clone(); + async_cron( + &runtime, + "repository stats updater", + Duration::from_secs(60 * 60), + move || { + let updater = updater.clone(); + async move { + updater.update_all_crates().await?; + Ok(()) + } + }, + ); + Ok(()) +} + pub fn start_daemon(context: Context, enable_registry_watcher: bool) -> Result<(), Error> { let context = Arc::new(context); diff --git a/src/utils/queue_builder.rs b/src/utils/queue_builder.rs index 292cedfc0..21034c2cd 100644 --- a/src/utils/queue_builder.rs +++ b/src/utils/queue_builder.rs @@ -8,55 +8,56 @@ use tracing::{debug, error, warn}; /// the main build-server loop pub fn queue_builder(context: &Context, mut builder: RustwideBuilder) -> Result<(), Error> { - loop { - let temp_dir = &context.config.temp_dir; - if temp_dir.exists() - && let Err(e) = remove_tempdirs(temp_dir) - { - report_error(&anyhow::anyhow!(e).context(format!( - "failed to clean temporary directory {:?}", - temp_dir - ))); - } + todo!(); + // loop { + // let temp_dir = &context.config.temp_dir; + // if temp_dir.exists() + // && let Err(e) = remove_tempdirs(temp_dir) + // { + // report_error(&anyhow::anyhow!(e).context(format!( + // "failed to clean temporary directory {:?}", + // temp_dir + // ))); + // } - let build_queue = &context.build_queue; + // let build_queue = &context.build_queue; - // check lock file - match build_queue.is_locked().context("could not get queue lock") { - Ok(true) => { - warn!("Build queue is locked, skipping building new crates"); - thread::sleep(Duration::from_secs(60)); - continue; - } - Ok(false) => {} - Err(err) => { - report_error(&err); - thread::sleep(Duration::from_secs(60)); - continue; - } - } + // // check lock file + // match build_queue.is_locked().context("could not get queue lock") { + // Ok(true) => { + // warn!("Build queue is locked, skipping building new crates"); + // thread::sleep(Duration::from_secs(60)); + // continue; + // } + // Ok(false) => {} + // Err(err) => { + // report_error(&err); + // thread::sleep(Duration::from_secs(60)); + // continue; + // } + // } - // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. - debug!("Checking build queue"); - let res = catch_unwind(AssertUnwindSafe(|| { - match build_queue.build_next_queue_package(context, &mut builder) { - Ok(true) => {} - Ok(false) => { - debug!("Queue is empty, going back to sleep"); - thread::sleep(Duration::from_secs(60)); - } - Err(e) => { - report_error(&e.context("Failed to build crate from queue")); - } - } - })); + // // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. + // debug!("Checking build queue"); + // let res = catch_unwind(AssertUnwindSafe(|| { + // match build_queue.build_next_queue_package(context, &mut builder) { + // Ok(true) => {} + // Ok(false) => { + // debug!("Queue is empty, going back to sleep"); + // thread::sleep(Duration::from_secs(60)); + // } + // Err(e) => { + // report_error(&e.context("Failed to build crate from queue")); + // } + // } + // })); - if let Err(e) = res { - error!("GRAVE ERROR Building new crates panicked: {:?}", e); - thread::sleep(Duration::from_secs(60)); - continue; - } - } + // if let Err(e) = res { + // error!("GRAVE ERROR Building new crates panicked: {:?}", e); + // thread::sleep(Duration::from_secs(60)); + // continue; + // } + // } } /// Sometimes, when the server hits a hard crash or a build thread panics, diff --git a/src/web/file.rs b/src/web/file.rs index d03031d76..0f15da1a8 100644 --- a/src/web/file.rs +++ b/src/web/file.rs @@ -26,13 +26,11 @@ impl File { path: &str, config: &Config, ) -> Result { - let max_size = if path.ends_with(".html") { - config.max_file_size_html - } else { - config.max_file_size - }; - - Ok(File(storage.get(path, max_size).await?)) + Ok(File( + storage + .get(path, config.storage.max_file_size_for(path)) + .await?, + )) } } diff --git a/src/web/source.rs b/src/web/source.rs index 334374eab..ff69859e8 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -286,7 +286,7 @@ pub(crate) async fn source_browser_handler( .insert(CachePolicy::ForeverInCdnAndStaleInBrowser); return Ok(response); } else { - let max_file_size = config.max_file_size_for(&stream.path); + let max_file_size = config.storage.max_file_size_for(&stream.path); // otherwise we'll now download the content to render it into our template. match stream.materialize(max_file_size).await { From edb44280ae14495f557b7f2cbbd2f989fda714a2 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sun, 7 Dec 2025 10:48:27 +0100 Subject: [PATCH 09/46] kjalksdjf --- Cargo.lock | 13 ++ crates/docs_rs_build_queue/src/lib.rs | 2 +- crates/docs_rs_context/Cargo.toml | 10 ++ crates/docs_rs_context/src/lib.rs | 72 ++++++++++ crates/docs_rs_utils/Cargo.toml | 1 + crates/docs_rs_utils/src/lib.rs | 32 ++++- crates/docs_rs_watcher/Cargo.toml | 7 +- crates/docs_rs_watcher/src/lib.rs | 1 + crates/docs_rs_watcher/src/main.rs | 127 +++++++++++++++--- .../docs_rs_watcher/src/service_metrics.rs | 2 +- src/metrics/mod.rs | 2 - src/utils/daemon.rs | 80 ++++------- 12 files changed, 267 insertions(+), 82 deletions(-) create mode 100644 crates/docs_rs_context/Cargo.toml create mode 100644 crates/docs_rs_context/src/lib.rs rename src/metrics/service.rs => crates/docs_rs_watcher/src/service_metrics.rs (97%) diff --git a/Cargo.lock b/Cargo.lock index 83b94a9f7..ae862ba15 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1989,6 +1989,16 @@ dependencies = [ "serde_json", ] +[[package]] +name = "docs_rs_context" +version = "0.1.0" +dependencies = [ + "anyhow", + "docs_rs_build_queue", + "docs_rs_database", + "docs_rs_opentelemetry", +] + [[package]] name = "docs_rs_database" version = "0.1.0" @@ -2115,13 +2125,16 @@ dependencies = [ "crates-index-diff", "docs_rs_build_queue", "docs_rs_cargo_metadata", + "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", "docs_rs_logging", + "docs_rs_opentelemetry", "docs_rs_utils", "futures-util", "itertools 0.14.0", "mockito", + "opentelemetry", "rayon", "regex", "reqwest", diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 53adb1441..77288bee8 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -47,7 +47,7 @@ pub struct AsyncBuildQueue { } impl AsyncBuildQueue { - pub fn new(db: Pool, config: Arc, otel_meter_provider: &AnyMeterProvider) -> Self { + pub fn new(db: Pool, config: &Config, otel_meter_provider: &AnyMeterProvider) -> Self { AsyncBuildQueue { max_attempts: config.build_attempts.into(), db, diff --git a/crates/docs_rs_context/Cargo.toml b/crates/docs_rs_context/Cargo.toml new file mode 100644 index 000000000..54ed64cfc --- /dev/null +++ b/crates/docs_rs_context/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "docs_rs_context" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_build_queue = { path = "../docs_rs_build_queue" } diff --git a/crates/docs_rs_context/src/lib.rs b/crates/docs_rs_context/src/lib.rs new file mode 100644 index 000000000..7d41220f5 --- /dev/null +++ b/crates/docs_rs_context/src/lib.rs @@ -0,0 +1,72 @@ +use anyhow::{Result, anyhow}; +use docs_rs_build_queue::{AsyncBuildQueue, Config}; +use docs_rs_database::Pool; +use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; +use std::sync::Arc; + +pub struct Context { + meter_provider: AnyMeterProvider, + pool: Option, + build_queue: Option>, +} + +// builder +impl Context { + pub fn new() -> Result { + let config = docs_rs_opentelemetry::Config::from_environment()?; + Ok(Context { + meter_provider: get_meter_provider(&config)?, + pool: None, + build_queue: None, + }) + } + + pub async fn with_pool(mut self) -> Result { + if self.pool.is_some() { + return Ok(self); + } + + let config = docs_rs_database::Config::from_environment()?; + let pool = Pool::new(&config, &self.meter_provider).await?; + self.pool = Some(pool); + Ok(self) + } + + pub async fn with_build_queue(mut self) -> Result { + if self.build_queue.is_some() { + return Ok(self); + } + + self = self.with_pool().await?; + + let pool = self.pool()?; + + let config = docs_rs_build_queue::Config::from_environment()?; + let build_queue = AsyncBuildQueue::new(pool, &config, &self.meter_provider); + self.build_queue = Some(Arc::new(build_queue)); + Ok(self) + } +} + +// accessors +impl Context { + pub fn meter_provider(&self) -> &AnyMeterProvider { + &self.meter_provider + } + + pub fn pool(&self) -> Result { + if let Some(ref pool) = self.pool { + Ok(pool.clone()) + } else { + Err(anyhow!("Pool is not initialized")) + } + } + + pub fn build_queue(&self) -> Result> { + if let Some(ref build_queue) = self.build_queue { + Ok(build_queue.clone()) + } else { + Err(anyhow!("Build queue is not initialized")) + } + } +} diff --git a/crates/docs_rs_utils/Cargo.toml b/crates/docs_rs_utils/Cargo.toml index ac337eb9e..9176844af 100644 --- a/crates/docs_rs_utils/Cargo.toml +++ b/crates/docs_rs_utils/Cargo.toml @@ -12,4 +12,5 @@ tracing = { workspace = true } [build-dependencies] anyhow = { workspace = true } chrono = { workspace = true } +tokio = { workspace = true } time = "0.3" diff --git a/crates/docs_rs_utils/src/lib.rs b/crates/docs_rs_utils/src/lib.rs index ecab125f6..32faebb49 100644 --- a/crates/docs_rs_utils/src/lib.rs +++ b/crates/docs_rs_utils/src/lib.rs @@ -1,6 +1,7 @@ use anyhow::Result; use std::{panic, thread, time::Duration}; -use tracing::{Span, warn}; +use tokio::runtime; +use tracing::{Span, error, warn}; /// Version string generated at build time contains last git /// commit hash and build date @@ -111,3 +112,32 @@ where } unreachable!(); } + +pub fn start_async_cron(name: &'static str, interval: Duration, exec: F) +where + Fut: Future> + Send, + F: Fn() -> Fut + Send + 'static, +{ + start_async_cron_in_runtime(&runtime::Handle::current(), name, interval, exec) +} + +pub fn start_async_cron_in_runtime( + runtime: &runtime::Handle, + name: &'static str, + interval: Duration, + exec: F, +) where + Fut: Future> + Send, + F: Fn() -> Fut + Send + 'static, +{ + runtime.spawn(async move { + let mut interval = tokio::time::interval(interval); + loop { + interval.tick().await; + if let Err(err) = exec().await { + // FIXME: is there value in report_error over tracing::error!? + error!(?err, name, "failed to run scheduled task"); + } + } + }); +} diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index f18d125dc..c83becc29 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -7,16 +7,20 @@ edition = "2024" anyhow = { workspace = true } async-trait = "0.1.83" chrono = { workspace = true } +clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } +docs_rs_context = { path = "../docs_rs_context" } docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } -docs_rs_utils = { path = "../docs_rs_utils" } docs_rs_logging = { path = "../docs_rs_logging" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_utils = { path = "../docs_rs_utils" } futures-util = { workspace = true } itertools = { workspace = true } +opentelemetry = { workspace = true } rayon = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } @@ -27,7 +31,6 @@ thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true } -clap = { workspace = true } [dev-dependencies] diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index 71c65bb6c..585edb579 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -2,6 +2,7 @@ mod config; mod consistency; mod index; pub mod repositories; +pub mod service_metrics; mod utils; pub use config::Config; diff --git a/crates/docs_rs_watcher/src/main.rs b/crates/docs_rs_watcher/src/main.rs index 44a4981b8..0abe805c4 100644 --- a/crates/docs_rs_watcher/src/main.rs +++ b/crates/docs_rs_watcher/src/main.rs @@ -1,7 +1,31 @@ -use anyhow::Context as _; +use anyhow::{Context as _, Result}; use clap::Parser; -use docs_rs_build_queue::AsyncBuildQueue; -use docs_rs_watcher::{Config, watch_registry}; +use docs_rs_build_queue::{AsyncBuildQueue, rebuilds::queue_rebuilds}; +use docs_rs_database::Pool; +use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_utils::start_async_cron; +use docs_rs_watcher::{ + Config, repositories::RepositoryStatsUpdater, service_metrics::OtelServiceMetrics, + watch_registry, +}; +use std::{sync::Arc, time::Duration}; +use tracing::{info, trace}; + +#[derive(Parser)] +#[command( + about = env!("CARGO_PKG_DESCRIPTION"), + version = docs_rs_utils::BUILD_VERSION, + rename_all = "kebab-case", +)] +struct Cli { + /// Enable or disable the repository stats updater + #[arg(long = "repository-stats-updater")] + repository_stats_updater: bool, + + /// Enable or disable rebuild queueing + #[arg(long = "queue-rebuilds")] + queue_rebuilds: bool, +} #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -9,38 +33,99 @@ async fn main() -> anyhow::Result<()> { let args = Cli::try_parse()?; + let context = docs_rs_context::Context::new()? + .with_pool() + .await? + .with_build_queue() + .await?; + let config = Config::from_environment()?; - let build_queue = AsyncBuildQueue::new(); + let pool = context.pool()?; + let build_queue = context.build_queue()?; if args.repository_stats_updater { - // start_background_repository_stats_updater(&ctx)?; + start_background_repository_stats_updater(&config, pool.clone()); } if args.queue_rebuilds { - // start_background_queue_rebuild(&ctx)?; + start_background_queue_rebuild(pool.clone(), build_queue.clone())?; } // When people run the services separately, we assume that we can collect service // metrics from the registry watcher, which should only run once, and all the time. - start_background_service_metric_collector(&ctx)?; + start_background_service_metric_collector(build_queue.clone(), context.meter_provider())?; - watch_registry(&async_build_queue, &config).await?; + watch_registry(&build_queue, &config).await?; Ok(()) } -#[derive(Parser)] -#[command( - about = env!("CARGO_PKG_DESCRIPTION"), - version = docs_rs_utils::BUILD_VERSION, - rename_all = "kebab-case", -)] -struct Cli { - /// Enable or disable the repository stats updater - #[arg(long = "repository-stats-updater")] - repository_stats_updater: bool, +fn start_background_repository_stats_updater(config: &Config, pool: Pool) { + // This call will still skip github repositories updates and continue if no token is provided + // (gitlab doesn't require to have a token). The only time this can return an error is when + // creating a pool or if config fails, which shouldn't happen here because this is run right at + // startup. - /// Enable or disable rebuild queueing - #[arg(long = "queue-rebuilds")] - queue_rebuilds: bool, + let updater = Arc::new(RepositoryStatsUpdater::new(&config, pool)); + + start_async_cron( + "repository stats updater", + Duration::from_secs(60 * 60), + move || { + let updater = updater.clone(); + async move { + updater.update_all_crates().await?; + Ok(()) + } + }, + ); +} + +fn start_background_queue_rebuild(pool: Pool, build_queue: Arc) -> Result<()> { + // TODO: is refetching th config ok here? or should we store it globally somewhere? + let config = Arc::new(docs_rs_build_queue::Config::from_environment()?); + + if config.max_queued_rebuilds.is_none() { + info!("rebuild config incomplete, skipping rebuild queueing"); + return Ok(()); + } + + start_async_cron( + "background queue rebuilder", + Duration::from_secs(60 * 60), + move || { + let pool = pool.clone(); + let build_queue = build_queue.clone(); + let config = config.clone(); + async move { + let mut conn = pool.get_async().await?; + queue_rebuilds(&mut conn, &config, &build_queue).await?; + Ok(()) + } + }, + ); + Ok(()) +} + +pub fn start_background_service_metric_collector( + build_queue: Arc, + meter_provider: &AnyMeterProvider, +) -> Result<()> { + let service_metrics = Arc::new(OtelServiceMetrics::new(&meter_provider)); + + start_async_cron( + "background service metric collector", + // old prometheus scrape interval seems to have been ~5s, but IMO that's far too frequent + // for these service metrics. + Duration::from_secs(30), + move || { + let build_queue = build_queue.clone(); + let service_metrics = service_metrics.clone(); + async move { + trace!("collecting service metrics"); + service_metrics.gather(&build_queue).await + } + }, + ); + Ok(()) } diff --git a/src/metrics/service.rs b/crates/docs_rs_watcher/src/service_metrics.rs similarity index 97% rename from src/metrics/service.rs rename to crates/docs_rs_watcher/src/service_metrics.rs index 218ef78d9..26342ab23 100644 --- a/src/metrics/service.rs +++ b/crates/docs_rs_watcher/src/service_metrics.rs @@ -41,7 +41,7 @@ impl OtelServiceMetrics { } } - pub(crate) async fn gather(&self, queue: &AsyncBuildQueue) -> Result<(), Error> { + pub async fn gather(&self, queue: &AsyncBuildQueue) -> Result<(), Error> { self.queue_is_locked .record(queue.is_locked().await? as u64, &[]); self.queued_crates_count diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs index eaa13f986..fb09390d7 100644 --- a/src/metrics/mod.rs +++ b/src/metrics/mod.rs @@ -1,5 +1,3 @@ -pub(crate) mod service; - /// the measured times from cdn invalidations, meaning: /// * how long an invalidation took, or /// * how long the invalidation was queued diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs index 34ce5f52a..24187ab87 100644 --- a/src/utils/daemon.rs +++ b/src/utils/daemon.rs @@ -2,44 +2,16 @@ //! //! This daemon will start web server, track new packages and build them -use crate::{ - Context, RustwideBuilder, - metrics::service::OtelServiceMetrics, - utils::{queue_builder, report_error}, - web::start_web_server, -}; -use anyhow::{Context as _, Error, anyhow}; +use crate::{Context, RustwideBuilder, utils::queue_builder, web::start_web_server}; +use anyhow::{Error, anyhow}; use docs_rs_build_queue::rebuilds::queue_rebuilds; -use std::future::Future; +use docs_rs_utils::start_async_cron_in_runtime; +use docs_rs_watcher::service_metrics::OtelServiceMetrics; use std::sync::Arc; use std::thread; use std::time::Duration; -use tokio::runtime; use tracing::{info, trace}; -pub fn start_background_service_metric_collector(context: &Context) -> Result<(), Error> { - let runtime = context.runtime.clone(); - let build_queue = context.async_build_queue.clone(); - let service_metrics = Arc::new(OtelServiceMetrics::new(&context.meter_provider)); - - async_cron( - &runtime, - "background service metric collector", - // old prometheus scrape interval seems to have been ~5s, but IMO that's far too frequent - // for these service metrics. - Duration::from_secs(30), - move || { - let build_queue = build_queue.clone(); - let service_metrics = service_metrics.clone(); - async move { - trace!("collecting service metrics"); - service_metrics.gather(&build_queue).await - } - }, - ); - Ok(()) -} - fn start_registry_watcher(context: &Context) -> Result<(), Error> { let build_queue = context.async_build_queue.clone(); let config = context.config.clone(); @@ -65,7 +37,7 @@ pub fn start_background_queue_rebuild(context: &Context) -> Result<(), Error> { return Ok(()); } - async_cron( + start_async_cron_in_runtime( &runtime, "background queue rebuilder", Duration::from_secs(60 * 60), @@ -90,7 +62,7 @@ pub fn start_background_repository_stats_updater(context: &Context) -> Result<() // startup. let updater = context.repository_stats_updater.clone(); let runtime = context.runtime.clone(); - async_cron( + start_async_cron_in_runtime( &runtime, "repository stats updater", Duration::from_secs(60 * 60), @@ -145,25 +117,25 @@ pub fn start_daemon(context: Context, enable_registry_watcher: bool) -> Result<( .map_err(|err| anyhow!("web server panicked: {:?}", err))? } -pub(crate) fn async_cron( - runtime: &runtime::Handle, - name: &'static str, - interval: Duration, - exec: F, -) where - Fut: Future> + Send, - F: Fn() -> Fut + Send + 'static, -{ - runtime.spawn(async move { - let mut interval = tokio::time::interval(interval); - loop { - interval.tick().await; - if let Err(err) = exec() - .await - .with_context(|| format!("failed to run scheduled task '{name}'")) - { - report_error(&err); +pub fn start_background_service_metric_collector(context: &Context) -> Result<(), Error> { + let runtime = context.runtime.clone(); + let build_queue = context.async_build_queue.clone(); + let service_metrics = Arc::new(OtelServiceMetrics::new(&context.meter_provider)); + + start_async_cron_in_runtime( + &runtime, + "background service metric collector", + // old prometheus scrape interval seems to have been ~5s, but IMO that's far too frequent + // for these service metrics. + Duration::from_secs(30), + move || { + let build_queue = build_queue.clone(); + let service_metrics = service_metrics.clone(); + async move { + trace!("collecting service metrics"); + service_metrics.gather(&build_queue).await } - } - }); + }, + ); + Ok(()) } From 1bc973eeca5d67192a9f3cb735dc7de35136372a Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sun, 7 Dec 2025 11:59:44 +0100 Subject: [PATCH 10/46] WIP --- Cargo.lock | 41 +- Cargo.toml | 4 +- crates/docs_rs_build_queue/src/lib.rs | 2 + crates/docs_rs_build_queue/src/metrics.rs | 2 +- .../docs_rs_database/src}/types/krate_name.rs | 94 +-- crates/docs_rs_database/src/types/mod.rs | 1 + crates/docs_rs_fastly/Cargo.toml | 20 + crates/docs_rs_fastly/src/config.rs | 27 + crates/docs_rs_fastly/src/lib.rs | 338 +++++++++++ crates/docs_rs_fastly/src/metrics.rs | 40 ++ crates/docs_rs_headers/Cargo.toml | 9 + .../docs_rs_headers/src}/canonical_url.rs | 126 ++-- crates/docs_rs_headers/src/if_none_match.rs | 168 ++++++ crates/docs_rs_headers/src/lib.rs | 16 + .../docs_rs_headers/src}/surrogate_key.rs | 5 +- crates/docs_rs_watcher/src/build_queue.rs | 152 +++++ crates/docs_rs_watcher/src/db/delete.rs | 557 +++++++++++++++++ crates/docs_rs_watcher/src/db/mod.rs | 0 crates/docs_rs_watcher/src/lib.rs | 2 + crates/docs_rs_watcher/src/priorities.rs | 198 +++++++ crates/docs_rs_web_utils/Cargo.toml | 12 + .../docs_rs_web_utils/src}/escaped_uri.rs | 2 +- crates/docs_rs_web_utils/src/lib.rs | 19 + src/cdn/fastly.rs | 322 ---------- src/cdn/mod.rs | 72 --- src/config.rs | 15 - src/db/delete.rs | 558 ------------------ src/db/types/mod.rs | 1 - src/utils/queue.rs | 199 ------- src/web/headers/if_none_match.rs | 173 ------ src/web/headers/mod.rs | 18 - src/web/mod.rs | 16 - 32 files changed, 1717 insertions(+), 1492 deletions(-) rename {src/db => crates/docs_rs_database/src}/types/krate_name.rs (64%) create mode 100644 crates/docs_rs_fastly/Cargo.toml create mode 100644 crates/docs_rs_fastly/src/config.rs create mode 100644 crates/docs_rs_fastly/src/lib.rs create mode 100644 crates/docs_rs_fastly/src/metrics.rs rename {src/web/headers => crates/docs_rs_headers/src}/canonical_url.rs (51%) create mode 100644 crates/docs_rs_headers/src/if_none_match.rs rename {src/web/headers => crates/docs_rs_headers/src}/surrogate_key.rs (98%) create mode 100644 crates/docs_rs_watcher/src/build_queue.rs create mode 100644 crates/docs_rs_watcher/src/db/delete.rs create mode 100644 crates/docs_rs_watcher/src/db/mod.rs create mode 100644 crates/docs_rs_watcher/src/priorities.rs create mode 100644 crates/docs_rs_web_utils/Cargo.toml rename {src/web => crates/docs_rs_web_utils/src}/escaped_uri.rs (99%) create mode 100644 crates/docs_rs_web_utils/src/lib.rs delete mode 100644 src/cdn/fastly.rs delete mode 100644 src/cdn/mod.rs delete mode 100644 src/utils/queue.rs delete mode 100644 src/web/headers/if_none_match.rs delete mode 100644 src/web/headers/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ae862ba15..852a1bca7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1925,7 +1925,6 @@ dependencies = [ "opentelemetry-resource-detectors", "opentelemetry_sdk", "path-slash", - "percent-encoding", "phf 0.13.1", "phf_codegen 0.13.1", "pretty_assertions", @@ -2031,12 +2030,40 @@ dependencies = [ "tracing", ] +[[package]] +name = "docs_rs_fastly" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_headers", + "docs_rs_opentelemetry", + "docs_rs_utils", + "http 1.4.0", + "itertools 0.14.0", + "opentelemetry", + "reqwest", + "tracing", + "url", +] + [[package]] name = "docs_rs_headers" version = "0.1.0" dependencies = [ + "anyhow", + "askama", + "derive_more 2.0.1", + "docs_rs_database", + "docs_rs_utils", + "docs_rs_web_utils", "headers", + "http 1.4.0", + "itertools 0.14.0", "md5", + "serde", ] [[package]] @@ -2147,6 +2174,18 @@ dependencies = [ "url", ] +[[package]] +name = "docs_rs_web_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "askama", + "bincode 2.0.1", + "http 1.4.0", + "percent-encoding", + "url", +] + [[package]] name = "docsrs-metadata" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index e07ab85e7..3ed2ebdef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ reqwest = { version = "0.12", features = ["json", "gzip"] } rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } clap = { version = "4.0.22", features = [ "derive" ] } +askama = "0.14.0" # dev dependencies test-case = "3.0.0" @@ -51,7 +52,7 @@ mockito = "1.0.2" [dependencies] anyhow = { workspace = true } -askama = "0.14.0" +askama = { workspace = true } async-stream = { workspace = true } axum = { version = "0.8.1", features = ["macros"] } axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middleware"] } @@ -90,7 +91,6 @@ opentelemetry-otlp = { workspace = true } opentelemetry-resource-detectors = { workspace = true } opentelemetry_sdk = { workspace = true } path-slash = "0.2.0" -percent-encoding = "2.2.0" phf = "0.13.1" rayon = { workspace = true } regex = { workspace = true } diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 77288bee8..2ebea148e 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -81,6 +81,8 @@ impl AsyncBuildQueue { .execute(&mut *conn) .await?; + self.queue_metrics.queued_builds.add(1, &[]); + Ok(()) } diff --git a/crates/docs_rs_build_queue/src/metrics.rs b/crates/docs_rs_build_queue/src/metrics.rs index bcdb451e2..edd87a148 100644 --- a/crates/docs_rs_build_queue/src/metrics.rs +++ b/crates/docs_rs_build_queue/src/metrics.rs @@ -3,7 +3,7 @@ use opentelemetry::metrics::Counter; #[derive(Debug)] pub struct BuildQueueMetrics { - queued_builds: Counter, + pub(crate) queued_builds: Counter, } impl BuildQueueMetrics { diff --git a/src/db/types/krate_name.rs b/crates/docs_rs_database/src/types/krate_name.rs similarity index 64% rename from src/db/types/krate_name.rs rename to crates/docs_rs_database/src/types/krate_name.rs index 0b963426b..1564c7144 100644 --- a/src/db/types/krate_name.rs +++ b/crates/docs_rs_database/src/types/krate_name.rs @@ -104,50 +104,50 @@ fn validate_crate_name(name: &str) -> Result<()> { Ok(()) } -#[cfg(test)] -mod tests { - use crate::test::TestEnvironment; - - use super::*; - use test_case::test_case; - - #[test_case("valid_crate_name")] - #[test_case("with-dash")] - #[test_case("CapitalLetter")] - fn test_valid_crate_name(name: &str) { - assert!(validate_crate_name(name).is_ok()); - assert_eq!(name.parse::().unwrap(), name); - } - - #[test_case("with space")] - #[test_case("line break\n")] - #[test_case("non ascii äöü")] - #[test_case("0123456789101112131415161718192021222324252627282930313233343536373839"; "too long")] - fn test_invalid_crate_name(name: &str) { - assert!(validate_crate_name(name).is_err()); - assert!(name.parse::().is_err()); - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_sqlx_encode_decode() -> Result<()> { - let env = TestEnvironment::new().await?; - let mut conn = env.async_db().async_conn().await; - - let some_crate_name = "some-krate-123".parse::()?; - - sqlx::query!( - "INSERT INTO crates (name) VALUES ($1)", - some_crate_name as _ - ) - .execute(&mut *conn) - .await?; - - let new_name = sqlx::query_scalar!(r#"SELECT name as "name: KrateName" FROM crates"#) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(new_name, some_crate_name); - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use crate::test::TestEnvironment; + +// use super::*; +// use test_case::test_case; + +// #[test_case("valid_crate_name")] +// #[test_case("with-dash")] +// #[test_case("CapitalLetter")] +// fn test_valid_crate_name(name: &str) { +// assert!(validate_crate_name(name).is_ok()); +// assert_eq!(name.parse::().unwrap(), name); +// } + +// #[test_case("with space")] +// #[test_case("line break\n")] +// #[test_case("non ascii äöü")] +// #[test_case("0123456789101112131415161718192021222324252627282930313233343536373839"; "too long")] +// fn test_invalid_crate_name(name: &str) { +// assert!(validate_crate_name(name).is_err()); +// assert!(name.parse::().is_err()); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_sqlx_encode_decode() -> Result<()> { +// let env = TestEnvironment::new().await?; +// let mut conn = env.async_db().async_conn().await; + +// let some_crate_name = "some-krate-123".parse::()?; + +// sqlx::query!( +// "INSERT INTO crates (name) VALUES ($1)", +// some_crate_name as _ +// ) +// .execute(&mut *conn) +// .await?; + +// let new_name = sqlx::query_scalar!(r#"SELECT name as "name: KrateName" FROM crates"#) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(new_name, some_crate_name); + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_database/src/types/mod.rs b/crates/docs_rs_database/src/types/mod.rs index 0762b5109..71e48173b 100644 --- a/crates/docs_rs_database/src/types/mod.rs +++ b/crates/docs_rs_database/src/types/mod.rs @@ -1,6 +1,7 @@ use derive_more::{Display, FromStr}; use serde::Serialize; +pub mod krate_name; pub mod version; #[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] diff --git a/crates/docs_rs_fastly/Cargo.toml b/crates/docs_rs_fastly/Cargo.toml new file mode 100644 index 000000000..6ab8f8e37 --- /dev/null +++ b/crates/docs_rs_fastly/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "docs_rs_fastly" +version = "0.1.0" +edition = "2024" + +[dependencies] +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_utils = { path = "../docs_rs_utils" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_headers = { path = "../docs_rs_headers" } +docs_rs_database = { path = "../docs_rs_database" } +tracing = { workspace = true } +url = { workspace = true } +opentelemetry = { workspace = true } +chrono = { workspace = true } +anyhow = { workspace = true } +itertools = { workspace = true } +reqwest = { workspace = true } +http = { workspace = true } + diff --git a/crates/docs_rs_fastly/src/config.rs b/crates/docs_rs_fastly/src/config.rs new file mode 100644 index 000000000..88d058d50 --- /dev/null +++ b/crates/docs_rs_fastly/src/config.rs @@ -0,0 +1,27 @@ +use docs_rs_env_vars::{env, maybe_env}; +use url::Url; + +#[derive(Debug)] +pub struct Config { + /// Fastly API host, typically only overwritten for testing + pub api_host: Url, + + /// Fastly API token for purging the services below. + pub api_token: Option, + + /// fastly service SID for the main domain + pub service_sid: Option, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + api_host: env( + "DOCSRS_FASTLY_API_HOST", + "https://api.fastly.com".parse().unwrap(), + )?, + api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, + service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, + }) + } +} diff --git a/crates/docs_rs_fastly/src/lib.rs b/crates/docs_rs_fastly/src/lib.rs new file mode 100644 index 000000000..6b4cf77f9 --- /dev/null +++ b/crates/docs_rs_fastly/src/lib.rs @@ -0,0 +1,338 @@ +mod config; +mod metrics; + +use anyhow::{Result, anyhow, bail}; +use chrono::{DateTime, TimeZone as _, Utc}; +use docs_rs_database::types::krate_name::KrateName; +use docs_rs_headers::{SURROGATE_KEY, SurrogateKey, SurrogateKeys}; +use docs_rs_utils::APP_USER_AGENT; +use http::{ + HeaderMap, HeaderName, HeaderValue, + header::{ACCEPT, USER_AGENT}, +}; +use itertools::Itertools as _; +use opentelemetry::KeyValue; +use std::sync::OnceLock; +use tracing::{error, info, instrument}; + +const FASTLY_KEY: HeaderName = HeaderName::from_static("fastly-key"); + +// https://www.fastly.com/documentation/reference/api/#rate-limiting +const FASTLY_RATELIMIT_REMAINING: HeaderName = + HeaderName::from_static("fastly-ratelimit-remaining"); +const FASTLY_RATELIMIT_RESET: HeaderName = HeaderName::from_static("fastyly-ratelimit-reset"); + +static CLIENT: OnceLock> = OnceLock::new(); + +fn fastly_client(api_token: impl AsRef) -> anyhow::Result<&'static reqwest::Client> { + CLIENT + .get_or_init(|| -> Result<_> { + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, HeaderValue::from_static(APP_USER_AGENT)); + headers.insert(ACCEPT, HeaderValue::from_static("application/json")); + headers.insert(FASTLY_KEY, HeaderValue::from_str(api_token.as_ref())?); + + Ok(reqwest::Client::builder() + .default_headers(headers) + .build()?) + }) + .as_ref() + .map_err(|err| anyhow!("reqwest Client init failed: {}", err)) +} + +fn fetch_rate_limit_state(headers: &HeaderMap) -> (Option, Option>) { + // https://www.fastly.com/documentation/reference/api/#rate-limiting + ( + headers + .get(FASTLY_RATELIMIT_REMAINING) + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse().ok()), + headers + .get(FASTLY_RATELIMIT_RESET) + .and_then(|hv| hv.to_str().ok()) + .and_then(|s| s.parse::().ok()) + .and_then(|ts| Utc.timestamp_opt(ts, 0).single()), + ) +} + +/// Purge the given surrogate keys from all configured fastly services. +/// +/// Accepts any number of surrogate keys, and splits them into appropriately sized +/// batches for the Fastly API. +pub(crate) async fn purge_surrogate_keys( + config: &config::Config, + metrics: &metrics::CdnMetrics, + keys: I, +) -> Result<()> +where + I: IntoIterator, +{ + let Some(api_token) = &config.api_token else { + bail!("Fastly API token not configured"); + }; + + let client = fastly_client(api_token)?; + + let record_rate_limit_metrics = + |limit_remaining: Option, limit_reset: Option>| { + if let Some(limit_remaining) = limit_remaining { + metrics.rate_limit_remaining.record(limit_remaining, &[]); + } + + if let Some(limit_reset) = limit_reset { + metrics + .time_until_rate_limit_reset + .record((limit_reset - Utc::now()).num_seconds() as u64, &[]); + } + }; + + // the `bulk_purge_tag` supports up to 256 surrogate keys in its list, + // but I believe we also have to respect the length limits for the full + // surrogate key header we send in this purge request. + // see https://www.fastly.com/documentation/reference/api/purging/ + for encoded_surrogate_keys in keys.into_iter().batching(|it| { + const MAX_SURROGATE_KEYS_IN_BATCH_PURGE: usize = 256; + + // SurrogateKeys::from_iter::until_full only consumes as many elements as will fit into + // the header. + // The rest is up to the next `batching` iteration. + let keys = SurrogateKeys::from_iter_until_full(it.take(MAX_SURROGATE_KEYS_IN_BATCH_PURGE)); + + if keys.key_count() > 0 { + Some(keys) + } else { + None + } + }) { + if let Some(ref sid) = config.service_sid { + // NOTE: we start with just calling the API, and logging an error if they happen. + // We can then see if we need retries or escalation to full purges. + + let kv = [KeyValue::new("service_sid", sid.clone())]; + + // https://www.fastly.com/documentation/reference/api/purging/ + // TODO: investigate how they could help & test + // soft purge. But later, after the initial migration. + match client + .post(config.api_host.join(&format!("/service/{}/purge", sid))?) + .header(&SURROGATE_KEY, encoded_surrogate_keys.to_string()) + .send() + .await + { + Ok(response) if response.status().is_success() => { + metrics.batch_purges_with_surrogate.add(1, &kv); + metrics + .purge_surrogate_keys + .add(encoded_surrogate_keys.key_count() as u64, &kv); + + let (limit_remaining, limit_reset) = fetch_rate_limit_state(response.headers()); + record_rate_limit_metrics(limit_remaining, limit_reset); + } + Ok(error_response) => { + metrics.batch_purge_errors.add(1, &kv); + + let (limit_remaining, limit_reset) = + fetch_rate_limit_state(error_response.headers()); + record_rate_limit_metrics(limit_remaining, limit_reset); + + let limit_reset = limit_reset.map(|dt| dt.to_rfc3339()); + + let status = error_response.status(); + let content = error_response.text().await.unwrap_or_default(); + error!( + sid, + %status, + content, + %encoded_surrogate_keys, + rate_limit_remaining=limit_remaining, + rate_limit_reset=limit_reset, + "Failed to purge Fastly surrogate keys for service" + ); + } + Err(err) => { + // connection errors or similar, where we don't have a response + metrics.batch_purge_errors.add(1, &kv); + error!( + sid, + ?err, + %encoded_surrogate_keys, + "Failed to purge Fastly surrogate keys for service" + ); + } + }; + } + } + + Ok(()) +} + +#[instrument(skip(config))] +pub async fn queue_crate_invalidation( + config: &config::Config, + metrics: &metrics::CdnMetrics, + krate_name: &KrateName, +) -> Result<()> { + if config.api_token.is_some() + && let Err(err) = purge_surrogate_keys( + config, + metrics, + std::iter::once(SurrogateKey::from(krate_name.clone())), + ) + .await + { + // TODO: for now just consume & report the error, I want to see how often that happens. + // We can then decide if we need more protection mechanisms (like retries or queuing). + error!(%krate_name, ?err, "error purging Fastly surrogate keys"); + } + + Ok(()) +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::{TestEnvironment, setup_test_meter_provider}; +// use chrono::TimeZone; +// use std::str::FromStr as _; + +// #[test] +// fn test_read_rate_limit() { +// // https://www.fastly.com/documentation/reference/api/#rate-limiting +// let mut hm = HeaderMap::new(); +// hm.insert(FASTLY_RATELIMIT_REMAINING, HeaderValue::from_static("999")); +// hm.insert( +// FASTLY_RATELIMIT_RESET, +// HeaderValue::from_static("1452032384"), +// ); + +// let (remaining, reset) = fetch_rate_limit_state(&hm); +// assert_eq!(remaining, Some(999)); +// assert_eq!( +// reset, +// Some(Utc.timestamp_opt(1452032384, 0).single().unwrap()) +// ); +// } + +// #[tokio::test] +// async fn test_purge() -> Result<()> { +// let mut fastly_api = mockito::Server::new_async().await; + +// let config = TestEnvironment::base_config() +// .fastly_api_host(fastly_api.url().parse().unwrap()) +// .fastly_api_token(Some("test-token".into())) +// .fastly_service_sid(Some("test-sid-1".into())) +// .build()?; + +// let m = fastly_api +// .mock("POST", "/service/test-sid-1/purge") +// .match_header(FASTLY_KEY, "test-token") +// .match_header(&SURROGATE_KEY, "crate-foo crate-bar") +// .with_status(200) +// .create_async() +// .await; + +// let (_exporter, meter_provider) = setup_test_meter_provider(); +// let metrics = CdnMetrics::new(&meter_provider); + +// purge_surrogate_keys( +// &config, +// &metrics, +// vec![ +// SurrogateKey::from_str("crate-foo").unwrap(), +// SurrogateKey::from_str("crate-bar").unwrap(), +// ], +// ) +// .await?; + +// m.assert_async().await; + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_purge_err_doesnt_err() -> Result<()> { +// let mut fastly_api = mockito::Server::new_async().await; + +// let config = TestEnvironment::base_config() +// .fastly_api_host(fastly_api.url().parse().unwrap()) +// .fastly_api_token(Some("test-token".into())) +// .fastly_service_sid(Some("test-sid-1".into())) +// .build()?; + +// let m = fastly_api +// .mock("POST", "/service/test-sid-1/purge") +// .match_header(FASTLY_KEY, "test-token") +// .match_header(&SURROGATE_KEY, "crate-foo crate-bar") +// .with_status(500) +// .create_async() +// .await; + +// let (_exporter, meter_provider) = setup_test_meter_provider(); +// let metrics = CdnMetrics::new(&meter_provider); + +// assert!( +// purge_surrogate_keys( +// &config, +// &metrics, +// vec![ +// SurrogateKey::from_str("crate-foo").unwrap(), +// SurrogateKey::from_str("crate-bar").unwrap(), +// ], +// ) +// .await +// .is_ok() +// ); + +// m.assert_async().await; + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_purge_split_requests() -> Result<()> { +// let mut fastly_api = mockito::Server::new_async().await; + +// let config = TestEnvironment::base_config() +// .fastly_api_host(fastly_api.url().parse().unwrap()) +// .fastly_api_token(Some("test-token".into())) +// .fastly_service_sid(Some("test-sid-1".into())) +// .build()?; + +// let m = fastly_api +// .mock("POST", "/service/test-sid-1/purge") +// .match_header(FASTLY_KEY, "test-token") +// .match_request(|request| { +// let [surrogate_keys] = request.header(&SURROGATE_KEY)[..] else { +// panic!("expected one SURROGATE_KEY header"); +// }; +// let surrogate_keys: SurrogateKeys = +// surrogate_keys.to_str().unwrap().parse().unwrap(); + +// assert!( +// // first request +// surrogate_keys.key_count() == 256 || +// // second request +// surrogate_keys.key_count() == 94 +// ); + +// true +// }) +// .expect(2) // 300 keys below +// .with_status(200) +// .create_async() +// .await; + +// let (_exporter, meter_provider) = setup_test_meter_provider(); +// let metrics = CdnMetrics::new(&meter_provider); + +// let keys: Vec<_> = (0..350) +// .map(|n| SurrogateKey::from_str(&format!("crate-foo-{n}")).unwrap()) +// .collect(); + +// purge_surrogate_keys(&config, &metrics, keys).await?; + +// m.assert_async().await; + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_fastly/src/metrics.rs b/crates/docs_rs_fastly/src/metrics.rs new file mode 100644 index 000000000..f160d1693 --- /dev/null +++ b/crates/docs_rs_fastly/src/metrics.rs @@ -0,0 +1,40 @@ +use docs_rs_opentelemetry::AnyMeterProvider; +use opentelemetry::metrics::{Counter, Gauge}; + +#[derive(Debug)] +pub struct CdnMetrics { + pub(crate) batch_purges_with_surrogate: Counter, + pub(crate) batch_purge_errors: Counter, + pub(crate) purge_surrogate_keys: Counter, + pub(crate) rate_limit_remaining: Gauge, + pub(crate) time_until_rate_limit_reset: Gauge, +} + +impl CdnMetrics { + pub fn new(meter_provider: &AnyMeterProvider) -> Self { + let meter = meter_provider.meter("cdn"); + const PREFIX: &str = "docsrs.cdn"; + Self { + batch_purges_with_surrogate: meter + .u64_counter(format!("{PREFIX}.fastly_batch_purges_with_surrogate")) + .with_unit("1") + .build(), + batch_purge_errors: meter + .u64_counter(format!("{PREFIX}.fastly_batch_purge_errors")) + .with_unit("1") + .build(), + purge_surrogate_keys: meter + .u64_counter(format!("{PREFIX}.fastly_purge_surrogate_keys")) + .with_unit("1") + .build(), + rate_limit_remaining: meter + .u64_gauge(format!("{PREFIX}.fasty_rate_limit_remaining")) + .with_unit("1") + .build(), + time_until_rate_limit_reset: meter + .u64_gauge(format!("{PREFIX}.fastly_time_until_rate_limit_reset")) + .with_unit("s") + .build(), + } + } +} diff --git a/crates/docs_rs_headers/Cargo.toml b/crates/docs_rs_headers/Cargo.toml index 2ffaea105..385f27641 100644 --- a/crates/docs_rs_headers/Cargo.toml +++ b/crates/docs_rs_headers/Cargo.toml @@ -6,3 +6,12 @@ edition = "2024" [dependencies] md5 = "0.8.0" headers = "0.4.1" # not sure if we want this in this crate +http = { workspace = true } +derive_more = { workspace = true } +serde = { workspace = true } +anyhow = { workspace = true } +itertools = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } +docs_rs_utils = { path = "../docs_rs_utils" } +docs_rs_web_utils = { path = "../docs_rs_web_utils" } +askama = { workspace = true } diff --git a/src/web/headers/canonical_url.rs b/crates/docs_rs_headers/src/canonical_url.rs similarity index 51% rename from src/web/headers/canonical_url.rs rename to crates/docs_rs_headers/src/canonical_url.rs index c7560c807..a0dff324b 100644 --- a/src/web/headers/canonical_url.rs +++ b/crates/docs_rs_headers/src/canonical_url.rs @@ -1,8 +1,8 @@ -use crate::web::escaped_uri::EscapedURI; use anyhow::Result; use askama::filters::HtmlSafe; -use axum::http::uri::Uri; -use axum_extra::headers::{Header, HeaderName, HeaderValue}; +use docs_rs_web_utils::escaped_uri::EscapedURI; +use headers::{Header, HeaderName, HeaderValue}; +use http::uri::Uri; use serde::Serialize; use std::{fmt, ops::Deref}; @@ -39,7 +39,7 @@ impl Header for CanonicalUrl { &http::header::LINK } - fn decode<'i, I>(_values: &mut I) -> Result + fn decode<'i, I>(_values: &mut I) -> Result where I: Iterator, { @@ -93,62 +93,62 @@ impl Deref for CanonicalUrl { impl HtmlSafe for CanonicalUrl {} -#[cfg(test)] -mod tests { - use super::*; - - use axum::http::HeaderMap; - use axum_extra::headers::HeaderMapExt; - - #[test] - fn test_serialize_canonical_from_uri() { - let url = CanonicalUrl::from_uri(EscapedURI::from_uri( - Uri::builder() - .scheme("https") - .authority("some_server.org") - .path_and_query("/some/path.html") - .build() - .unwrap(), - )); - - assert_eq!( - serde_json::to_string(&url).unwrap(), - "\"https://some_server.org/some/path.html\"" - ); - } - - #[test] - fn test_serialize_canonical() { - let url = CanonicalUrl::from_uri("/some/path/".parse::().unwrap().into()); - - assert_eq!( - serde_json::to_string(&url).unwrap(), - "\"https://docs.rs/some/path/\"" - ); - } - - #[test] - fn test_encode_canonical() { - let mut map = HeaderMap::new(); - map.typed_insert(CanonicalUrl::from_uri( - "/some/path/".parse::().unwrap().into(), - )); - assert_eq!( - map["link"], - "; rel=\"canonical\"" - ); - } - - #[test] - fn test_encode_canonical_with_encoding() { - // umlauts are allowed in http::Uri, but we still want to encode them. - let mut map = HeaderMap::new(); - map.typed_insert(CanonicalUrl::from_uri( - "/some/äöü/".parse::().unwrap().into(), - )); - assert_eq!( - map["link"], - "; rel=\"canonical\"" - ); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; + +// use axum::http::HeaderMap; +// use headers::HeaderMapExt; + +// #[test] +// fn test_serialize_canonical_from_uri() { +// let url = CanonicalUrl::from_uri(EscapedURI::from_uri( +// Uri::builder() +// .scheme("https") +// .authority("some_server.org") +// .path_and_query("/some/path.html") +// .build() +// .unwrap(), +// )); + +// assert_eq!( +// serde_json::to_string(&url).unwrap(), +// "\"https://some_server.org/some/path.html\"" +// ); +// } + +// #[test] +// fn test_serialize_canonical() { +// let url = CanonicalUrl::from_uri("/some/path/".parse::().unwrap().into()); + +// assert_eq!( +// serde_json::to_string(&url).unwrap(), +// "\"https://docs.rs/some/path/\"" +// ); +// } + +// #[test] +// fn test_encode_canonical() { +// let mut map = HeaderMap::new(); +// map.typed_insert(CanonicalUrl::from_uri( +// "/some/path/".parse::().unwrap().into(), +// )); +// assert_eq!( +// map["link"], +// "; rel=\"canonical\"" +// ); +// } + +// #[test] +// fn test_encode_canonical_with_encoding() { +// // umlauts are allowed in http::Uri, but we still want to encode them. +// let mut map = HeaderMap::new(); +// map.typed_insert(CanonicalUrl::from_uri( +// "/some/äöü/".parse::().unwrap().into(), +// )); +// assert_eq!( +// map["link"], +// "; rel=\"canonical\"" +// ); +// } +// } diff --git a/crates/docs_rs_headers/src/if_none_match.rs b/crates/docs_rs_headers/src/if_none_match.rs new file mode 100644 index 000000000..34b2046ec --- /dev/null +++ b/crates/docs_rs_headers/src/if_none_match.rs @@ -0,0 +1,168 @@ +//! Adapted version of `headers::IfNoneMatch`. +//! +//! The combination of `TypedHeader` and `IfNoneMatch` works in odd ways. +//! They are built in a way that a _missing_ `If-None-Match` header will lead to: +//! +//! 1. extractor with `TypedHeader` returning `IfNoneMatch("")` +//! 2. extractor with `Option>` returning `Some(IfNoneMatch(""))` +//! +//! Where I would expect: +//! 1. a failure because of the missing header +//! 2. `None` for the missing header +//! +//! This could be solved by either adapting `TypedHeader` or `IfNoneMatch`, I'm not sure which is +//! right. +//! +//! Some reading material for those interested: +//! * https://github.com/hyperium/headers/issues/204 +//! * https://github.com/hyperium/headers/pull/165 +//! * https://github.com/tokio-rs/axum/issues/1781 +//! * https://github.com/tokio-rs/axum/pull/1810 +//! * https://github.com/tokio-rs/axum/pull/2475 +//! +//! Right now I feel like adapting `IfNoneMatch` is the "most correct-ish" option. + +use derive_more::Deref; +use headers::{self, ETag, Header, IfNoneMatch as OriginalIfNoneMatch}; + +#[derive(Debug, Clone, PartialEq, Deref)] +pub struct IfNoneMatch(pub headers::IfNoneMatch); + +impl Header for IfNoneMatch { + fn name() -> &'static http::HeaderName { + OriginalIfNoneMatch::name() + } + + fn decode<'i, I>(values: &mut I) -> Result + where + Self: Sized, + I: Iterator, + { + let mut values = values.peekable(); + + // NOTE: this is the difference to the original implementation. + // When there is no header in the request, I want the decoding to fail. + // This makes Option> return `None`, and also matches + // most other header implementations. + if values.peek().is_none() { + Err(headers::Error::invalid()) + } else { + OriginalIfNoneMatch::decode(&mut values).map(IfNoneMatch) + } + } + + fn encode>(&self, values: &mut E) { + self.0.encode(values) + } +} + +impl From for IfNoneMatch { + fn from(value: ETag) -> Self { + Self(value.into()) + } +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use anyhow::Result; +// use axum::{RequestPartsExt, body::Body, extract::Request}; +// use axum_extra::{ +// TypedHeader, +// headers::{ETag, HeaderMapExt as _}, +// }; +// use http::{HeaderMap, request}; + +// fn parts(if_none_match: Option) -> request::Parts { +// let mut builder = Request::builder(); + +// if let Some(if_none_match) = if_none_match { +// let headers = builder.headers_mut().unwrap(); +// headers.typed_insert(if_none_match.clone()); +// } + +// let (parts, _body) = builder.uri("/").body(Body::empty()).unwrap().into_parts(); + +// parts +// } + +// fn example_header() -> IfNoneMatch { +// IfNoneMatch::from("\"some-etag-value\"".parse::().unwrap()) +// } + +// #[test] +// fn test_normal_typed_get_with_empty_headers() { +// let map = HeaderMap::new(); +// assert!(map.typed_get::().is_none()); +// assert!(map.typed_try_get::().unwrap().is_none()); +// } + +// #[test] +// fn test_normal_typed_get_with_value_headers() -> Result<()> { +// let if_none_match = example_header(); + +// let mut map = HeaderMap::new(); +// map.typed_insert(if_none_match.clone()); + +// assert_eq!(map.typed_get::(), Some(if_none_match.clone())); +// assert_eq!(map.typed_try_get::()?, Some(if_none_match)); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_extract_from_empty_request_via_optional_typed_header() -> Result<()> { +// let mut parts = parts(None); + +// assert!( +// parts +// .extract::>>() +// .await? +// // this is what we want, and the default `headers::IfNoneMatch` header can't +// // offer. Or the impl of the `TypedHeader` extractor, depending on +// // interpretation. +// .is_none() +// ); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_extract_from_empty_request_via_mandatory_typed_header() -> Result<()> { +// let mut parts = parts(None); + +// // mandatory extractor leads to error when the header is missing. +// assert!(parts.extract::>().await.is_err()); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_extract_from_header_via_optional_typed_header() -> Result<()> { +// let if_none_match = example_header(); +// let mut parts = parts(Some(if_none_match.clone())); + +// assert_eq!( +// parts +// .extract::>>() +// .await? +// .map(|th| th.0), +// Some(if_none_match) +// ); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_extract_from_header_via_mandatory_typed_header() -> Result<()> { +// let if_none_match = example_header(); +// let mut parts = parts(Some(if_none_match.clone())); + +// assert_eq!( +// parts.extract::>().await?.0, +// if_none_match +// ); + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_headers/src/lib.rs b/crates/docs_rs_headers/src/lib.rs index 97f979113..558dfce98 100644 --- a/crates/docs_rs_headers/src/lib.rs +++ b/crates/docs_rs_headers/src/lib.rs @@ -1,2 +1,18 @@ pub mod etag; pub use headers::ETag; + +mod canonical_url; +mod if_none_match; +mod surrogate_key; + +pub use canonical_url::CanonicalUrl; +use http::HeaderName; +pub use if_none_match::IfNoneMatch; +pub use surrogate_key::{SURROGATE_KEY, SurrogateKey, SurrogateKeys}; + +/// Fastly's Surrogate-Control header +/// https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Control/ +pub static SURROGATE_CONTROL: HeaderName = HeaderName::from_static("surrogate-control"); + +/// X-Robots-Tag header for search engines. +pub static X_ROBOTS_TAG: HeaderName = HeaderName::from_static("x-robots-tag"); diff --git a/src/web/headers/surrogate_key.rs b/crates/docs_rs_headers/src/surrogate_key.rs similarity index 98% rename from src/web/headers/surrogate_key.rs rename to crates/docs_rs_headers/src/surrogate_key.rs index e1e70871d..3f606ab62 100644 --- a/src/web/headers/surrogate_key.rs +++ b/crates/docs_rs_headers/src/surrogate_key.rs @@ -3,14 +3,13 @@ //! https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Key/haeders.surrogate keys use anyhow::{Context as _, bail}; -use axum_extra::headers::{self, Header}; use derive_more::Deref; +use docs_rs_database::types::krate_name::KrateName; +use headers::{self, Header}; use http::{HeaderName, HeaderValue}; use itertools::Itertools as _; use std::{fmt::Display, iter, str::FromStr}; -use crate::db::types::krate_name::KrateName; - pub static SURROGATE_KEY: HeaderName = HeaderName::from_static("surrogate-key"); /// a single surrogate key. diff --git a/crates/docs_rs_watcher/src/build_queue.rs b/crates/docs_rs_watcher/src/build_queue.rs new file mode 100644 index 000000000..6d8a808a3 --- /dev/null +++ b/crates/docs_rs_watcher/src/build_queue.rs @@ -0,0 +1,152 @@ +use std::sync::Arc; + +use crate::{index::Index, priorities::get_crate_priority}; +use anyhow::{Context as _, Result}; +use docs_rs_build_queue::AsyncBuildQueue; +use docs_rs_database::{ + service_config::{ConfigName, get_config, set_config}, + types::version::Version, +}; +use tracing::{debug, error, info, warn}; + +pub async fn last_seen_reference( + conn: &mut sqlx::PgConnection, +) -> Result> { + if let Some(value) = get_config::(conn, ConfigName::LastSeenIndexReference).await? { + return Ok(Some(crates_index_diff::gix::ObjectId::from_hex( + value.as_bytes(), + )?)); + } + Ok(None) +} + +pub async fn set_last_seen_reference( + conn: &mut sqlx::PgConnection, + oid: crates_index_diff::gix::ObjectId, +) -> Result<()> { + set_config(conn, ConfigName::LastSeenIndexReference, oid.to_string()).await?; + Ok(()) +} + +pub async fn get_new_crates( + conn: &mut sqlx::PgConnection, + index: &Index, + build_queue: Arc, +) -> Result { + let last_seen_reference = last_seen_reference(conn).await?; + let last_seen_reference = if let Some(oid) = last_seen_reference { + oid + } else { + warn!( + "no last-seen reference found in our database. We assume a fresh install and + set the latest reference (HEAD) as last. This means we will then start to queue + builds for new releases only from now on, and not for all existing releases." + ); + index.latest_commit_reference().await? + }; + + index.set_last_seen_reference(last_seen_reference).await?; + + let (changes, new_reference) = index.peek_changes_ordered().await?; + + let mut crates_added = 0; + + debug!("queueing changes from {last_seen_reference} to {new_reference}"); + + for change in &changes { + if let Some((ref krate, ..)) = change.crate_deleted() { + match delete_crate(&mut conn, &storage, &config, krate).await { + Ok(_) => info!( + "crate {} was deleted from the index and the database", + krate + ), + Err(err) => { + // FIXME: worth going back to report_error here? + error!(?err, krate, "failed to delete crate"); + } + }; + + queue_crate_invalidation(krate).await; + build_queue.remove_crate_from_queue(krate).await?; + continue; + } + + if let Some(release) = change.version_deleted() { + let version: Version = release + .version + .parse() + .context("couldn't parse release version as semver")?; + + match delete_version(&mut conn, &storage, &config, &release.name, &version).await { + Ok(_) => info!( + "release {}-{} was deleted from the index and the database", + release.name, release.version + ), + Err(err) => { + error!(?err, %release.name, %release.version, "failed to delete version") + } + } + + queue_crate_invalidation(&release.name).await; + build_queue + .remove_version_from_queue(&release.name, &version) + .await?; + continue; + } + + if let Some(release) = change.added() { + let priority = get_crate_priority(&mut conn, &release.name).await?; + + match build_queue + .add_crate( + &release.name, + &release + .version + .parse() + .context("couldn't parse release version as semver")?, + priority, + index.repository_url(), + ) + .await + { + Ok(()) => { + debug!( + "{}-{} added into build queue", + release.name, release.version + ); + crates_added += 1; + } + Err(err) => { + error!(?err, %release.name, %release.version, "failed adding release build queue"); + } + } + } + + let yanked = change.yanked(); + let unyanked = change.unyanked(); + if let Some(release) = yanked.or(unyanked) { + // FIXME: delay yanks of crates that have not yet finished building + // https://github.com/rust-lang/docs.rs/issues/1934 + if let Ok(release_version) = Version::parse(&release.version) + && let Err(err) = set_yanked_inner( + &mut conn, + release.name.as_str(), + &release_version, + yanked.is_some(), + ) + .await + { + error!(?err, %release.name, %release.version, "error setting yanked status"); + } + + queue_crate_invalidation(&release.name).await; + } + } + + // set the reference in the database + // so this survives recreating the registry watcher + // server. + set_last_seen_reference(conn, new_reference).await?; + + Ok(crates_added) +} diff --git a/crates/docs_rs_watcher/src/db/delete.rs b/crates/docs_rs_watcher/src/db/delete.rs new file mode 100644 index 000000000..e52084cfa --- /dev/null +++ b/crates/docs_rs_watcher/src/db/delete.rs @@ -0,0 +1,557 @@ +use anyhow::Context as _; +use docs_rs_database::types::{CrateId, version::Version}; +use docs_rs_storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}; +use fn_error_context::context; +use sqlx::Connection; + +use super::update_latest_version_id; + +/// List of directories in docs.rs's underlying storage (either the database or S3) containing a +/// subdirectory named after the crate. Those subdirectories will be deleted. +static LIBRARY_STORAGE_PATHS_TO_DELETE: &[&str] = &["rustdoc", "rustdoc-json", "sources"]; +static OTHER_STORAGE_PATHS_TO_DELETE: &[&str] = &["sources"]; + +#[context("error trying to delete crate {name} from database")] +pub async fn delete_crate( + conn: &mut sqlx::PgConnection, + storage: &AsyncStorage, + config: &Config, + name: &str, +) -> Result<()> { + let Some(crate_id) = get_id(conn, name).await? else { + return Ok(()); + }; + + let is_library = delete_crate_from_database(conn, name, crate_id).await?; + // #899 + let paths = if is_library { + LIBRARY_STORAGE_PATHS_TO_DELETE + } else { + OTHER_STORAGE_PATHS_TO_DELETE + }; + + for prefix in paths { + // delete the whole rustdoc/source folder for this crate. + // it will include existing archives. + let remote_folder = format!("{prefix}/{name}/"); + storage.delete_prefix(&remote_folder).await?; + + // remove existing local archive index files. + let local_index_folder = config.storage.local_archive_cache_path.join(&remote_folder); + if local_index_folder.exists() { + tokio::fs::remove_dir_all(&local_index_folder) + .await + .with_context(|| { + format!( + "error when trying to remove local index: {:?}", + &local_index_folder + ) + })?; + } + } + + Ok(()) +} + +#[context("error trying to delete release {name}-{version} from database")] +pub async fn delete_version( + conn: &mut sqlx::PgConnection, + storage: &AsyncStorage, + config: &Config, + name: &str, + version: &Version, +) -> Result<()> { + let Some(crate_id) = get_id(conn, name).await? else { + return Ok(()); + }; + + let is_library = delete_version_from_database(conn, crate_id, version).await?; + let paths = if is_library { + LIBRARY_STORAGE_PATHS_TO_DELETE + } else { + OTHER_STORAGE_PATHS_TO_DELETE + }; + + for prefix in paths { + storage + .delete_prefix(&format!("{prefix}/{name}/{version}/")) + .await?; + } + + let local_archive_cache = &config.storage.local_archive_cache_path; + let mut paths = vec![source_archive_path(name, version)]; + if is_library { + paths.push(rustdoc_archive_path(name, version)); + } + + for archive_filename in paths { + // delete remove archive and remote index + storage.delete_prefix(&archive_filename).await?; + + // delete eventually existing local indexes + let local_index_file = local_archive_cache.join(format!("{archive_filename}.index")); + if local_index_file.exists() { + tokio::fs::remove_file(&local_index_file) + .await + .with_context(|| { + format!("error when trying to remove local index: {local_index_file:?}") + })?; + } + } + + Ok(()) +} + +async fn get_id(conn: &mut sqlx::PgConnection, name: &str) -> Result> { + Ok(sqlx::query_scalar!( + r#" + SELECT id as "id: CrateId" + FROM crates + WHERE normalize_crate_name(name) = normalize_crate_name($1) + "#, + name + ) + .fetch_optional(&mut *conn) + .await?) +} + +// metaprogramming! +// WARNING: these must be hard-coded and NEVER user input. +const METADATA: &[(&str, &str)] = &[ + ("keyword_rels", "rid"), + ("builds", "rid"), + ("compression_rels", "release"), + ("doc_coverage", "release_id"), +]; + +/// Returns whether this release was a library +async fn delete_version_from_database( + conn: &mut sqlx::PgConnection, + crate_id: CrateId, + version: &Version, +) -> Result { + let mut transaction = conn.begin().await?; + for &(table, column) in METADATA { + sqlx::query( + format!("DELETE FROM {table} WHERE {column} IN (SELECT id FROM releases WHERE crate_id = $1 AND version = $2)").as_str()) + .bind(crate_id).bind(version).execute(&mut *transaction).await?; + } + let is_library: bool = sqlx::query_scalar!( + "DELETE FROM releases WHERE crate_id = $1 AND version = $2 RETURNING is_library", + crate_id.0, + version as _, + ) + .fetch_one(&mut *transaction) + .await? + .unwrap_or(false); + + update_latest_version_id(&mut transaction, crate_id).await?; + + transaction.commit().await?; + Ok(is_library) +} + +/// Returns whether any release in this crate was a library +async fn delete_crate_from_database( + conn: &mut sqlx::PgConnection, + name: &str, + crate_id: CrateId, +) -> Result { + let mut transaction = conn.begin().await?; + + sqlx::query!("DELETE FROM sandbox_overrides WHERE crate_name = $1", name,) + .execute(&mut *transaction) + .await?; + + for &(table, column) in METADATA { + sqlx::query( + format!( + "DELETE FROM {table} WHERE {column} IN (SELECT id FROM releases WHERE crate_id = $1)" + ) + .as_str()).bind(crate_id).execute(&mut *transaction).await?; + } + sqlx::query!("DELETE FROM owner_rels WHERE cid = $1;", crate_id.0) + .execute(&mut *transaction) + .await?; + + let has_library: bool = sqlx::query_scalar!( + "SELECT + BOOL_OR(releases.is_library) AS has_library + FROM releases + WHERE releases.crate_id = $1 + ", + crate_id.0 + ) + .fetch_one(&mut *transaction) + .await? + .unwrap_or(false); + + sqlx::query!("DELETE FROM releases WHERE crate_id = $1;", crate_id.0) + .execute(&mut *transaction) + .await?; + sqlx::query!("DELETE FROM crates WHERE id = $1;", crate_id.0) + .execute(&mut *transaction) + .await?; + + // Transactions automatically rollback when not committing, so if any of the previous queries + // fail the whole transaction will be aborted. + transaction.commit().await?; + Ok(has_library) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::db::ReleaseId; + use crate::registry_api::{CrateOwner, OwnerKind}; + use crate::storage::{CompressionAlgorithm, rustdoc_json_path}; + use crate::test::{KRATE, V1, V2, async_wrapper, fake_release_that_failed_before_build}; + use test_case::test_case; + + async fn crate_exists(conn: &mut sqlx::PgConnection, name: &str) -> Result { + Ok(sqlx::query!("SELECT id FROM crates WHERE name = $1;", name) + .fetch_optional(conn) + .await? + .is_some()) + } + + async fn release_exists(conn: &mut sqlx::PgConnection, id: ReleaseId) -> Result { + Ok(sqlx::query!("SELECT id FROM releases WHERE id = $1;", id.0) + .fetch_optional(conn) + .await? + .is_some()) + } + + #[test] + fn test_get_id_uses_normalization() { + async_wrapper(|env| async move { + env.fake_release() + .await + .name("Some_Package") + .version(V1) + .create() + .await?; + + let mut conn = env.async_db().async_conn().await; + assert!(get_id(&mut conn, "some-package").await.is_ok()); + + Ok(()) + }) + } + + #[test_case(true)] + #[test_case(false)] + fn test_delete_crate(archive_storage: bool) { + async_wrapper(|env| async move { + let mut conn = env.async_db().async_conn().await; + + // Create fake packages in the database + let pkg1_v1_id = env + .fake_release() + .await + .name("package-1") + .version(V1) + .archive_storage(archive_storage) + .create() + .await?; + let pkg1_v2_id = env + .fake_release() + .await + .name("package-1") + .version(V2) + .archive_storage(archive_storage) + .create() + .await?; + let pkg2_id = env + .fake_release() + .await + .name("package-2") + .version(V1) + .archive_storage(archive_storage) + .create() + .await?; + + assert!(crate_exists(&mut conn, "package-1").await?); + assert!(crate_exists(&mut conn, "package-2").await?); + assert!(release_exists(&mut conn, pkg1_v1_id).await?); + assert!(release_exists(&mut conn, pkg1_v2_id).await?); + assert!(release_exists(&mut conn, pkg2_id).await?); + for (pkg, version) in &[("package-1", V1), ("package-1", V2), ("package-2", V1)] { + assert!( + env.async_storage() + .rustdoc_file_exists( + pkg, + version, + None, + &format!("{pkg}/index.html"), + archive_storage + ) + .await? + ); + } + + delete_crate(&mut conn, env.async_storage(), env.config(), "package-1").await?; + + assert!(!crate_exists(&mut conn, "package-1").await?); + assert!(crate_exists(&mut conn, "package-2").await?); + assert!(!release_exists(&mut conn, pkg1_v1_id).await?); + assert!(!release_exists(&mut conn, pkg1_v2_id).await?); + assert!(release_exists(&mut conn, pkg2_id).await?); + + // files for package 2 still exists + assert!( + env.async_storage() + .rustdoc_file_exists( + "package-2", + &V1, + None, + "package-2/index.html", + archive_storage + ) + .await? + ); + + // files for package 1 are gone + if archive_storage { + assert!( + !env.async_storage() + .exists(&rustdoc_archive_path("package-1", &V1)) + .await? + ); + assert!( + !env.async_storage() + .exists(&rustdoc_archive_path("package-1", &V2)) + .await? + ); + } else { + assert!( + !env.async_storage() + .rustdoc_file_exists( + "package-1", + &V1, + None, + "package-1/index.html", + archive_storage + ) + .await? + ); + assert!( + !env.async_storage() + .rustdoc_file_exists( + "package-1", + &V2, + None, + "package-1/index.html", + archive_storage + ) + .await? + ); + } + + Ok(()) + }); + } + + #[test_case(true)] + #[test_case(false)] + fn test_delete_version(archive_storage: bool) { + async_wrapper(|env| async move { + async fn owners( + conn: &mut sqlx::PgConnection, + crate_id: CrateId, + ) -> Result> { + Ok(sqlx::query!( + "SELECT login FROM owners + INNER JOIN owner_rels ON owners.id = owner_rels.oid + WHERE owner_rels.cid = $1", + crate_id.0, + ) + .fetch_all(conn) + .await? + .into_iter() + .map(|row| row.login) + .collect()) + } + + async fn json_exists(storage: &AsyncStorage, version: &Version) -> Result { + storage + .exists(&rustdoc_json_path( + "a", + version, + "x86_64-unknown-linux-gnu", + crate::storage::RustdocJsonFormatVersion::Latest, + Some(CompressionAlgorithm::Zstd), + )) + .await + } + + let mut conn = env.async_db().async_conn().await; + let v1 = env + .fake_release() + .await + .name("a") + .version(V1) + .archive_storage(archive_storage) + .add_owner(CrateOwner { + login: "malicious actor".into(), + avatar: "https://example.org/malicious".into(), + kind: OwnerKind::User, + }) + .create() + .await?; + assert!(release_exists(&mut conn, v1).await?); + assert!( + env.async_storage() + .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) + .await? + ); + assert!(json_exists(env.async_storage(), &V1).await?); + let crate_id = sqlx::query_scalar!( + r#"SELECT crate_id as "crate_id: CrateId" FROM releases WHERE id = $1"#, + v1.0 + ) + .fetch_one(&mut *conn) + .await?; + assert_eq!( + owners(&mut conn, crate_id).await?, + vec!["malicious actor".to_string()] + ); + + let v2 = env + .fake_release() + .await + .name("a") + .version(V2) + .archive_storage(archive_storage) + .add_owner(CrateOwner { + login: "Peter Rabbit".into(), + avatar: "https://example.org/peter".into(), + kind: OwnerKind::User, + }) + .create() + .await?; + assert!(release_exists(&mut conn, v2).await?); + assert!( + env.async_storage() + .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) + .await? + ); + assert!(json_exists(env.async_storage(), &V2).await?); + assert_eq!( + owners(&mut conn, crate_id).await?, + vec!["Peter Rabbit".to_string()] + ); + + delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; + assert!(!release_exists(&mut conn, v1).await?); + if archive_storage { + // for archive storage the archive and index files + // need to be cleaned up. + let rustdoc_archive = rustdoc_archive_path("a", &V1); + assert!(!env.async_storage().exists(&rustdoc_archive).await?); + + // local and remote index are gone too + let archive_index = format!("{rustdoc_archive}.index"); + assert!(!env.async_storage().exists(&archive_index).await?); + assert!( + !env.config() + .local_archive_cache_path + .join(&archive_index) + .exists() + ); + } else { + assert!( + !env.async_storage() + .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) + .await? + ); + } + assert!(!json_exists(env.async_storage(), &V1,).await?); + + assert!(release_exists(&mut conn, v2).await?); + assert!( + env.async_storage() + .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) + .await? + ); + assert!(json_exists(env.async_storage(), &V2).await?); + assert_eq!( + owners(&mut conn, crate_id).await?, + vec!["Peter Rabbit".to_string()] + ); + + // FIXME: remove for now until test frontend is async + // let web = env.frontend(); + // assert_success("/a/2.0.0/a/", web)?; + // assert_eq!(web.get("/a/1.0.0/a/").send()?.status(), 404); + + Ok(()) + }) + } + + #[test] + fn test_delete_incomplete_version() { + async_wrapper(|env| async move { + let db = env.async_db(); + let mut conn = db.async_conn().await; + + let (release_id, _) = + fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; + + delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; + + assert!(!release_exists(&mut conn, release_id).await?); + + Ok(()) + }) + } + + #[test] + fn test_delete_incomplete_crate() { + async_wrapper(|env| async move { + let db = env.async_db(); + let mut conn = db.async_conn().await; + + let (release_id, _) = + fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; + + delete_crate(&mut conn, env.async_storage(), env.config(), "a").await?; + + assert!(!crate_exists(&mut conn, "a").await?); + assert!(!release_exists(&mut conn, release_id).await?); + + Ok(()) + }) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_delete_missing_crate_doesnt_error() -> Result<()> { + let env = crate::test::TestEnvironment::new().await?; + + let db = env.async_db(); + let mut conn = db.async_conn().await; + + assert!(!crate_exists(&mut conn, KRATE).await?); + delete_crate(&mut conn, env.async_storage(), env.config(), KRATE).await?; + + assert!(!crate_exists(&mut conn, KRATE).await?); + + Ok(()) + } + + #[tokio::test(flavor = "multi_thread")] + async fn test_delete_missing_version_doesnt_error() -> Result<()> { + let env = crate::test::TestEnvironment::new().await?; + + let db = env.async_db(); + let mut conn = db.async_conn().await; + + assert!(!crate_exists(&mut conn, KRATE).await?); + + delete_version(&mut conn, env.async_storage(), env.config(), KRATE, &V1).await?; + + assert!(!crate_exists(&mut conn, KRATE).await?); + + Ok(()) + } +} diff --git a/crates/docs_rs_watcher/src/db/mod.rs b/crates/docs_rs_watcher/src/db/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index 585edb579..77f74c8a2 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -1,6 +1,8 @@ +mod build_queue; mod config; mod consistency; mod index; +mod priorities; pub mod repositories; pub mod service_metrics; mod utils; diff --git a/crates/docs_rs_watcher/src/priorities.rs b/crates/docs_rs_watcher/src/priorities.rs new file mode 100644 index 000000000..4b530acc3 --- /dev/null +++ b/crates/docs_rs_watcher/src/priorities.rs @@ -0,0 +1,198 @@ +use anyhow::Result; +use docs_rs_build_queue::PRIORITY_DEFAULT; +use futures_util::stream::TryStreamExt; + +/// Get the build queue priority for a crate, returns the matching pattern too +pub async fn list_crate_priorities(conn: &mut sqlx::PgConnection) -> Result> { + Ok( + sqlx::query!("SELECT pattern, priority FROM crate_priorities") + .fetch(conn) + .map_ok(|r| (r.pattern, r.priority)) + .try_collect() + .await?, + ) +} + +/// Get the build queue priority for a crate with its matching pattern +pub async fn get_crate_pattern_and_priority( + conn: &mut sqlx::PgConnection, + name: &str, +) -> Result> { + // Search the `priority` table for a priority where the crate name matches the stored pattern + Ok(sqlx::query!( + "SELECT pattern, priority FROM crate_priorities WHERE $1 LIKE pattern LIMIT 1", + name + ) + .fetch_optional(&mut *conn) + .await? + .map(|row| (row.pattern, row.priority))) +} + +/// Get the build queue priority for a crate +pub async fn get_crate_priority(conn: &mut sqlx::PgConnection, name: &str) -> Result { + Ok(get_crate_pattern_and_priority(conn, name) + .await? + .map_or(PRIORITY_DEFAULT, |(_, priority)| priority)) +} + +/// Set all crates that match [`pattern`] to have a certain priority +/// +/// Note: `pattern` is used in a `LIKE` statement, so it must follow the postgres like syntax +/// +/// [`pattern`]: https://www.postgresql.org/docs/8.3/functions-matching.html +pub async fn set_crate_priority( + conn: &mut sqlx::PgConnection, + pattern: &str, + priority: i32, +) -> Result<()> { + sqlx::query!( + "INSERT INTO crate_priorities (pattern, priority) VALUES ($1, $2)", + pattern, + priority, + ) + .execute(&mut *conn) + .await?; + + Ok(()) +} + +/// Remove a pattern from the priority table, returning the priority that it was associated with or `None` +/// if nothing was removed +pub async fn remove_crate_priority( + conn: &mut sqlx::PgConnection, + pattern: &str, +) -> Result> { + Ok(sqlx::query_scalar!( + "DELETE FROM crate_priorities WHERE pattern = $1 RETURNING priority", + pattern, + ) + .fetch_optional(&mut *conn) + .await?) +} + +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::async_wrapper; + +// #[test] +// fn set_priority() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// set_crate_priority(&mut conn, "docsrs-%", -100).await?; +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs-database").await?, +// -100 +// ); +// assert_eq!(get_crate_priority(&mut conn, "docsrs-").await?, -100); +// assert_eq!(get_crate_priority(&mut conn, "docsrs-s3").await?, -100); +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs-webserver").await?, +// -100 +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs").await?, +// PRIORITY_DEFAULT +// ); + +// set_crate_priority(&mut conn, "_c_", 100).await?; +// assert_eq!(get_crate_priority(&mut conn, "rcc").await?, 100); +// assert_eq!(get_crate_priority(&mut conn, "rc").await?, PRIORITY_DEFAULT); + +// set_crate_priority(&mut conn, "hexponent", 10).await?; +// assert_eq!(get_crate_priority(&mut conn, "hexponent").await?, 10); +// assert_eq!( +// get_crate_priority(&mut conn, "hexponents").await?, +// PRIORITY_DEFAULT +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "floathexponent").await?, +// PRIORITY_DEFAULT +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn remove_priority() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// set_crate_priority(&mut conn, "docsrs-%", -100).await?; +// assert_eq!(get_crate_priority(&mut conn, "docsrs-").await?, -100); + +// assert_eq!( +// remove_crate_priority(&mut conn, "docsrs-%").await?, +// Some(-100) +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs-").await?, +// PRIORITY_DEFAULT +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn get_priority() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// set_crate_priority(&mut conn, "docsrs-%", -100).await?; + +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs-database").await?, +// -100 +// ); +// assert_eq!(get_crate_priority(&mut conn, "docsrs-").await?, -100); +// assert_eq!(get_crate_priority(&mut conn, "docsrs-s3").await?, -100); +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs-webserver").await?, +// -100 +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "unrelated").await?, +// PRIORITY_DEFAULT +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn get_default_priority() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// assert_eq!( +// get_crate_priority(&mut conn, "docsrs").await?, +// PRIORITY_DEFAULT +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "rcc").await?, +// PRIORITY_DEFAULT +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "lasso").await?, +// PRIORITY_DEFAULT +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "hexponent").await?, +// PRIORITY_DEFAULT +// ); +// assert_eq!( +// get_crate_priority(&mut conn, "rust4lyfe").await?, +// PRIORITY_DEFAULT +// ); + +// Ok(()) +// }) +// } +// } diff --git a/crates/docs_rs_web_utils/Cargo.toml b/crates/docs_rs_web_utils/Cargo.toml new file mode 100644 index 000000000..6b484b5e9 --- /dev/null +++ b/crates/docs_rs_web_utils/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "docs_rs_web_utils" +version = "0.1.0" +edition = "2024" + +[dependencies] +percent-encoding = "2.2.0" +anyhow = { workspace = true } +askama = { workspace = true } +http = { workspace = true } +url = { workspace = true } +bincode = { workspace = true } diff --git a/src/web/escaped_uri.rs b/crates/docs_rs_web_utils/src/escaped_uri.rs similarity index 99% rename from src/web/escaped_uri.rs rename to crates/docs_rs_web_utils/src/escaped_uri.rs index 2e292365a..873a8e680 100644 --- a/src/web/escaped_uri.rs +++ b/crates/docs_rs_web_utils/src/escaped_uri.rs @@ -1,4 +1,4 @@ -use crate::web::{encode_url_path, url_decode}; +use super::{encode_url_path, url_decode}; use askama::filters::HtmlSafe; use http::{Uri, uri::PathAndQuery}; use std::{borrow::Borrow, fmt::Display, iter, str::FromStr}; diff --git a/crates/docs_rs_web_utils/src/lib.rs b/crates/docs_rs_web_utils/src/lib.rs new file mode 100644 index 000000000..f1ec5ac74 --- /dev/null +++ b/crates/docs_rs_web_utils/src/lib.rs @@ -0,0 +1,19 @@ +use std::borrow::Cow; + +use anyhow::Result; +use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode}; + +pub mod escaped_uri; + +// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs +// and https://github.com/tokio-rs/axum/blob/main/axum-extra/src/lib.rs +const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); +const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); + +pub(crate) fn encode_url_path(path: &str) -> String { + utf8_percent_encode(path, PATH).to_string() +} + +pub(crate) fn url_decode<'a>(input: &'a str) -> Result> { + Ok(percent_encoding::percent_decode(input.as_bytes()).decode_utf8()?) +} diff --git a/src/cdn/fastly.rs b/src/cdn/fastly.rs deleted file mode 100644 index 4eb063326..000000000 --- a/src/cdn/fastly.rs +++ /dev/null @@ -1,322 +0,0 @@ -use crate::{ - cdn::CdnMetrics, - config::Config, - web::headers::{SURROGATE_KEY, SurrogateKey, SurrogateKeys}, -}; -use anyhow::{Result, anyhow, bail}; -use chrono::{DateTime, TimeZone as _, Utc}; -use docs_rs_utils::APP_USER_AGENT; -use http::{ - HeaderMap, HeaderName, HeaderValue, - header::{ACCEPT, USER_AGENT}, -}; -use itertools::Itertools as _; -use opentelemetry::KeyValue; -use std::sync::OnceLock; -use tracing::error; - -const FASTLY_KEY: HeaderName = HeaderName::from_static("fastly-key"); - -// https://www.fastly.com/documentation/reference/api/#rate-limiting -const FASTLY_RATELIMIT_REMAINING: HeaderName = - HeaderName::from_static("fastly-ratelimit-remaining"); -const FASTLY_RATELIMIT_RESET: HeaderName = HeaderName::from_static("fastyly-ratelimit-reset"); - -static CLIENT: OnceLock> = OnceLock::new(); - -fn fastly_client(api_token: impl AsRef) -> anyhow::Result<&'static reqwest::Client> { - CLIENT - .get_or_init(|| -> Result<_> { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, HeaderValue::from_static(APP_USER_AGENT)); - headers.insert(ACCEPT, HeaderValue::from_static("application/json")); - headers.insert(FASTLY_KEY, HeaderValue::from_str(api_token.as_ref())?); - - Ok(reqwest::Client::builder() - .default_headers(headers) - .build()?) - }) - .as_ref() - .map_err(|err| anyhow!("reqwest Client init failed: {}", err)) -} - -fn fetch_rate_limit_state(headers: &HeaderMap) -> (Option, Option>) { - // https://www.fastly.com/documentation/reference/api/#rate-limiting - ( - headers - .get(FASTLY_RATELIMIT_REMAINING) - .and_then(|hv| hv.to_str().ok()) - .and_then(|s| s.parse().ok()), - headers - .get(FASTLY_RATELIMIT_RESET) - .and_then(|hv| hv.to_str().ok()) - .and_then(|s| s.parse::().ok()) - .and_then(|ts| Utc.timestamp_opt(ts, 0).single()), - ) -} - -/// Purge the given surrogate keys from all configured fastly services. -/// -/// Accepts any number of surrogate keys, and splits them into appropriately sized -/// batches for the Fastly API. -pub(crate) async fn purge_surrogate_keys( - config: &Config, - metrics: &CdnMetrics, - keys: I, -) -> Result<()> -where - I: IntoIterator, -{ - let Some(api_token) = &config.fastly_api_token else { - bail!("Fastly API token not configured"); - }; - - let client = fastly_client(api_token)?; - - let record_rate_limit_metrics = - |limit_remaining: Option, limit_reset: Option>| { - if let Some(limit_remaining) = limit_remaining { - metrics - .fastly_rate_limit_remaining - .record(limit_remaining, &[]); - } - - if let Some(limit_reset) = limit_reset { - metrics - .fastly_time_until_rate_limit_reset - .record((limit_reset - Utc::now()).num_seconds() as u64, &[]); - } - }; - - // the `bulk_purge_tag` supports up to 256 surrogate keys in its list, - // but I believe we also have to respect the length limits for the full - // surrogate key header we send in this purge request. - // see https://www.fastly.com/documentation/reference/api/purging/ - for encoded_surrogate_keys in keys.into_iter().batching(|it| { - const MAX_SURROGATE_KEYS_IN_BATCH_PURGE: usize = 256; - - // SurrogateKeys::from_iter::until_full only consumes as many elements as will fit into - // the header. - // The rest is up to the next `batching` iteration. - let keys = SurrogateKeys::from_iter_until_full(it.take(MAX_SURROGATE_KEYS_IN_BATCH_PURGE)); - - if keys.key_count() > 0 { - Some(keys) - } else { - None - } - }) { - if let Some(ref sid) = config.fastly_service_sid { - // NOTE: we start with just calling the API, and logging an error if they happen. - // We can then see if we need retries or escalation to full purges. - - let kv = [KeyValue::new("service_sid", sid.clone())]; - - // https://www.fastly.com/documentation/reference/api/purging/ - // TODO: investigate how they could help & test - // soft purge. But later, after the initial migration. - match client - .post( - config - .fastly_api_host - .join(&format!("/service/{}/purge", sid))?, - ) - .header(&SURROGATE_KEY, encoded_surrogate_keys.to_string()) - .send() - .await - { - Ok(response) if response.status().is_success() => { - metrics.fastly_batch_purges_with_surrogate.add(1, &kv); - metrics - .fastly_purge_surrogate_keys - .add(encoded_surrogate_keys.key_count() as u64, &kv); - - let (limit_remaining, limit_reset) = fetch_rate_limit_state(response.headers()); - record_rate_limit_metrics(limit_remaining, limit_reset); - } - Ok(error_response) => { - metrics.fastly_batch_purge_errors.add(1, &kv); - - let (limit_remaining, limit_reset) = - fetch_rate_limit_state(error_response.headers()); - record_rate_limit_metrics(limit_remaining, limit_reset); - - let limit_reset = limit_reset.map(|dt| dt.to_rfc3339()); - - let status = error_response.status(); - let content = error_response.text().await.unwrap_or_default(); - error!( - sid, - %status, - content, - %encoded_surrogate_keys, - rate_limit_remaining=limit_remaining, - rate_limit_reset=limit_reset, - "Failed to purge Fastly surrogate keys for service" - ); - } - Err(err) => { - // connection errors or similar, where we don't have a response - metrics.fastly_batch_purge_errors.add(1, &kv); - error!( - sid, - ?err, - %encoded_surrogate_keys, - "Failed to purge Fastly surrogate keys for service" - ); - } - }; - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{TestEnvironment, setup_test_meter_provider}; - use chrono::TimeZone; - use std::str::FromStr as _; - - #[test] - fn test_read_rate_limit() { - // https://www.fastly.com/documentation/reference/api/#rate-limiting - let mut hm = HeaderMap::new(); - hm.insert(FASTLY_RATELIMIT_REMAINING, HeaderValue::from_static("999")); - hm.insert( - FASTLY_RATELIMIT_RESET, - HeaderValue::from_static("1452032384"), - ); - - let (remaining, reset) = fetch_rate_limit_state(&hm); - assert_eq!(remaining, Some(999)); - assert_eq!( - reset, - Some(Utc.timestamp_opt(1452032384, 0).single().unwrap()) - ); - } - - #[tokio::test] - async fn test_purge() -> Result<()> { - let mut fastly_api = mockito::Server::new_async().await; - - let config = TestEnvironment::base_config() - .fastly_api_host(fastly_api.url().parse().unwrap()) - .fastly_api_token(Some("test-token".into())) - .fastly_service_sid(Some("test-sid-1".into())) - .build()?; - - let m = fastly_api - .mock("POST", "/service/test-sid-1/purge") - .match_header(FASTLY_KEY, "test-token") - .match_header(&SURROGATE_KEY, "crate-foo crate-bar") - .with_status(200) - .create_async() - .await; - - let (_exporter, meter_provider) = setup_test_meter_provider(); - let metrics = CdnMetrics::new(&meter_provider); - - purge_surrogate_keys( - &config, - &metrics, - vec![ - SurrogateKey::from_str("crate-foo").unwrap(), - SurrogateKey::from_str("crate-bar").unwrap(), - ], - ) - .await?; - - m.assert_async().await; - - Ok(()) - } - - #[tokio::test] - async fn test_purge_err_doesnt_err() -> Result<()> { - let mut fastly_api = mockito::Server::new_async().await; - - let config = TestEnvironment::base_config() - .fastly_api_host(fastly_api.url().parse().unwrap()) - .fastly_api_token(Some("test-token".into())) - .fastly_service_sid(Some("test-sid-1".into())) - .build()?; - - let m = fastly_api - .mock("POST", "/service/test-sid-1/purge") - .match_header(FASTLY_KEY, "test-token") - .match_header(&SURROGATE_KEY, "crate-foo crate-bar") - .with_status(500) - .create_async() - .await; - - let (_exporter, meter_provider) = setup_test_meter_provider(); - let metrics = CdnMetrics::new(&meter_provider); - - assert!( - purge_surrogate_keys( - &config, - &metrics, - vec![ - SurrogateKey::from_str("crate-foo").unwrap(), - SurrogateKey::from_str("crate-bar").unwrap(), - ], - ) - .await - .is_ok() - ); - - m.assert_async().await; - - Ok(()) - } - - #[tokio::test] - async fn test_purge_split_requests() -> Result<()> { - let mut fastly_api = mockito::Server::new_async().await; - - let config = TestEnvironment::base_config() - .fastly_api_host(fastly_api.url().parse().unwrap()) - .fastly_api_token(Some("test-token".into())) - .fastly_service_sid(Some("test-sid-1".into())) - .build()?; - - let m = fastly_api - .mock("POST", "/service/test-sid-1/purge") - .match_header(FASTLY_KEY, "test-token") - .match_request(|request| { - let [surrogate_keys] = request.header(&SURROGATE_KEY)[..] else { - panic!("expected one SURROGATE_KEY header"); - }; - let surrogate_keys: SurrogateKeys = - surrogate_keys.to_str().unwrap().parse().unwrap(); - - assert!( - // first request - surrogate_keys.key_count() == 256 || - // second request - surrogate_keys.key_count() == 94 - ); - - true - }) - .expect(2) // 300 keys below - .with_status(200) - .create_async() - .await; - - let (_exporter, meter_provider) = setup_test_meter_provider(); - let metrics = CdnMetrics::new(&meter_provider); - - let keys: Vec<_> = (0..350) - .map(|n| SurrogateKey::from_str(&format!("crate-foo-{n}")).unwrap()) - .collect(); - - purge_surrogate_keys(&config, &metrics, keys).await?; - - m.assert_async().await; - - Ok(()) - } -} diff --git a/src/cdn/mod.rs b/src/cdn/mod.rs deleted file mode 100644 index 191165193..000000000 --- a/src/cdn/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -use crate::{Config, db::types::krate_name::KrateName, web::headers::SurrogateKey}; -use anyhow::Result; -use docs_rs_opentelemetry::AnyMeterProvider; -use opentelemetry::metrics::{Counter, Gauge}; -use tracing::{error, info, instrument}; - -pub(crate) mod fastly; - -#[derive(Debug)] -pub struct CdnMetrics { - fastly_batch_purges_with_surrogate: Counter, - fastly_batch_purge_errors: Counter, - fastly_purge_surrogate_keys: Counter, - fastly_rate_limit_remaining: Gauge, - fastly_time_until_rate_limit_reset: Gauge, -} - -impl CdnMetrics { - pub fn new(meter_provider: &AnyMeterProvider) -> Self { - let meter = meter_provider.meter("cdn"); - const PREFIX: &str = "docsrs.cdn"; - Self { - fastly_batch_purges_with_surrogate: meter - .u64_counter(format!("{PREFIX}.fastly_batch_purges_with_surrogate")) - .with_unit("1") - .build(), - fastly_batch_purge_errors: meter - .u64_counter(format!("{PREFIX}.fastly_batch_purge_errors")) - .with_unit("1") - .build(), - fastly_purge_surrogate_keys: meter - .u64_counter(format!("{PREFIX}.fastly_purge_surrogate_keys")) - .with_unit("1") - .build(), - fastly_rate_limit_remaining: meter - .u64_gauge(format!("{PREFIX}.fasty_rate_limit_remaining")) - .with_unit("1") - .build(), - fastly_time_until_rate_limit_reset: meter - .u64_gauge(format!("{PREFIX}.fastly_time_until_rate_limit_reset")) - .with_unit("s") - .build(), - } - } -} - -#[instrument(skip(config))] -pub(crate) async fn queue_crate_invalidation( - config: &Config, - metrics: &CdnMetrics, - krate_name: &KrateName, -) -> Result<()> { - if !config.cache_invalidatable_responses { - info!("full page cache disabled, skipping queueing invalidation"); - return Ok(()); - } - - if config.fastly_api_token.is_some() - && let Err(err) = fastly::purge_surrogate_keys( - config, - metrics, - std::iter::once(SurrogateKey::from(krate_name.clone())), - ) - .await - { - // TODO: for now just consume & report the error, I want to see how often that happens. - // We can then decide if we need more protection mechanisms (like retries or queuing). - error!(%krate_name, ?err, "error purging Fastly surrogate keys"); - } - - Ok(()) -} diff --git a/src/config.rs b/src/config.rs index 1b786c0e3..ab88c4008 100644 --- a/src/config.rs +++ b/src/config.rs @@ -55,15 +55,6 @@ pub struct Config { // This only affects pages that depend on invalidations to work. pub(crate) cache_invalidatable_responses: bool, - /// Fastly API host, typically only overwritten for testing - pub fastly_api_host: Url, - - /// Fastly API token for purging the services below. - pub fastly_api_token: Option, - - /// fastly service SID for the main domain - pub fastly_service_sid: Option, - pub(crate) build_workspace_reinitialization_interval: Duration, // Build params @@ -113,12 +104,6 @@ impl Config { "CACHE_CONTROL_STALE_WHILE_REVALIDATE", )?, cache_invalidatable_responses: env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?, - fastly_api_host: env( - "DOCSRS_FASTLY_API_HOST", - "https://api.fastly.com".parse().unwrap(), - )?, - fastly_api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, - fastly_service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, compiler_metrics_collection_path: maybe_env("DOCSRS_COMPILER_METRICS_PATH")?, temp_dir: temp_dir, rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, diff --git a/src/db/delete.rs b/src/db/delete.rs index bfadb7eab..e69de29bb 100644 --- a/src/db/delete.rs +++ b/src/db/delete.rs @@ -1,558 +0,0 @@ -use crate::{Config, error::Result}; -use anyhow::Context as _; -use docs_rs_database::types::{CrateId, version::Version}; -use docs_rs_storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}; -use fn_error_context::context; -use sqlx::Connection; - -use super::update_latest_version_id; - -/// List of directories in docs.rs's underlying storage (either the database or S3) containing a -/// subdirectory named after the crate. Those subdirectories will be deleted. -static LIBRARY_STORAGE_PATHS_TO_DELETE: &[&str] = &["rustdoc", "rustdoc-json", "sources"]; -static OTHER_STORAGE_PATHS_TO_DELETE: &[&str] = &["sources"]; - -#[context("error trying to delete crate {name} from database")] -pub async fn delete_crate( - conn: &mut sqlx::PgConnection, - storage: &AsyncStorage, - config: &Config, - name: &str, -) -> Result<()> { - let Some(crate_id) = get_id(conn, name).await? else { - return Ok(()); - }; - - let is_library = delete_crate_from_database(conn, name, crate_id).await?; - // #899 - let paths = if is_library { - LIBRARY_STORAGE_PATHS_TO_DELETE - } else { - OTHER_STORAGE_PATHS_TO_DELETE - }; - - for prefix in paths { - // delete the whole rustdoc/source folder for this crate. - // it will include existing archives. - let remote_folder = format!("{prefix}/{name}/"); - storage.delete_prefix(&remote_folder).await?; - - // remove existing local archive index files. - let local_index_folder = config.storage.local_archive_cache_path.join(&remote_folder); - if local_index_folder.exists() { - tokio::fs::remove_dir_all(&local_index_folder) - .await - .with_context(|| { - format!( - "error when trying to remove local index: {:?}", - &local_index_folder - ) - })?; - } - } - - Ok(()) -} - -#[context("error trying to delete release {name}-{version} from database")] -pub async fn delete_version( - conn: &mut sqlx::PgConnection, - storage: &AsyncStorage, - config: &Config, - name: &str, - version: &Version, -) -> Result<()> { - let Some(crate_id) = get_id(conn, name).await? else { - return Ok(()); - }; - - let is_library = delete_version_from_database(conn, crate_id, version).await?; - let paths = if is_library { - LIBRARY_STORAGE_PATHS_TO_DELETE - } else { - OTHER_STORAGE_PATHS_TO_DELETE - }; - - for prefix in paths { - storage - .delete_prefix(&format!("{prefix}/{name}/{version}/")) - .await?; - } - - let local_archive_cache = &config.storage.local_archive_cache_path; - let mut paths = vec![source_archive_path(name, version)]; - if is_library { - paths.push(rustdoc_archive_path(name, version)); - } - - for archive_filename in paths { - // delete remove archive and remote index - storage.delete_prefix(&archive_filename).await?; - - // delete eventually existing local indexes - let local_index_file = local_archive_cache.join(format!("{archive_filename}.index")); - if local_index_file.exists() { - tokio::fs::remove_file(&local_index_file) - .await - .with_context(|| { - format!("error when trying to remove local index: {local_index_file:?}") - })?; - } - } - - Ok(()) -} - -async fn get_id(conn: &mut sqlx::PgConnection, name: &str) -> Result> { - Ok(sqlx::query_scalar!( - r#" - SELECT id as "id: CrateId" - FROM crates - WHERE normalize_crate_name(name) = normalize_crate_name($1) - "#, - name - ) - .fetch_optional(&mut *conn) - .await?) -} - -// metaprogramming! -// WARNING: these must be hard-coded and NEVER user input. -const METADATA: &[(&str, &str)] = &[ - ("keyword_rels", "rid"), - ("builds", "rid"), - ("compression_rels", "release"), - ("doc_coverage", "release_id"), -]; - -/// Returns whether this release was a library -async fn delete_version_from_database( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, - version: &Version, -) -> Result { - let mut transaction = conn.begin().await?; - for &(table, column) in METADATA { - sqlx::query( - format!("DELETE FROM {table} WHERE {column} IN (SELECT id FROM releases WHERE crate_id = $1 AND version = $2)").as_str()) - .bind(crate_id).bind(version).execute(&mut *transaction).await?; - } - let is_library: bool = sqlx::query_scalar!( - "DELETE FROM releases WHERE crate_id = $1 AND version = $2 RETURNING is_library", - crate_id.0, - version as _, - ) - .fetch_one(&mut *transaction) - .await? - .unwrap_or(false); - - update_latest_version_id(&mut transaction, crate_id).await?; - - transaction.commit().await?; - Ok(is_library) -} - -/// Returns whether any release in this crate was a library -async fn delete_crate_from_database( - conn: &mut sqlx::PgConnection, - name: &str, - crate_id: CrateId, -) -> Result { - let mut transaction = conn.begin().await?; - - sqlx::query!("DELETE FROM sandbox_overrides WHERE crate_name = $1", name,) - .execute(&mut *transaction) - .await?; - - for &(table, column) in METADATA { - sqlx::query( - format!( - "DELETE FROM {table} WHERE {column} IN (SELECT id FROM releases WHERE crate_id = $1)" - ) - .as_str()).bind(crate_id).execute(&mut *transaction).await?; - } - sqlx::query!("DELETE FROM owner_rels WHERE cid = $1;", crate_id.0) - .execute(&mut *transaction) - .await?; - - let has_library: bool = sqlx::query_scalar!( - "SELECT - BOOL_OR(releases.is_library) AS has_library - FROM releases - WHERE releases.crate_id = $1 - ", - crate_id.0 - ) - .fetch_one(&mut *transaction) - .await? - .unwrap_or(false); - - sqlx::query!("DELETE FROM releases WHERE crate_id = $1;", crate_id.0) - .execute(&mut *transaction) - .await?; - sqlx::query!("DELETE FROM crates WHERE id = $1;", crate_id.0) - .execute(&mut *transaction) - .await?; - - // Transactions automatically rollback when not committing, so if any of the previous queries - // fail the whole transaction will be aborted. - transaction.commit().await?; - Ok(has_library) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::db::ReleaseId; - use crate::registry_api::{CrateOwner, OwnerKind}; - use crate::storage::{CompressionAlgorithm, rustdoc_json_path}; - use crate::test::{KRATE, V1, V2, async_wrapper, fake_release_that_failed_before_build}; - use test_case::test_case; - - async fn crate_exists(conn: &mut sqlx::PgConnection, name: &str) -> Result { - Ok(sqlx::query!("SELECT id FROM crates WHERE name = $1;", name) - .fetch_optional(conn) - .await? - .is_some()) - } - - async fn release_exists(conn: &mut sqlx::PgConnection, id: ReleaseId) -> Result { - Ok(sqlx::query!("SELECT id FROM releases WHERE id = $1;", id.0) - .fetch_optional(conn) - .await? - .is_some()) - } - - #[test] - fn test_get_id_uses_normalization() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("Some_Package") - .version(V1) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - assert!(get_id(&mut conn, "some-package").await.is_ok()); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_delete_crate(archive_storage: bool) { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - // Create fake packages in the database - let pkg1_v1_id = env - .fake_release() - .await - .name("package-1") - .version(V1) - .archive_storage(archive_storage) - .create() - .await?; - let pkg1_v2_id = env - .fake_release() - .await - .name("package-1") - .version(V2) - .archive_storage(archive_storage) - .create() - .await?; - let pkg2_id = env - .fake_release() - .await - .name("package-2") - .version(V1) - .archive_storage(archive_storage) - .create() - .await?; - - assert!(crate_exists(&mut conn, "package-1").await?); - assert!(crate_exists(&mut conn, "package-2").await?); - assert!(release_exists(&mut conn, pkg1_v1_id).await?); - assert!(release_exists(&mut conn, pkg1_v2_id).await?); - assert!(release_exists(&mut conn, pkg2_id).await?); - for (pkg, version) in &[("package-1", V1), ("package-1", V2), ("package-2", V1)] { - assert!( - env.async_storage() - .rustdoc_file_exists( - pkg, - version, - None, - &format!("{pkg}/index.html"), - archive_storage - ) - .await? - ); - } - - delete_crate(&mut conn, env.async_storage(), env.config(), "package-1").await?; - - assert!(!crate_exists(&mut conn, "package-1").await?); - assert!(crate_exists(&mut conn, "package-2").await?); - assert!(!release_exists(&mut conn, pkg1_v1_id).await?); - assert!(!release_exists(&mut conn, pkg1_v2_id).await?); - assert!(release_exists(&mut conn, pkg2_id).await?); - - // files for package 2 still exists - assert!( - env.async_storage() - .rustdoc_file_exists( - "package-2", - &V1, - None, - "package-2/index.html", - archive_storage - ) - .await? - ); - - // files for package 1 are gone - if archive_storage { - assert!( - !env.async_storage() - .exists(&rustdoc_archive_path("package-1", &V1)) - .await? - ); - assert!( - !env.async_storage() - .exists(&rustdoc_archive_path("package-1", &V2)) - .await? - ); - } else { - assert!( - !env.async_storage() - .rustdoc_file_exists( - "package-1", - &V1, - None, - "package-1/index.html", - archive_storage - ) - .await? - ); - assert!( - !env.async_storage() - .rustdoc_file_exists( - "package-1", - &V2, - None, - "package-1/index.html", - archive_storage - ) - .await? - ); - } - - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn test_delete_version(archive_storage: bool) { - async_wrapper(|env| async move { - async fn owners( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, - ) -> Result> { - Ok(sqlx::query!( - "SELECT login FROM owners - INNER JOIN owner_rels ON owners.id = owner_rels.oid - WHERE owner_rels.cid = $1", - crate_id.0, - ) - .fetch_all(conn) - .await? - .into_iter() - .map(|row| row.login) - .collect()) - } - - async fn json_exists(storage: &AsyncStorage, version: &Version) -> Result { - storage - .exists(&rustdoc_json_path( - "a", - version, - "x86_64-unknown-linux-gnu", - crate::storage::RustdocJsonFormatVersion::Latest, - Some(CompressionAlgorithm::Zstd), - )) - .await - } - - let mut conn = env.async_db().async_conn().await; - let v1 = env - .fake_release() - .await - .name("a") - .version(V1) - .archive_storage(archive_storage) - .add_owner(CrateOwner { - login: "malicious actor".into(), - avatar: "https://example.org/malicious".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - assert!(release_exists(&mut conn, v1).await?); - assert!( - env.async_storage() - .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) - .await? - ); - assert!(json_exists(env.async_storage(), &V1).await?); - let crate_id = sqlx::query_scalar!( - r#"SELECT crate_id as "crate_id: CrateId" FROM releases WHERE id = $1"#, - v1.0 - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!( - owners(&mut conn, crate_id).await?, - vec!["malicious actor".to_string()] - ); - - let v2 = env - .fake_release() - .await - .name("a") - .version(V2) - .archive_storage(archive_storage) - .add_owner(CrateOwner { - login: "Peter Rabbit".into(), - avatar: "https://example.org/peter".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - assert!(release_exists(&mut conn, v2).await?); - assert!( - env.async_storage() - .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) - .await? - ); - assert!(json_exists(env.async_storage(), &V2).await?); - assert_eq!( - owners(&mut conn, crate_id).await?, - vec!["Peter Rabbit".to_string()] - ); - - delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; - assert!(!release_exists(&mut conn, v1).await?); - if archive_storage { - // for archive storage the archive and index files - // need to be cleaned up. - let rustdoc_archive = rustdoc_archive_path("a", &V1); - assert!(!env.async_storage().exists(&rustdoc_archive).await?); - - // local and remote index are gone too - let archive_index = format!("{rustdoc_archive}.index"); - assert!(!env.async_storage().exists(&archive_index).await?); - assert!( - !env.config() - .local_archive_cache_path - .join(&archive_index) - .exists() - ); - } else { - assert!( - !env.async_storage() - .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) - .await? - ); - } - assert!(!json_exists(env.async_storage(), &V1,).await?); - - assert!(release_exists(&mut conn, v2).await?); - assert!( - env.async_storage() - .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) - .await? - ); - assert!(json_exists(env.async_storage(), &V2).await?); - assert_eq!( - owners(&mut conn, crate_id).await?, - vec!["Peter Rabbit".to_string()] - ); - - // FIXME: remove for now until test frontend is async - // let web = env.frontend(); - // assert_success("/a/2.0.0/a/", web)?; - // assert_eq!(web.get("/a/1.0.0/a/").send()?.status(), 404); - - Ok(()) - }) - } - - #[test] - fn test_delete_incomplete_version() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let (release_id, _) = - fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; - - delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; - - assert!(!release_exists(&mut conn, release_id).await?); - - Ok(()) - }) - } - - #[test] - fn test_delete_incomplete_crate() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let (release_id, _) = - fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; - - delete_crate(&mut conn, env.async_storage(), env.config(), "a").await?; - - assert!(!crate_exists(&mut conn, "a").await?); - assert!(!release_exists(&mut conn, release_id).await?); - - Ok(()) - }) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_delete_missing_crate_doesnt_error() -> Result<()> { - let env = crate::test::TestEnvironment::new().await?; - - let db = env.async_db(); - let mut conn = db.async_conn().await; - - assert!(!crate_exists(&mut conn, KRATE).await?); - delete_crate(&mut conn, env.async_storage(), env.config(), KRATE).await?; - - assert!(!crate_exists(&mut conn, KRATE).await?); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_delete_missing_version_doesnt_error() -> Result<()> { - let env = crate::test::TestEnvironment::new().await?; - - let db = env.async_db(); - let mut conn = db.async_conn().await; - - assert!(!crate_exists(&mut conn, KRATE).await?); - - delete_version(&mut conn, env.async_storage(), env.config(), KRATE, &V1).await?; - - assert!(!crate_exists(&mut conn, KRATE).await?); - - Ok(()) - } -} diff --git a/src/db/types/mod.rs b/src/db/types/mod.rs index f28d5c8d3..7ba865c31 100644 --- a/src/db/types/mod.rs +++ b/src/db/types/mod.rs @@ -1,7 +1,6 @@ use serde::{Deserialize, Serialize}; pub mod dependencies; -pub mod krate_name; #[derive(Debug, Clone, PartialEq, Eq, Serialize, sqlx::Type)] #[sqlx(type_name = "feature")] diff --git a/src/utils/queue.rs b/src/utils/queue.rs deleted file mode 100644 index 6c246fffa..000000000 --- a/src/utils/queue.rs +++ /dev/null @@ -1,199 +0,0 @@ -//! Utilities for interacting with the build queue -use crate::error::Result; -use docs_rs_build_queue::PRIORITY_DEFAULT; -use futures_util::stream::TryStreamExt; - -/// Get the build queue priority for a crate, returns the matching pattern too -pub async fn list_crate_priorities(conn: &mut sqlx::PgConnection) -> Result> { - Ok( - sqlx::query!("SELECT pattern, priority FROM crate_priorities") - .fetch(conn) - .map_ok(|r| (r.pattern, r.priority)) - .try_collect() - .await?, - ) -} - -/// Get the build queue priority for a crate with its matching pattern -pub async fn get_crate_pattern_and_priority( - conn: &mut sqlx::PgConnection, - name: &str, -) -> Result> { - // Search the `priority` table for a priority where the crate name matches the stored pattern - Ok(sqlx::query!( - "SELECT pattern, priority FROM crate_priorities WHERE $1 LIKE pattern LIMIT 1", - name - ) - .fetch_optional(&mut *conn) - .await? - .map(|row| (row.pattern, row.priority))) -} - -/// Get the build queue priority for a crate -pub async fn get_crate_priority(conn: &mut sqlx::PgConnection, name: &str) -> Result { - Ok(get_crate_pattern_and_priority(conn, name) - .await? - .map_or(PRIORITY_DEFAULT, |(_, priority)| priority)) -} - -/// Set all crates that match [`pattern`] to have a certain priority -/// -/// Note: `pattern` is used in a `LIKE` statement, so it must follow the postgres like syntax -/// -/// [`pattern`]: https://www.postgresql.org/docs/8.3/functions-matching.html -pub async fn set_crate_priority( - conn: &mut sqlx::PgConnection, - pattern: &str, - priority: i32, -) -> Result<()> { - sqlx::query!( - "INSERT INTO crate_priorities (pattern, priority) VALUES ($1, $2)", - pattern, - priority, - ) - .execute(&mut *conn) - .await?; - - Ok(()) -} - -/// Remove a pattern from the priority table, returning the priority that it was associated with or `None` -/// if nothing was removed -pub async fn remove_crate_priority( - conn: &mut sqlx::PgConnection, - pattern: &str, -) -> Result> { - Ok(sqlx::query_scalar!( - "DELETE FROM crate_priorities WHERE pattern = $1 RETURNING priority", - pattern, - ) - .fetch_optional(&mut *conn) - .await?) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::test::async_wrapper; - - #[test] - fn set_priority() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - set_crate_priority(&mut conn, "docsrs-%", -100).await?; - assert_eq!( - get_crate_priority(&mut conn, "docsrs-database").await?, - -100 - ); - assert_eq!(get_crate_priority(&mut conn, "docsrs-").await?, -100); - assert_eq!(get_crate_priority(&mut conn, "docsrs-s3").await?, -100); - assert_eq!( - get_crate_priority(&mut conn, "docsrs-webserver").await?, - -100 - ); - assert_eq!( - get_crate_priority(&mut conn, "docsrs").await?, - PRIORITY_DEFAULT - ); - - set_crate_priority(&mut conn, "_c_", 100).await?; - assert_eq!(get_crate_priority(&mut conn, "rcc").await?, 100); - assert_eq!(get_crate_priority(&mut conn, "rc").await?, PRIORITY_DEFAULT); - - set_crate_priority(&mut conn, "hexponent", 10).await?; - assert_eq!(get_crate_priority(&mut conn, "hexponent").await?, 10); - assert_eq!( - get_crate_priority(&mut conn, "hexponents").await?, - PRIORITY_DEFAULT - ); - assert_eq!( - get_crate_priority(&mut conn, "floathexponent").await?, - PRIORITY_DEFAULT - ); - - Ok(()) - }) - } - - #[test] - fn remove_priority() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - set_crate_priority(&mut conn, "docsrs-%", -100).await?; - assert_eq!(get_crate_priority(&mut conn, "docsrs-").await?, -100); - - assert_eq!( - remove_crate_priority(&mut conn, "docsrs-%").await?, - Some(-100) - ); - assert_eq!( - get_crate_priority(&mut conn, "docsrs-").await?, - PRIORITY_DEFAULT - ); - - Ok(()) - }) - } - - #[test] - fn get_priority() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - set_crate_priority(&mut conn, "docsrs-%", -100).await?; - - assert_eq!( - get_crate_priority(&mut conn, "docsrs-database").await?, - -100 - ); - assert_eq!(get_crate_priority(&mut conn, "docsrs-").await?, -100); - assert_eq!(get_crate_priority(&mut conn, "docsrs-s3").await?, -100); - assert_eq!( - get_crate_priority(&mut conn, "docsrs-webserver").await?, - -100 - ); - assert_eq!( - get_crate_priority(&mut conn, "unrelated").await?, - PRIORITY_DEFAULT - ); - - Ok(()) - }) - } - - #[test] - fn get_default_priority() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - assert_eq!( - get_crate_priority(&mut conn, "docsrs").await?, - PRIORITY_DEFAULT - ); - assert_eq!( - get_crate_priority(&mut conn, "rcc").await?, - PRIORITY_DEFAULT - ); - assert_eq!( - get_crate_priority(&mut conn, "lasso").await?, - PRIORITY_DEFAULT - ); - assert_eq!( - get_crate_priority(&mut conn, "hexponent").await?, - PRIORITY_DEFAULT - ); - assert_eq!( - get_crate_priority(&mut conn, "rust4lyfe").await?, - PRIORITY_DEFAULT - ); - - Ok(()) - }) - } -} diff --git a/src/web/headers/if_none_match.rs b/src/web/headers/if_none_match.rs deleted file mode 100644 index 67983a504..000000000 --- a/src/web/headers/if_none_match.rs +++ /dev/null @@ -1,173 +0,0 @@ -//! Adapted version of `headers::IfNoneMatch`. -//! -//! The combination of `TypedHeader` and `IfNoneMatch` works in odd ways. -//! They are built in a way that a _missing_ `If-None-Match` header will lead to: -//! -//! 1. extractor with `TypedHeader` returning `IfNoneMatch("")` -//! 2. extractor with `Option>` returning `Some(IfNoneMatch(""))` -//! -//! Where I would expect: -//! 1. a failure because of the missing header -//! 2. `None` for the missing header -//! -//! This could be solved by either adapting `TypedHeader` or `IfNoneMatch`, I'm not sure which is -//! right. -//! -//! Some reading material for those interested: -//! * https://github.com/hyperium/headers/issues/204 -//! * https://github.com/hyperium/headers/pull/165 -//! * https://github.com/tokio-rs/axum/issues/1781 -//! * https://github.com/tokio-rs/axum/pull/1810 -//! * https://github.com/tokio-rs/axum/pull/2475 -//! -//! Right now I feel like adapting `IfNoneMatch` is the "most correct-ish" option. - -#[allow(clippy::disallowed_types)] -mod header_impl { - use axum_extra::headers::{self, ETag, Header, IfNoneMatch as OriginalIfNoneMatch}; - use derive_more::Deref; - - #[derive(Debug, Clone, PartialEq, Deref)] - pub(crate) struct IfNoneMatch(pub axum_extra::headers::IfNoneMatch); - - impl Header for IfNoneMatch { - fn name() -> &'static http::HeaderName { - OriginalIfNoneMatch::name() - } - - fn decode<'i, I>(values: &mut I) -> Result - where - Self: Sized, - I: Iterator, - { - let mut values = values.peekable(); - - // NOTE: this is the difference to the original implementation. - // When there is no header in the request, I want the decoding to fail. - // This makes Option> return `None`, and also matches - // most other header implementations. - if values.peek().is_none() { - Err(headers::Error::invalid()) - } else { - OriginalIfNoneMatch::decode(&mut values).map(IfNoneMatch) - } - } - - fn encode>(&self, values: &mut E) { - self.0.encode(values) - } - } - - impl From for IfNoneMatch { - fn from(value: ETag) -> Self { - Self(value.into()) - } - } -} - -pub(crate) use header_impl::IfNoneMatch; - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - use axum::{RequestPartsExt, body::Body, extract::Request}; - use axum_extra::{ - TypedHeader, - headers::{ETag, HeaderMapExt as _}, - }; - use http::{HeaderMap, request}; - - fn parts(if_none_match: Option) -> request::Parts { - let mut builder = Request::builder(); - - if let Some(if_none_match) = if_none_match { - let headers = builder.headers_mut().unwrap(); - headers.typed_insert(if_none_match.clone()); - } - - let (parts, _body) = builder.uri("/").body(Body::empty()).unwrap().into_parts(); - - parts - } - - fn example_header() -> IfNoneMatch { - IfNoneMatch::from("\"some-etag-value\"".parse::().unwrap()) - } - - #[test] - fn test_normal_typed_get_with_empty_headers() { - let map = HeaderMap::new(); - assert!(map.typed_get::().is_none()); - assert!(map.typed_try_get::().unwrap().is_none()); - } - - #[test] - fn test_normal_typed_get_with_value_headers() -> Result<()> { - let if_none_match = example_header(); - - let mut map = HeaderMap::new(); - map.typed_insert(if_none_match.clone()); - - assert_eq!(map.typed_get::(), Some(if_none_match.clone())); - assert_eq!(map.typed_try_get::()?, Some(if_none_match)); - - Ok(()) - } - - #[tokio::test] - async fn test_extract_from_empty_request_via_optional_typed_header() -> Result<()> { - let mut parts = parts(None); - - assert!( - parts - .extract::>>() - .await? - // this is what we want, and the default `headers::IfNoneMatch` header can't - // offer. Or the impl of the `TypedHeader` extractor, depending on - // interpretation. - .is_none() - ); - - Ok(()) - } - - #[tokio::test] - async fn test_extract_from_empty_request_via_mandatory_typed_header() -> Result<()> { - let mut parts = parts(None); - - // mandatory extractor leads to error when the header is missing. - assert!(parts.extract::>().await.is_err()); - - Ok(()) - } - - #[tokio::test] - async fn test_extract_from_header_via_optional_typed_header() -> Result<()> { - let if_none_match = example_header(); - let mut parts = parts(Some(if_none_match.clone())); - - assert_eq!( - parts - .extract::>>() - .await? - .map(|th| th.0), - Some(if_none_match) - ); - - Ok(()) - } - - #[tokio::test] - async fn test_extract_from_header_via_mandatory_typed_header() -> Result<()> { - let if_none_match = example_header(); - let mut parts = parts(Some(if_none_match.clone())); - - assert_eq!( - parts.extract::>().await?.0, - if_none_match - ); - - Ok(()) - } -} diff --git a/src/web/headers/mod.rs b/src/web/headers/mod.rs deleted file mode 100644 index 7a19e4bc8..000000000 --- a/src/web/headers/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -mod canonical_url; -mod if_none_match; -mod surrogate_key; - -use axum_extra::headers::ETag; -use http::HeaderName; -use std::io::{self, Write}; - -pub use canonical_url::CanonicalUrl; -pub(crate) use if_none_match::IfNoneMatch; -pub use surrogate_key::{SURROGATE_KEY, SurrogateKey, SurrogateKeys}; - -/// Fastly's Surrogate-Control header -/// https://www.fastly.com/documentation/reference/http/http-headers/Surrogate-Control/ -pub static SURROGATE_CONTROL: HeaderName = HeaderName::from_static("surrogate-control"); - -/// X-Robots-Tag header for search engines. -pub static X_ROBOTS_TAG: HeaderName = HeaderName::from_static("x-robots-tag"); diff --git a/src/web/mod.rs b/src/web/mod.rs index a0ae7a1f3..12fd1f5b8 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -24,11 +24,9 @@ pub(crate) mod cache; pub(crate) mod crate_details; mod csp; pub(crate) mod error; -mod escaped_uri; mod extractors; mod features; mod file; -pub(crate) mod headers; mod highlight; mod licenses; mod markdown; @@ -54,7 +52,6 @@ use axum::{ use chrono::{DateTime, Utc}; use error::AxumNope; use page::TemplateData; -use percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode}; use semver::VersionReq; use sentry::integrations::tower as sentry_tower; use serde_with::{DeserializeFromStr, SerializeDisplay}; @@ -70,19 +67,6 @@ use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::Tra use self::crate_details::Release; -// from https://github.com/servo/rust-url/blob/master/url/src/parser.rs -// and https://github.com/tokio-rs/axum/blob/main/axum-extra/src/lib.rs -const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); -const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); - -pub(crate) fn encode_url_path(path: &str) -> String { - utf8_percent_encode(path, PATH).to_string() -} - -pub(crate) fn url_decode<'a>(input: &'a str) -> Result> { - Ok(percent_encoding::percent_decode(input.as_bytes()).decode_utf8()?) -} - const DEFAULT_BIND: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3000); /// Represents a version identifier in a request in the original state. From 72db3453ec2ae15b37e9913329366394150bd17c Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sun, 7 Dec 2025 12:38:19 +0100 Subject: [PATCH 11/46] kk --- Cargo.lock | 1 + crates/docs_rs_watcher/Cargo.toml | 1 + crates/docs_rs_watcher/src/build_queue.rs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 852a1bca7..3809cb1f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2155,6 +2155,7 @@ dependencies = [ "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_fastly", "docs_rs_logging", "docs_rs_opentelemetry", "docs_rs_utils", diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index c83becc29..9b6025981 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -10,6 +10,7 @@ chrono = { workspace = true } clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} +docs_rs_fastly = { path = "../docs_rs_fastly" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } docs_rs_context = { path = "../docs_rs_context" } diff --git a/crates/docs_rs_watcher/src/build_queue.rs b/crates/docs_rs_watcher/src/build_queue.rs index 6d8a808a3..0ec6c8e0a 100644 --- a/crates/docs_rs_watcher/src/build_queue.rs +++ b/crates/docs_rs_watcher/src/build_queue.rs @@ -66,7 +66,7 @@ pub async fn get_new_crates( } }; - queue_crate_invalidation(krate).await; + docs_rs_fastly::queue_crate_invalidation(krate).await; build_queue.remove_crate_from_queue(krate).await?; continue; } From 6caa46b9eadcb73441ca0a265eca35f4593777eb Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 10 Dec 2025 06:26:00 +0100 Subject: [PATCH 12/46] fix --- Cargo.lock | 1 + crates/docs_rs_fastly/src/lib.rs | 247 +++---- crates/docs_rs_storage/src/lib.rs | 36 +- crates/docs_rs_watcher/Cargo.toml | 1 + crates/docs_rs_watcher/src/build_queue.rs | 31 +- crates/docs_rs_watcher/src/db/delete.rs | 747 +++++++++++----------- crates/docs_rs_watcher/src/db/mod.rs | 1 + crates/docs_rs_watcher/src/lib.rs | 1 + src/db/delete.rs | 0 9 files changed, 540 insertions(+), 525 deletions(-) delete mode 100644 src/db/delete.rs diff --git a/Cargo.lock b/Cargo.lock index 3809cb1f9..de5ceb63e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2158,6 +2158,7 @@ dependencies = [ "docs_rs_fastly", "docs_rs_logging", "docs_rs_opentelemetry", + "docs_rs_storage", "docs_rs_utils", "futures-util", "itertools 0.14.0", diff --git a/crates/docs_rs_fastly/src/lib.rs b/crates/docs_rs_fastly/src/lib.rs index 6b4cf77f9..8d15dc22f 100644 --- a/crates/docs_rs_fastly/src/lib.rs +++ b/crates/docs_rs_fastly/src/lib.rs @@ -13,14 +13,14 @@ use http::{ use itertools::Itertools as _; use opentelemetry::KeyValue; use std::sync::OnceLock; -use tracing::{error, info, instrument}; +use tracing::error; const FASTLY_KEY: HeaderName = HeaderName::from_static("fastly-key"); // https://www.fastly.com/documentation/reference/api/#rate-limiting const FASTLY_RATELIMIT_REMAINING: HeaderName = HeaderName::from_static("fastly-ratelimit-remaining"); -const FASTLY_RATELIMIT_RESET: HeaderName = HeaderName::from_static("fastyly-ratelimit-reset"); +const FASTLY_RATELIMIT_RESET: HeaderName = HeaderName::from_static("fastly-ratelimit-reset"); static CLIENT: OnceLock> = OnceLock::new(); @@ -55,137 +55,140 @@ fn fetch_rate_limit_state(headers: &HeaderMap) -> (Option, Option( - config: &config::Config, - metrics: &metrics::CdnMetrics, - keys: I, -) -> Result<()> -where - I: IntoIterator, -{ - let Some(api_token) = &config.api_token else { - bail!("Fastly API token not configured"); - }; - - let client = fastly_client(api_token)?; - - let record_rate_limit_metrics = - |limit_remaining: Option, limit_reset: Option>| { - if let Some(limit_remaining) = limit_remaining { - metrics.rate_limit_remaining.record(limit_remaining, &[]); - } +pub struct Cdn { + config: config::Config, + metrics: metrics::CdnMetrics, +} - if let Some(limit_reset) = limit_reset { - metrics - .time_until_rate_limit_reset - .record((limit_reset - Utc::now()).num_seconds() as u64, &[]); - } +impl Cdn { + /// Purge the given surrogate keys from all configured fastly services. + /// + /// Accepts any number of surrogate keys, and splits them into appropriately sized + /// batches for the Fastly API. + pub(crate) async fn purge_surrogate_keys(&self, keys: I) -> Result<()> + where + I: IntoIterator, + { + let Some(api_token) = &self.config.api_token else { + bail!("Fastly API token not configured"); }; - // the `bulk_purge_tag` supports up to 256 surrogate keys in its list, - // but I believe we also have to respect the length limits for the full - // surrogate key header we send in this purge request. - // see https://www.fastly.com/documentation/reference/api/purging/ - for encoded_surrogate_keys in keys.into_iter().batching(|it| { - const MAX_SURROGATE_KEYS_IN_BATCH_PURGE: usize = 256; - - // SurrogateKeys::from_iter::until_full only consumes as many elements as will fit into - // the header. - // The rest is up to the next `batching` iteration. - let keys = SurrogateKeys::from_iter_until_full(it.take(MAX_SURROGATE_KEYS_IN_BATCH_PURGE)); - - if keys.key_count() > 0 { - Some(keys) - } else { - None - } - }) { - if let Some(ref sid) = config.service_sid { - // NOTE: we start with just calling the API, and logging an error if they happen. - // We can then see if we need retries or escalation to full purges. - - let kv = [KeyValue::new("service_sid", sid.clone())]; - - // https://www.fastly.com/documentation/reference/api/purging/ - // TODO: investigate how they could help & test - // soft purge. But later, after the initial migration. - match client - .post(config.api_host.join(&format!("/service/{}/purge", sid))?) - .header(&SURROGATE_KEY, encoded_surrogate_keys.to_string()) - .send() - .await - { - Ok(response) if response.status().is_success() => { - metrics.batch_purges_with_surrogate.add(1, &kv); - metrics - .purge_surrogate_keys - .add(encoded_surrogate_keys.key_count() as u64, &kv); - - let (limit_remaining, limit_reset) = fetch_rate_limit_state(response.headers()); - record_rate_limit_metrics(limit_remaining, limit_reset); - } - Ok(error_response) => { - metrics.batch_purge_errors.add(1, &kv); - - let (limit_remaining, limit_reset) = - fetch_rate_limit_state(error_response.headers()); - record_rate_limit_metrics(limit_remaining, limit_reset); - - let limit_reset = limit_reset.map(|dt| dt.to_rfc3339()); - - let status = error_response.status(); - let content = error_response.text().await.unwrap_or_default(); - error!( - sid, - %status, - content, - %encoded_surrogate_keys, - rate_limit_remaining=limit_remaining, - rate_limit_reset=limit_reset, - "Failed to purge Fastly surrogate keys for service" - ); + let client = fastly_client(api_token)?; + + let record_rate_limit_metrics = + |limit_remaining: Option, limit_reset: Option>| { + if let Some(limit_remaining) = limit_remaining { + self.metrics + .rate_limit_remaining + .record(limit_remaining, &[]); } - Err(err) => { - // connection errors or similar, where we don't have a response - metrics.batch_purge_errors.add(1, &kv); - error!( - sid, - ?err, - %encoded_surrogate_keys, - "Failed to purge Fastly surrogate keys for service" - ); + + if let Some(limit_reset) = limit_reset { + self.metrics + .time_until_rate_limit_reset + .record((limit_reset - Utc::now()).num_seconds() as u64, &[]); } }; + + // the `bulk_purge_tag` supports up to 256 surrogate keys in its list, + // but I believe we also have to respect the length limits for the full + // surrogate key header we send in this purge request. + // see https://www.fastly.com/documentation/reference/api/purging/ + for encoded_surrogate_keys in keys.into_iter().batching(|it| { + const MAX_SURROGATE_KEYS_IN_BATCH_PURGE: usize = 256; + + // SurrogateKeys::from_iter::until_full only consumes as many elements as will fit into + // the header. + // The rest is up to the next `batching` iteration. + let keys = + SurrogateKeys::from_iter_until_full(it.take(MAX_SURROGATE_KEYS_IN_BATCH_PURGE)); + + if keys.key_count() > 0 { + Some(keys) + } else { + None + } + }) { + if let Some(ref sid) = self.config.service_sid { + // NOTE: we start with just calling the API, and logging an error if they happen. + // We can then see if we need retries or escalation to full purges. + + let kv = [KeyValue::new("service_sid", sid.clone())]; + + // https://www.fastly.com/documentation/reference/api/purging/ + // TODO: investigate how they could help & test + // soft purge. But later, after the initial migration. + match client + .post( + self.config + .api_host + .join(&format!("/service/{}/purge", sid))?, + ) + .header(&SURROGATE_KEY, encoded_surrogate_keys.to_string()) + .send() + .await + { + Ok(response) if response.status().is_success() => { + self.metrics.batch_purges_with_surrogate.add(1, &kv); + self.metrics + .purge_surrogate_keys + .add(encoded_surrogate_keys.key_count() as u64, &kv); + + let (limit_remaining, limit_reset) = + fetch_rate_limit_state(response.headers()); + record_rate_limit_metrics(limit_remaining, limit_reset); + } + Ok(error_response) => { + self.metrics.batch_purge_errors.add(1, &kv); + + let (limit_remaining, limit_reset) = + fetch_rate_limit_state(error_response.headers()); + record_rate_limit_metrics(limit_remaining, limit_reset); + + let limit_reset = limit_reset.map(|dt| dt.to_rfc3339()); + + let status = error_response.status(); + let content = error_response.text().await.unwrap_or_default(); + error!( + sid, + %status, + content, + %encoded_surrogate_keys, + rate_limit_remaining=limit_remaining, + rate_limit_reset=limit_reset, + "Failed to purge Fastly surrogate keys for service" + ); + } + Err(err) => { + // connection errors or similar, where we don't have a response + self.metrics.batch_purge_errors.add(1, &kv); + error!( + sid, + ?err, + %encoded_surrogate_keys, + "Failed to purge Fastly surrogate keys for service" + ); + } + }; + } } + + Ok(()) } - Ok(()) -} + pub async fn queue_crate_invalidation(&self, krate_name: &KrateName) -> Result<()> { + if self.config.api_token.is_some() + && let Err(err) = self + .purge_surrogate_keys(std::iter::once(SurrogateKey::from(krate_name.clone()))) + .await + { + // TODO: for now just consume & report the error, I want to see how often that happens. + // We can then decide if we need more protection mechanisms (like retries or queuing). + error!(%krate_name, ?err, "error purging Fastly surrogate keys"); + } -#[instrument(skip(config))] -pub async fn queue_crate_invalidation( - config: &config::Config, - metrics: &metrics::CdnMetrics, - krate_name: &KrateName, -) -> Result<()> { - if config.api_token.is_some() - && let Err(err) = purge_surrogate_keys( - config, - metrics, - std::iter::once(SurrogateKey::from(krate_name.clone())), - ) - .await - { - // TODO: for now just consume & report the error, I want to see how often that happens. - // We can then decide if we need more protection mechanisms (like retries or queuing). - error!(%krate_name, ?err, "error purging Fastly surrogate keys"); + Ok(()) } - - Ok(()) } // #[cfg(test)] diff --git a/crates/docs_rs_storage/src/lib.rs b/crates/docs_rs_storage/src/lib.rs index 1d002cd84..d24589cfa 100644 --- a/crates/docs_rs_storage/src/lib.rs +++ b/crates/docs_rs_storage/src/lib.rs @@ -16,7 +16,7 @@ use self::{ s3::S3Backend, }; use crate::file::FileEntry; -use anyhow::{Result, anyhow}; +use anyhow::{Context as _, Result, anyhow}; use chrono::{DateTime, Utc}; use dashmap::DashMap; use docs_rs_database::{ @@ -919,9 +919,39 @@ impl AsyncStorage { pub async fn delete_prefix(&self, prefix: &str) -> Result<()> { match &self.backend { - StorageBackend::Database(db) => db.delete_prefix(prefix).await, - StorageBackend::S3(s3) => s3.delete_prefix(prefix).await, + StorageBackend::Database(db) => db.delete_prefix(prefix).await?, + StorageBackend::S3(s3) => s3.delete_prefix(prefix).await?, } + + // remove existing local archive index files. + let local_index_folder = self.config.local_archive_cache_path.join(&prefix); + if local_index_folder.exists() { + // FIXME: make this work when prefix is not a folder? + tokio::fs::remove_dir_all(&local_index_folder) + .await + .with_context(|| { + format!( + "error when trying to remove local index: {:?}", + &local_index_folder + ) + })?; + } + + // for archive_filename in paths { + // // delete remove archive and remote index + // storage.delete_prefix(&archive_filename).await?; + + // // delete eventually existing local indexes + // let local_index_file = local_archive_cache.join(format!("{archive_filename}.index")); + // if local_index_file.exists() { + // tokio::fs::remove_file(&local_index_file) + // .await + // .with_context(|| { + // format!("error when trying to remove local index: {local_index_file:?}") + // })?; + // } + // } + Ok(()) } // We're using `&self` instead of consuming `self` or creating a Drop impl because during tests diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index 9b6025981..10e5c628c 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -18,6 +18,7 @@ docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_logging = { path = "../docs_rs_logging" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_storage = { path = "../docs_rs_storage" } docs_rs_utils = { path = "../docs_rs_utils" } futures-util = { workspace = true } itertools = { workspace = true } diff --git a/crates/docs_rs_watcher/src/build_queue.rs b/crates/docs_rs_watcher/src/build_queue.rs index 0ec6c8e0a..e7f7eaf46 100644 --- a/crates/docs_rs_watcher/src/build_queue.rs +++ b/crates/docs_rs_watcher/src/build_queue.rs @@ -1,12 +1,16 @@ -use std::sync::Arc; - -use crate::{index::Index, priorities::get_crate_priority}; +use crate::{ + db::delete::{delete_crate, delete_version}, + index::Index, + priorities::get_crate_priority, +}; use anyhow::{Context as _, Result}; use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::{ service_config::{ConfigName, get_config, set_config}, - types::version::Version, + types::{krate_name::KrateName, version::Version}, }; +use docs_rs_fastly::Cdn; +use docs_rs_storage::AsyncStorage; use tracing::{debug, error, info, warn}; pub async fn last_seen_reference( @@ -31,7 +35,9 @@ pub async fn set_last_seen_reference( pub async fn get_new_crates( conn: &mut sqlx::PgConnection, index: &Index, - build_queue: Arc, + build_queue: &AsyncBuildQueue, + storage: &AsyncStorage, + cdn: &Cdn, ) -> Result { let last_seen_reference = last_seen_reference(conn).await?; let last_seen_reference = if let Some(oid) = last_seen_reference { @@ -55,7 +61,7 @@ pub async fn get_new_crates( for change in &changes { if let Some((ref krate, ..)) = change.crate_deleted() { - match delete_crate(&mut conn, &storage, &config, krate).await { + match delete_crate(&mut conn, &storage, krate).await { Ok(_) => info!( "crate {} was deleted from the index and the database", krate @@ -66,8 +72,10 @@ pub async fn get_new_crates( } }; - docs_rs_fastly::queue_crate_invalidation(krate).await; - build_queue.remove_crate_from_queue(krate).await?; + let krate: KrateName = krate.parse().unwrap(); + + cdn.queue_crate_invalidation(&krate).await; + build_queue.remove_crate_from_queue(&krate).await?; continue; } @@ -77,7 +85,7 @@ pub async fn get_new_crates( .parse() .context("couldn't parse release version as semver")?; - match delete_version(&mut conn, &storage, &config, &release.name, &version).await { + match delete_version(&mut conn, &storage, &release.name, &version).await { Ok(_) => info!( "release {}-{} was deleted from the index and the database", release.name, release.version @@ -87,7 +95,8 @@ pub async fn get_new_crates( } } - queue_crate_invalidation(&release.name).await; + let krate: KrateName = release.name.parse().unwrap(); + cdn.queue_crate_invalidation(&krate).await; build_queue .remove_version_from_queue(&release.name, &version) .await?; @@ -139,7 +148,7 @@ pub async fn get_new_crates( error!(?err, %release.name, %release.version, "error setting yanked status"); } - queue_crate_invalidation(&release.name).await; + cdn.queue_crate_invalidation(&release.name).await; } } diff --git a/crates/docs_rs_watcher/src/db/delete.rs b/crates/docs_rs_watcher/src/db/delete.rs index e52084cfa..7e346b00a 100644 --- a/crates/docs_rs_watcher/src/db/delete.rs +++ b/crates/docs_rs_watcher/src/db/delete.rs @@ -1,21 +1,16 @@ -use anyhow::Context as _; +use anyhow::{Context as _, Result}; use docs_rs_database::types::{CrateId, version::Version}; use docs_rs_storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}; -use fn_error_context::context; use sqlx::Connection; -use super::update_latest_version_id; - /// List of directories in docs.rs's underlying storage (either the database or S3) containing a /// subdirectory named after the crate. Those subdirectories will be deleted. static LIBRARY_STORAGE_PATHS_TO_DELETE: &[&str] = &["rustdoc", "rustdoc-json", "sources"]; static OTHER_STORAGE_PATHS_TO_DELETE: &[&str] = &["sources"]; -#[context("error trying to delete crate {name} from database")] pub async fn delete_crate( conn: &mut sqlx::PgConnection, storage: &AsyncStorage, - config: &Config, name: &str, ) -> Result<()> { let Some(crate_id) = get_id(conn, name).await? else { @@ -35,29 +30,14 @@ pub async fn delete_crate( // it will include existing archives. let remote_folder = format!("{prefix}/{name}/"); storage.delete_prefix(&remote_folder).await?; - - // remove existing local archive index files. - let local_index_folder = config.storage.local_archive_cache_path.join(&remote_folder); - if local_index_folder.exists() { - tokio::fs::remove_dir_all(&local_index_folder) - .await - .with_context(|| { - format!( - "error when trying to remove local index: {:?}", - &local_index_folder - ) - })?; - } } Ok(()) } -#[context("error trying to delete release {name}-{version} from database")] pub async fn delete_version( conn: &mut sqlx::PgConnection, storage: &AsyncStorage, - config: &Config, name: &str, version: &Version, ) -> Result<()> { @@ -78,7 +58,6 @@ pub async fn delete_version( .await?; } - let local_archive_cache = &config.storage.local_archive_cache_path; let mut paths = vec![source_archive_path(name, version)]; if is_library { paths.push(rustdoc_archive_path(name, version)); @@ -87,16 +66,6 @@ pub async fn delete_version( for archive_filename in paths { // delete remove archive and remote index storage.delete_prefix(&archive_filename).await?; - - // delete eventually existing local indexes - let local_index_file = local_archive_cache.join(format!("{archive_filename}.index")); - if local_index_file.exists() { - tokio::fs::remove_file(&local_index_file) - .await - .with_context(|| { - format!("error when trying to remove local index: {local_index_file:?}") - })?; - } } Ok(()) @@ -145,7 +114,7 @@ async fn delete_version_from_database( .await? .unwrap_or(false); - update_latest_version_id(&mut transaction, crate_id).await?; + // FIXME: update_latest_version_id(&mut transaction, crate_id).await?; transaction.commit().await?; Ok(is_library) @@ -199,359 +168,359 @@ async fn delete_crate_from_database( Ok(has_library) } -#[cfg(test)] -mod tests { - use super::*; - use crate::db::ReleaseId; - use crate::registry_api::{CrateOwner, OwnerKind}; - use crate::storage::{CompressionAlgorithm, rustdoc_json_path}; - use crate::test::{KRATE, V1, V2, async_wrapper, fake_release_that_failed_before_build}; - use test_case::test_case; - - async fn crate_exists(conn: &mut sqlx::PgConnection, name: &str) -> Result { - Ok(sqlx::query!("SELECT id FROM crates WHERE name = $1;", name) - .fetch_optional(conn) - .await? - .is_some()) - } - - async fn release_exists(conn: &mut sqlx::PgConnection, id: ReleaseId) -> Result { - Ok(sqlx::query!("SELECT id FROM releases WHERE id = $1;", id.0) - .fetch_optional(conn) - .await? - .is_some()) - } - - #[test] - fn test_get_id_uses_normalization() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("Some_Package") - .version(V1) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - assert!(get_id(&mut conn, "some-package").await.is_ok()); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_delete_crate(archive_storage: bool) { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - // Create fake packages in the database - let pkg1_v1_id = env - .fake_release() - .await - .name("package-1") - .version(V1) - .archive_storage(archive_storage) - .create() - .await?; - let pkg1_v2_id = env - .fake_release() - .await - .name("package-1") - .version(V2) - .archive_storage(archive_storage) - .create() - .await?; - let pkg2_id = env - .fake_release() - .await - .name("package-2") - .version(V1) - .archive_storage(archive_storage) - .create() - .await?; - - assert!(crate_exists(&mut conn, "package-1").await?); - assert!(crate_exists(&mut conn, "package-2").await?); - assert!(release_exists(&mut conn, pkg1_v1_id).await?); - assert!(release_exists(&mut conn, pkg1_v2_id).await?); - assert!(release_exists(&mut conn, pkg2_id).await?); - for (pkg, version) in &[("package-1", V1), ("package-1", V2), ("package-2", V1)] { - assert!( - env.async_storage() - .rustdoc_file_exists( - pkg, - version, - None, - &format!("{pkg}/index.html"), - archive_storage - ) - .await? - ); - } - - delete_crate(&mut conn, env.async_storage(), env.config(), "package-1").await?; - - assert!(!crate_exists(&mut conn, "package-1").await?); - assert!(crate_exists(&mut conn, "package-2").await?); - assert!(!release_exists(&mut conn, pkg1_v1_id).await?); - assert!(!release_exists(&mut conn, pkg1_v2_id).await?); - assert!(release_exists(&mut conn, pkg2_id).await?); - - // files for package 2 still exists - assert!( - env.async_storage() - .rustdoc_file_exists( - "package-2", - &V1, - None, - "package-2/index.html", - archive_storage - ) - .await? - ); - - // files for package 1 are gone - if archive_storage { - assert!( - !env.async_storage() - .exists(&rustdoc_archive_path("package-1", &V1)) - .await? - ); - assert!( - !env.async_storage() - .exists(&rustdoc_archive_path("package-1", &V2)) - .await? - ); - } else { - assert!( - !env.async_storage() - .rustdoc_file_exists( - "package-1", - &V1, - None, - "package-1/index.html", - archive_storage - ) - .await? - ); - assert!( - !env.async_storage() - .rustdoc_file_exists( - "package-1", - &V2, - None, - "package-1/index.html", - archive_storage - ) - .await? - ); - } - - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn test_delete_version(archive_storage: bool) { - async_wrapper(|env| async move { - async fn owners( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, - ) -> Result> { - Ok(sqlx::query!( - "SELECT login FROM owners - INNER JOIN owner_rels ON owners.id = owner_rels.oid - WHERE owner_rels.cid = $1", - crate_id.0, - ) - .fetch_all(conn) - .await? - .into_iter() - .map(|row| row.login) - .collect()) - } - - async fn json_exists(storage: &AsyncStorage, version: &Version) -> Result { - storage - .exists(&rustdoc_json_path( - "a", - version, - "x86_64-unknown-linux-gnu", - crate::storage::RustdocJsonFormatVersion::Latest, - Some(CompressionAlgorithm::Zstd), - )) - .await - } - - let mut conn = env.async_db().async_conn().await; - let v1 = env - .fake_release() - .await - .name("a") - .version(V1) - .archive_storage(archive_storage) - .add_owner(CrateOwner { - login: "malicious actor".into(), - avatar: "https://example.org/malicious".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - assert!(release_exists(&mut conn, v1).await?); - assert!( - env.async_storage() - .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) - .await? - ); - assert!(json_exists(env.async_storage(), &V1).await?); - let crate_id = sqlx::query_scalar!( - r#"SELECT crate_id as "crate_id: CrateId" FROM releases WHERE id = $1"#, - v1.0 - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!( - owners(&mut conn, crate_id).await?, - vec!["malicious actor".to_string()] - ); - - let v2 = env - .fake_release() - .await - .name("a") - .version(V2) - .archive_storage(archive_storage) - .add_owner(CrateOwner { - login: "Peter Rabbit".into(), - avatar: "https://example.org/peter".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - assert!(release_exists(&mut conn, v2).await?); - assert!( - env.async_storage() - .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) - .await? - ); - assert!(json_exists(env.async_storage(), &V2).await?); - assert_eq!( - owners(&mut conn, crate_id).await?, - vec!["Peter Rabbit".to_string()] - ); - - delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; - assert!(!release_exists(&mut conn, v1).await?); - if archive_storage { - // for archive storage the archive and index files - // need to be cleaned up. - let rustdoc_archive = rustdoc_archive_path("a", &V1); - assert!(!env.async_storage().exists(&rustdoc_archive).await?); - - // local and remote index are gone too - let archive_index = format!("{rustdoc_archive}.index"); - assert!(!env.async_storage().exists(&archive_index).await?); - assert!( - !env.config() - .local_archive_cache_path - .join(&archive_index) - .exists() - ); - } else { - assert!( - !env.async_storage() - .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) - .await? - ); - } - assert!(!json_exists(env.async_storage(), &V1,).await?); - - assert!(release_exists(&mut conn, v2).await?); - assert!( - env.async_storage() - .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) - .await? - ); - assert!(json_exists(env.async_storage(), &V2).await?); - assert_eq!( - owners(&mut conn, crate_id).await?, - vec!["Peter Rabbit".to_string()] - ); - - // FIXME: remove for now until test frontend is async - // let web = env.frontend(); - // assert_success("/a/2.0.0/a/", web)?; - // assert_eq!(web.get("/a/1.0.0/a/").send()?.status(), 404); - - Ok(()) - }) - } - - #[test] - fn test_delete_incomplete_version() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let (release_id, _) = - fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; - - delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; - - assert!(!release_exists(&mut conn, release_id).await?); - - Ok(()) - }) - } - - #[test] - fn test_delete_incomplete_crate() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let (release_id, _) = - fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; - - delete_crate(&mut conn, env.async_storage(), env.config(), "a").await?; - - assert!(!crate_exists(&mut conn, "a").await?); - assert!(!release_exists(&mut conn, release_id).await?); - - Ok(()) - }) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_delete_missing_crate_doesnt_error() -> Result<()> { - let env = crate::test::TestEnvironment::new().await?; - - let db = env.async_db(); - let mut conn = db.async_conn().await; - - assert!(!crate_exists(&mut conn, KRATE).await?); - delete_crate(&mut conn, env.async_storage(), env.config(), KRATE).await?; - - assert!(!crate_exists(&mut conn, KRATE).await?); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_delete_missing_version_doesnt_error() -> Result<()> { - let env = crate::test::TestEnvironment::new().await?; - - let db = env.async_db(); - let mut conn = db.async_conn().await; - - assert!(!crate_exists(&mut conn, KRATE).await?); - - delete_version(&mut conn, env.async_storage(), env.config(), KRATE, &V1).await?; - - assert!(!crate_exists(&mut conn, KRATE).await?); - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::db::ReleaseId; +// use crate::registry_api::{CrateOwner, OwnerKind}; +// use crate::storage::{CompressionAlgorithm, rustdoc_json_path}; +// use crate::test::{KRATE, V1, V2, async_wrapper, fake_release_that_failed_before_build}; +// use test_case::test_case; + +// async fn crate_exists(conn: &mut sqlx::PgConnection, name: &str) -> Result { +// Ok(sqlx::query!("SELECT id FROM crates WHERE name = $1;", name) +// .fetch_optional(conn) +// .await? +// .is_some()) +// } + +// async fn release_exists(conn: &mut sqlx::PgConnection, id: ReleaseId) -> Result { +// Ok(sqlx::query!("SELECT id FROM releases WHERE id = $1;", id.0) +// .fetch_optional(conn) +// .await? +// .is_some()) +// } + +// #[test] +// fn test_get_id_uses_normalization() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("Some_Package") +// .version(V1) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// assert!(get_id(&mut conn, "some-package").await.is_ok()); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_delete_crate(archive_storage: bool) { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// // Create fake packages in the database +// let pkg1_v1_id = env +// .fake_release() +// .await +// .name("package-1") +// .version(V1) +// .archive_storage(archive_storage) +// .create() +// .await?; +// let pkg1_v2_id = env +// .fake_release() +// .await +// .name("package-1") +// .version(V2) +// .archive_storage(archive_storage) +// .create() +// .await?; +// let pkg2_id = env +// .fake_release() +// .await +// .name("package-2") +// .version(V1) +// .archive_storage(archive_storage) +// .create() +// .await?; + +// assert!(crate_exists(&mut conn, "package-1").await?); +// assert!(crate_exists(&mut conn, "package-2").await?); +// assert!(release_exists(&mut conn, pkg1_v1_id).await?); +// assert!(release_exists(&mut conn, pkg1_v2_id).await?); +// assert!(release_exists(&mut conn, pkg2_id).await?); +// for (pkg, version) in &[("package-1", V1), ("package-1", V2), ("package-2", V1)] { +// assert!( +// env.async_storage() +// .rustdoc_file_exists( +// pkg, +// version, +// None, +// &format!("{pkg}/index.html"), +// archive_storage +// ) +// .await? +// ); +// } + +// delete_crate(&mut conn, env.async_storage(), env.config(), "package-1").await?; + +// assert!(!crate_exists(&mut conn, "package-1").await?); +// assert!(crate_exists(&mut conn, "package-2").await?); +// assert!(!release_exists(&mut conn, pkg1_v1_id).await?); +// assert!(!release_exists(&mut conn, pkg1_v2_id).await?); +// assert!(release_exists(&mut conn, pkg2_id).await?); + +// // files for package 2 still exists +// assert!( +// env.async_storage() +// .rustdoc_file_exists( +// "package-2", +// &V1, +// None, +// "package-2/index.html", +// archive_storage +// ) +// .await? +// ); + +// // files for package 1 are gone +// if archive_storage { +// assert!( +// !env.async_storage() +// .exists(&rustdoc_archive_path("package-1", &V1)) +// .await? +// ); +// assert!( +// !env.async_storage() +// .exists(&rustdoc_archive_path("package-1", &V2)) +// .await? +// ); +// } else { +// assert!( +// !env.async_storage() +// .rustdoc_file_exists( +// "package-1", +// &V1, +// None, +// "package-1/index.html", +// archive_storage +// ) +// .await? +// ); +// assert!( +// !env.async_storage() +// .rustdoc_file_exists( +// "package-1", +// &V2, +// None, +// "package-1/index.html", +// archive_storage +// ) +// .await? +// ); +// } + +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_delete_version(archive_storage: bool) { +// async_wrapper(|env| async move { +// async fn owners( +// conn: &mut sqlx::PgConnection, +// crate_id: CrateId, +// ) -> Result> { +// Ok(sqlx::query!( +// "SELECT login FROM owners +// INNER JOIN owner_rels ON owners.id = owner_rels.oid +// WHERE owner_rels.cid = $1", +// crate_id.0, +// ) +// .fetch_all(conn) +// .await? +// .into_iter() +// .map(|row| row.login) +// .collect()) +// } + +// async fn json_exists(storage: &AsyncStorage, version: &Version) -> Result { +// storage +// .exists(&rustdoc_json_path( +// "a", +// version, +// "x86_64-unknown-linux-gnu", +// crate::storage::RustdocJsonFormatVersion::Latest, +// Some(CompressionAlgorithm::Zstd), +// )) +// .await +// } + +// let mut conn = env.async_db().async_conn().await; +// let v1 = env +// .fake_release() +// .await +// .name("a") +// .version(V1) +// .archive_storage(archive_storage) +// .add_owner(CrateOwner { +// login: "malicious actor".into(), +// avatar: "https://example.org/malicious".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; +// assert!(release_exists(&mut conn, v1).await?); +// assert!( +// env.async_storage() +// .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) +// .await? +// ); +// assert!(json_exists(env.async_storage(), &V1).await?); +// let crate_id = sqlx::query_scalar!( +// r#"SELECT crate_id as "crate_id: CrateId" FROM releases WHERE id = $1"#, +// v1.0 +// ) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!( +// owners(&mut conn, crate_id).await?, +// vec!["malicious actor".to_string()] +// ); + +// let v2 = env +// .fake_release() +// .await +// .name("a") +// .version(V2) +// .archive_storage(archive_storage) +// .add_owner(CrateOwner { +// login: "Peter Rabbit".into(), +// avatar: "https://example.org/peter".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; +// assert!(release_exists(&mut conn, v2).await?); +// assert!( +// env.async_storage() +// .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) +// .await? +// ); +// assert!(json_exists(env.async_storage(), &V2).await?); +// assert_eq!( +// owners(&mut conn, crate_id).await?, +// vec!["Peter Rabbit".to_string()] +// ); + +// delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; +// assert!(!release_exists(&mut conn, v1).await?); +// if archive_storage { +// // for archive storage the archive and index files +// // need to be cleaned up. +// let rustdoc_archive = rustdoc_archive_path("a", &V1); +// assert!(!env.async_storage().exists(&rustdoc_archive).await?); + +// // local and remote index are gone too +// let archive_index = format!("{rustdoc_archive}.index"); +// assert!(!env.async_storage().exists(&archive_index).await?); +// assert!( +// !env.config() +// .local_archive_cache_path +// .join(&archive_index) +// .exists() +// ); +// } else { +// assert!( +// !env.async_storage() +// .rustdoc_file_exists("a", &V1, None, "a/index.html", archive_storage) +// .await? +// ); +// } +// assert!(!json_exists(env.async_storage(), &V1,).await?); + +// assert!(release_exists(&mut conn, v2).await?); +// assert!( +// env.async_storage() +// .rustdoc_file_exists("a", &V2, None, "a/index.html", archive_storage) +// .await? +// ); +// assert!(json_exists(env.async_storage(), &V2).await?); +// assert_eq!( +// owners(&mut conn, crate_id).await?, +// vec!["Peter Rabbit".to_string()] +// ); + +// // FIXME: remove for now until test frontend is async +// // let web = env.frontend(); +// // assert_success("/a/2.0.0/a/", web)?; +// // assert_eq!(web.get("/a/1.0.0/a/").send()?.status(), 404); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_delete_incomplete_version() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let (release_id, _) = +// fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; + +// delete_version(&mut conn, env.async_storage(), env.config(), "a", &V1).await?; + +// assert!(!release_exists(&mut conn, release_id).await?); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_delete_incomplete_crate() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let (release_id, _) = +// fake_release_that_failed_before_build(&mut conn, "a", V1, "some-error").await?; + +// delete_crate(&mut conn, env.async_storage(), env.config(), "a").await?; + +// assert!(!crate_exists(&mut conn, "a").await?); +// assert!(!release_exists(&mut conn, release_id).await?); + +// Ok(()) +// }) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_delete_missing_crate_doesnt_error() -> Result<()> { +// let env = crate::test::TestEnvironment::new().await?; + +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// assert!(!crate_exists(&mut conn, KRATE).await?); +// delete_crate(&mut conn, env.async_storage(), env.config(), KRATE).await?; + +// assert!(!crate_exists(&mut conn, KRATE).await?); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_delete_missing_version_doesnt_error() -> Result<()> { +// let env = crate::test::TestEnvironment::new().await?; + +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// assert!(!crate_exists(&mut conn, KRATE).await?); + +// delete_version(&mut conn, env.async_storage(), env.config(), KRATE, &V1).await?; + +// assert!(!crate_exists(&mut conn, KRATE).await?); + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_watcher/src/db/mod.rs b/crates/docs_rs_watcher/src/db/mod.rs index e69de29bb..2ce5b1044 100644 --- a/crates/docs_rs_watcher/src/db/mod.rs +++ b/crates/docs_rs_watcher/src/db/mod.rs @@ -0,0 +1 @@ +pub(crate) mod delete; diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index 77f74c8a2..42b4a4edb 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -1,6 +1,7 @@ mod build_queue; mod config; mod consistency; +pub(crate) mod db; mod index; mod priorities; pub mod repositories; diff --git a/src/db/delete.rs b/src/db/delete.rs deleted file mode 100644 index e69de29bb..000000000 From 6ba92a53b1494a07f1b652166f6d4cf1e044e059 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 10 Dec 2025 06:38:14 +0100 Subject: [PATCH 13/46] watcher --- crates/docs_rs_context/src/lib.rs | 2 +- crates/docs_rs_logging/src/lib.rs | 1 + crates/docs_rs_watcher/src/build_queue.rs | 70 +++++++++++++++++++++-- 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/crates/docs_rs_context/src/lib.rs b/crates/docs_rs_context/src/lib.rs index 7d41220f5..1517663c3 100644 --- a/crates/docs_rs_context/src/lib.rs +++ b/crates/docs_rs_context/src/lib.rs @@ -1,5 +1,5 @@ use anyhow::{Result, anyhow}; -use docs_rs_build_queue::{AsyncBuildQueue, Config}; +use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::Pool; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; use std::sync::Arc; diff --git a/crates/docs_rs_logging/src/lib.rs b/crates/docs_rs_logging/src/lib.rs index 49c0a02eb..4eaa8facc 100644 --- a/crates/docs_rs_logging/src/lib.rs +++ b/crates/docs_rs_logging/src/lib.rs @@ -6,6 +6,7 @@ use std::{env, str::FromStr as _, sync::Arc}; use tracing_subscriber::{EnvFilter, filter::Directive, prelude::*}; pub struct Guard { + #[allow(dead_code)] sentry_guard: Option, } diff --git a/crates/docs_rs_watcher/src/build_queue.rs b/crates/docs_rs_watcher/src/build_queue.rs index e7f7eaf46..a432407c9 100644 --- a/crates/docs_rs_watcher/src/build_queue.rs +++ b/crates/docs_rs_watcher/src/build_queue.rs @@ -7,7 +7,7 @@ use anyhow::{Context as _, Result}; use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::{ service_config::{ConfigName, get_config, set_config}, - types::{krate_name::KrateName, version::Version}, + types::{CrateId, krate_name::KrateName, version::Version}, }; use docs_rs_fastly::Cdn; use docs_rs_storage::AsyncStorage; @@ -61,7 +61,7 @@ pub async fn get_new_crates( for change in &changes { if let Some((ref krate, ..)) = change.crate_deleted() { - match delete_crate(&mut conn, &storage, krate).await { + match delete_crate(&mut *conn, &storage, krate).await { Ok(_) => info!( "crate {} was deleted from the index and the database", krate @@ -85,7 +85,7 @@ pub async fn get_new_crates( .parse() .context("couldn't parse release version as semver")?; - match delete_version(&mut conn, &storage, &release.name, &version).await { + match delete_version(&mut *conn, &storage, &release.name, &version).await { Ok(_) => info!( "release {}-{} was deleted from the index and the database", release.name, release.version @@ -104,7 +104,7 @@ pub async fn get_new_crates( } if let Some(release) = change.added() { - let priority = get_crate_priority(&mut conn, &release.name).await?; + let priority = get_crate_priority(&mut *conn, &release.name).await?; match build_queue .add_crate( @@ -138,7 +138,8 @@ pub async fn get_new_crates( // https://github.com/rust-lang/docs.rs/issues/1934 if let Ok(release_version) = Version::parse(&release.version) && let Err(err) = set_yanked_inner( - &mut conn, + &mut *conn, + build_queue, release.name.as_str(), &release_version, yanked.is_some(), @@ -148,7 +149,8 @@ pub async fn get_new_crates( error!(?err, %release.name, %release.version, "error setting yanked status"); } - cdn.queue_crate_invalidation(&release.name).await; + let krate: KrateName = release.name.parse().unwrap(); + cdn.queue_crate_invalidation(&krate).await; } } @@ -159,3 +161,59 @@ pub async fn get_new_crates( Ok(crates_added) } + +async fn set_yanked_inner( + conn: &mut sqlx::PgConnection, + build_queue: &AsyncBuildQueue, + name: &str, + version: &Version, + yanked: bool, +) -> Result<()> { + let activity = if yanked { "yanked" } else { "unyanked" }; + + if let Some(crate_id) = sqlx::query_scalar!( + r#"UPDATE releases + SET yanked = $3 + FROM crates + WHERE crates.id = releases.crate_id + AND name = $1 + AND version = $2 + RETURNING crates.id as "id: CrateId" + "#, + name, + version as _, + yanked, + ) + .fetch_optional(&mut *conn) + .await? + { + debug!( + %name, + %version, + %activity, + "updating latest version id" + ); + // FIXME: update_latest_version_id(&mut *conn, crate_id).await?; + } else { + match build_queue.has_build_queued(name, version).await { + Ok(false) => { + error!( + %name, + %version, + "tried to yank or unyank non-existing release", + ); + } + Ok(true) => { + // the rustwide builder will fetch the current yank state from + // crates.io, so and missed update here will be fixed after the + // build is finished. + } + Err(err) => { + // FIXME: add back report_error? + error!(?err, "error trying to fetch build queue"); + } + } + } + + Ok(()) +} From ca239de8d937d77fbb3b743979bafa70389c27e1 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 10 Dec 2025 06:48:10 +0100 Subject: [PATCH 14/46] move --- crates/docs_rs_build_queue/src/config.rs | 5 +- crates/docs_rs_build_queue/src/lib.rs | 2 +- crates/docs_rs_build_queue/src/rebuilds.rs | 76 +------------------- crates/docs_rs_watcher/src/config.rs | 4 ++ crates/docs_rs_watcher/src/lib.rs | 1 + crates/docs_rs_watcher/src/main.rs | 19 ++--- crates/docs_rs_watcher/src/rebuilds.rs | 80 ++++++++++++++++++++++ 7 files changed, 98 insertions(+), 89 deletions(-) create mode 100644 crates/docs_rs_watcher/src/rebuilds.rs diff --git a/crates/docs_rs_build_queue/src/config.rs b/crates/docs_rs_build_queue/src/config.rs index 3842ba3bb..f1422e01f 100644 --- a/crates/docs_rs_build_queue/src/config.rs +++ b/crates/docs_rs_build_queue/src/config.rs @@ -1,17 +1,14 @@ -use docs_rs_env_vars::{env, maybe_env}; +use docs_rs_env_vars::env; #[derive(Debug)] pub struct Config { pub build_attempts: u16, - // automatic rebuild configuration - pub max_queued_rebuilds: Option, } impl Config { pub fn from_environment() -> anyhow::Result { Ok(Self { build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, - max_queued_rebuilds: maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?, }) } } diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 2ebea148e..03687047c 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -1,6 +1,6 @@ mod config; mod metrics; -pub mod rebuilds; +mod rebuilds; pub use config::Config; diff --git a/crates/docs_rs_build_queue/src/rebuilds.rs b/crates/docs_rs_build_queue/src/rebuilds.rs index 28a087f5f..0107782ba 100644 --- a/crates/docs_rs_build_queue/src/rebuilds.rs +++ b/crates/docs_rs_build_queue/src/rebuilds.rs @@ -1,84 +1,10 @@ -use crate::{AsyncBuildQueue, PRIORITY_BROKEN_RUSTDOC, PRIORITY_CONTINUOUS, config::Config}; +use crate::{AsyncBuildQueue, Config, PRIORITY_BROKEN_RUSTDOC, PRIORITY_CONTINUOUS}; use anyhow::Result; use chrono::NaiveDate; use docs_rs_database::types::version::Version; use futures_util::StreamExt as _; use tracing::{info, instrument}; -/// Queue rebuilds as configured. -/// -/// The idea is to rebuild: -/// * the latest release of each crate -/// * when the nightly version is older than our configured threshold -/// * and there was a successful build for that release, that included documentation. -/// * starting with the oldest nightly versions. -/// * also checking if there is already a build queued. -/// -/// This might exclude releases from rebuilds that -/// * previously failed but would succeed with a newer nightly version -/// * previously failed but would succeed just with a retry. -#[instrument(skip_all)] -pub async fn queue_rebuilds( - conn: &mut sqlx::PgConnection, - config: &Config, - build_queue: &AsyncBuildQueue, -) -> Result<()> { - let already_queued_rebuilds: usize = build_queue - .pending_count_by_priority() - .await? - .iter() - .filter_map(|(priority, count)| (*priority >= PRIORITY_CONTINUOUS).then_some(count)) - .sum(); - - let rebuilds_to_queue = config - .max_queued_rebuilds - .expect("config.max_queued_rebuilds not set") as i64 - - already_queued_rebuilds as i64; - - if rebuilds_to_queue <= 0 { - info!("not queueing rebuilds; queue limit reached"); - return Ok(()); - } - - let mut results = sqlx::query!( - r#"SELECT i.* FROM ( - SELECT - c.name, - r.version as "version: Version", - ( - SELECT MAX(COALESCE(b.build_finished, b.build_started)) - FROM builds AS b - WHERE b.rid = r.id - ) AS last_build_attempt - FROM crates AS c - INNER JOIN releases AS r ON c.latest_version_id = r.id - - WHERE - r.rustdoc_status = TRUE - ) as i - ORDER BY i.last_build_attempt ASC - LIMIT $1"#, - rebuilds_to_queue, - ) - .fetch(&mut *conn); - - while let Some(row) = results.next().await { - let row = row?; - - if !build_queue - .has_build_queued(&row.name, &row.version) - .await? - { - info!("queueing rebuild for {} {}...", &row.name, &row.version); - build_queue - .add_crate(&row.name, &row.version, PRIORITY_CONTINUOUS, None) - .await?; - } - } - - Ok(()) -} - /// Queue rebuilds for failed crates due to a faulty version of rustdoc /// /// It is assumed that the version of rustdoc matches the one of rustc, which is persisted in the DB. diff --git a/crates/docs_rs_watcher/src/config.rs b/crates/docs_rs_watcher/src/config.rs index 9a516dd18..c7abd7137 100644 --- a/crates/docs_rs_watcher/src/config.rs +++ b/crates/docs_rs_watcher/src/config.rs @@ -20,6 +20,9 @@ pub struct Config { // GitLab authentication pub gitlab_accesstoken: Option, + + // automatic rebuild configuration + pub max_queued_rebuilds: Option, } impl Config { @@ -40,6 +43,7 @@ impl Config { github_accesstoken: maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?, github_updater_min_rate_limit: env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500u32)?, gitlab_accesstoken: maybe_env("DOCSRS_GITLAB_ACCESSTOKEN")?, + max_queued_rebuilds: maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?, }) } } diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index 42b4a4edb..d7c3f9364 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -4,6 +4,7 @@ mod consistency; pub(crate) mod db; mod index; mod priorities; +pub mod rebuilds; pub mod repositories; pub mod service_metrics; mod utils; diff --git a/crates/docs_rs_watcher/src/main.rs b/crates/docs_rs_watcher/src/main.rs index 0abe805c4..f75bc385b 100644 --- a/crates/docs_rs_watcher/src/main.rs +++ b/crates/docs_rs_watcher/src/main.rs @@ -1,12 +1,12 @@ use anyhow::{Context as _, Result}; use clap::Parser; -use docs_rs_build_queue::{AsyncBuildQueue, rebuilds::queue_rebuilds}; +use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::Pool; use docs_rs_opentelemetry::AnyMeterProvider; use docs_rs_utils::start_async_cron; use docs_rs_watcher::{ - Config, repositories::RepositoryStatsUpdater, service_metrics::OtelServiceMetrics, - watch_registry, + Config, rebuilds::queue_rebuilds, repositories::RepositoryStatsUpdater, + service_metrics::OtelServiceMetrics, watch_registry, }; use std::{sync::Arc, time::Duration}; use tracing::{info, trace}; @@ -39,7 +39,7 @@ async fn main() -> anyhow::Result<()> { .with_build_queue() .await?; - let config = Config::from_environment()?; + let config = Arc::new(Config::from_environment()?); let pool = context.pool()?; let build_queue = context.build_queue()?; @@ -48,7 +48,7 @@ async fn main() -> anyhow::Result<()> { start_background_repository_stats_updater(&config, pool.clone()); } if args.queue_rebuilds { - start_background_queue_rebuild(pool.clone(), build_queue.clone())?; + start_background_queue_rebuild(config.clone(), pool.clone(), build_queue.clone())?; } // When people run the services separately, we assume that we can collect service @@ -81,10 +81,11 @@ fn start_background_repository_stats_updater(config: &Config, pool: Pool) { ); } -fn start_background_queue_rebuild(pool: Pool, build_queue: Arc) -> Result<()> { - // TODO: is refetching th config ok here? or should we store it globally somewhere? - let config = Arc::new(docs_rs_build_queue::Config::from_environment()?); - +fn start_background_queue_rebuild( + config: Arc, + pool: Pool, + build_queue: Arc, +) -> Result<()> { if config.max_queued_rebuilds.is_none() { info!("rebuild config incomplete, skipping rebuild queueing"); return Ok(()); diff --git a/crates/docs_rs_watcher/src/rebuilds.rs b/crates/docs_rs_watcher/src/rebuilds.rs new file mode 100644 index 000000000..35dc1eac0 --- /dev/null +++ b/crates/docs_rs_watcher/src/rebuilds.rs @@ -0,0 +1,80 @@ +use crate::Config; +use anyhow::Result; +use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS}; +use docs_rs_database::types::version::Version; +use futures_util::StreamExt as _; +use tracing::{info, instrument}; + +/// Queue rebuilds as configured. +/// +/// The idea is to rebuild: +/// * the latest release of each crate +/// * when the nightly version is older than our configured threshold +/// * and there was a successful build for that release, that included documentation. +/// * starting with the oldest nightly versions. +/// * also checking if there is already a build queued. +/// +/// This might exclude releases from rebuilds that +/// * previously failed but would succeed with a newer nightly version +/// * previously failed but would succeed just with a retry. +#[instrument(skip_all)] +pub async fn queue_rebuilds( + conn: &mut sqlx::PgConnection, + config: &Config, + build_queue: &AsyncBuildQueue, +) -> Result<()> { + let already_queued_rebuilds: usize = build_queue + .pending_count_by_priority() + .await? + .iter() + .filter_map(|(priority, count)| (*priority >= PRIORITY_CONTINUOUS).then_some(count)) + .sum(); + + let rebuilds_to_queue = config + .max_queued_rebuilds + .expect("config.max_queued_rebuilds not set") as i64 + - already_queued_rebuilds as i64; + + if rebuilds_to_queue <= 0 { + info!("not queueing rebuilds; queue limit reached"); + return Ok(()); + } + + let mut results = sqlx::query!( + r#"SELECT i.* FROM ( + SELECT + c.name, + r.version as "version: Version", + ( + SELECT MAX(COALESCE(b.build_finished, b.build_started)) + FROM builds AS b + WHERE b.rid = r.id + ) AS last_build_attempt + FROM crates AS c + INNER JOIN releases AS r ON c.latest_version_id = r.id + + WHERE + r.rustdoc_status = TRUE + ) as i + ORDER BY i.last_build_attempt ASC + LIMIT $1"#, + rebuilds_to_queue, + ) + .fetch(&mut *conn); + + while let Some(row) = results.next().await { + let row = row?; + + if !build_queue + .has_build_queued(&row.name, &row.version) + .await? + { + info!("queueing rebuild for {} {}...", &row.name, &row.version); + build_queue + .add_crate(&row.name, &row.version, PRIORITY_CONTINUOUS, None) + .await?; + } + } + + Ok(()) +} From 8d0e41cef8c8cbfb9cdf9fc1196f121c3505c9d0 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 10 Dec 2025 07:00:42 +0100 Subject: [PATCH 15/46] kk --- Cargo.lock | 2 +- Cargo.toml | 1 + crates/docs_rs_cargo_metadata/Cargo.toml | 2 +- crates/docs_rs_cargo_metadata/src/lib.rs | 32 +++++++-------- crates/docs_rs_web_utils/src/lib.rs | 4 +- src/context.rs | 4 +- src/db/mod.rs | 2 - src/lib.rs | 1 - src/utils/daemon.rs | 52 ------------------------ src/utils/mod.rs | 9 ++-- src/web/builds.rs | 5 +-- src/web/cache.rs | 11 ++--- src/web/crate_details.rs | 5 ++- src/web/error.rs | 3 +- src/web/extractors/rustdoc.rs | 10 ++--- src/web/features.rs | 5 +-- src/web/file.rs | 3 +- src/web/mod.rs | 4 +- src/web/page/web_page.rs | 2 +- src/web/releases.rs | 3 +- src/web/rustdoc.rs | 4 +- src/web/source.rs | 3 +- src/web/statics.rs | 5 +-- 23 files changed, 54 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de5ceb63e..c67706181 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1901,6 +1901,7 @@ dependencies = [ "docs_rs_storage", "docs_rs_utils", "docs_rs_watcher", + "docs_rs_web_utils", "docsrs-metadata", "fn-error-context", "font-awesome-as-a-crate", @@ -1982,7 +1983,6 @@ dependencies = [ "anyhow", "bincode 2.0.1", "docs_rs_database", - "rustwide", "semver", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 3ed2ebdef..86296727d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,6 +73,7 @@ docs_rs_storage = { path = "crates/docs_rs_storage" } docs_rs_headers = { path = "crates/docs_rs_headers" } docs_rs_watcher = { path = "crates/docs_rs_watcher" } docs_rs_logging = { path = "crates/docs_rs_logging" } +docs_rs_web_utils = { path = "crates/docs_rs_web_utils" } docsrs-metadata = { path = "crates/metadata" } fn-error-context = "0.2.0" font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } diff --git a/crates/docs_rs_cargo_metadata/Cargo.toml b/crates/docs_rs_cargo_metadata/Cargo.toml index 0b6c68073..4d22c1fe5 100644 --- a/crates/docs_rs_cargo_metadata/Cargo.toml +++ b/crates/docs_rs_cargo_metadata/Cargo.toml @@ -7,7 +7,7 @@ edition = "2024" anyhow = { workspace = true } bincode = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } -rustwide = { workspace = true } +# rustwide = { workspace = true } semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/docs_rs_cargo_metadata/src/lib.rs b/crates/docs_rs_cargo_metadata/src/lib.rs index 3409e610c..f57a14c13 100644 --- a/crates/docs_rs_cargo_metadata/src/lib.rs +++ b/crates/docs_rs_cargo_metadata/src/lib.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result, bail}; use docs_rs_database::types::version::Version; -use rustwide::{Toolchain, Workspace, cmd::Command}; +// use rustwide::{Toolchain, Workspace, cmd::Command}; use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -11,21 +11,21 @@ pub struct CargoMetadata { } impl CargoMetadata { - pub fn load_from_rustwide( - workspace: &Workspace, - toolchain: &Toolchain, - source_dir: &Path, - ) -> Result { - let res = Command::new(workspace, toolchain.cargo()) - .args(&["metadata", "--format-version", "1"]) - .cd(source_dir) - .log_output(false) - .run_capture()?; - let [metadata] = res.stdout_lines() else { - bail!("invalid output returned by `cargo metadata`") - }; - Self::load_from_metadata(metadata) - } + // pub fn load_from_rustwide( + // workspace: &Workspace, + // toolchain: &Toolchain, + // source_dir: &Path, + // ) -> Result { + // let res = Command::new(workspace, toolchain.cargo()) + // .args(&["metadata", "--format-version", "1"]) + // .cd(source_dir) + // .log_output(false) + // .run_capture()?; + // let [metadata] = res.stdout_lines() else { + // bail!("invalid output returned by `cargo metadata`") + // }; + // Self::load_from_metadata(metadata) + // } #[cfg(test)] pub fn load_from_host_path(source_dir: &Path) -> Result { diff --git a/crates/docs_rs_web_utils/src/lib.rs b/crates/docs_rs_web_utils/src/lib.rs index f1ec5ac74..21f8c3337 100644 --- a/crates/docs_rs_web_utils/src/lib.rs +++ b/crates/docs_rs_web_utils/src/lib.rs @@ -10,10 +10,10 @@ pub mod escaped_uri; const FRAGMENT: &AsciiSet = &CONTROLS.add(b' ').add(b'"').add(b'<').add(b'>').add(b'`'); const PATH: &AsciiSet = &FRAGMENT.add(b'#').add(b'?').add(b'{').add(b'}'); -pub(crate) fn encode_url_path(path: &str) -> String { +pub fn encode_url_path(path: &str) -> String { utf8_percent_encode(path, PATH).to_string() } -pub(crate) fn url_decode<'a>(input: &'a str) -> Result> { +pub fn url_decode<'a>(input: &'a str) -> Result> { Ok(percent_encoding::percent_decode(input.as_bytes()).decode_utf8()?) } diff --git a/src/context.rs b/src/context.rs index 595bfa404..57fdc37c6 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,4 +1,4 @@ -use crate::{Config, RegistryApi, cdn::CdnMetrics}; +use crate::{Config, RegistryApi}; use anyhow::Result; use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; use docs_rs_database::Pool; @@ -14,7 +14,7 @@ pub struct Context { pub build_queue: Arc, pub storage: Arc, pub async_storage: Arc, - pub cdn_metrics: Arc, + // pub cdn_metrics: Arc, pub pool: Pool, pub registry_api: Arc, pub repository_stats_updater: Arc, diff --git a/src/db/mod.rs b/src/db/mod.rs index 6a9367da8..62ac3baf8 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -9,13 +9,11 @@ pub(crate) use self::add_package::{ }; pub use self::{ add_package::{update_build_status, update_crate_data_in_database}, - delete::{delete_crate, delete_version}, overrides::Overrides, }; mod add_package; pub mod blacklist; -pub mod delete; mod overrides; pub mod types; diff --git a/src/lib.rs b/src/lib.rs index 9c2b27cd7..5cd338536 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,7 +11,6 @@ pub use self::web::start_web_server; pub use font_awesome_as_a_crate::icons; -pub mod cdn; mod config; mod context; pub mod db; diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs index 24187ab87..48cad6466 100644 --- a/src/utils/daemon.rs +++ b/src/utils/daemon.rs @@ -4,7 +4,6 @@ use crate::{Context, RustwideBuilder, utils::queue_builder, web::start_web_server}; use anyhow::{Error, anyhow}; -use docs_rs_build_queue::rebuilds::queue_rebuilds; use docs_rs_utils::start_async_cron_in_runtime; use docs_rs_watcher::service_metrics::OtelServiceMetrics; use std::sync::Arc; @@ -26,57 +25,6 @@ fn start_registry_watcher(context: &Context) -> Result<(), Error> { Ok(()) } -pub fn start_background_queue_rebuild(context: &Context) -> Result<(), Error> { - let runtime = context.runtime.clone(); - let pool = context.pool.clone(); - let config = context.config.clone(); - let build_queue = context.async_build_queue.clone(); - - if config.build_queue.max_queued_rebuilds.is_none() { - info!("rebuild config incomplete, skipping rebuild queueing"); - return Ok(()); - } - - start_async_cron_in_runtime( - &runtime, - "background queue rebuilder", - Duration::from_secs(60 * 60), - move || { - let pool = pool.clone(); - let build_queue = build_queue.clone(); - let config = config.clone(); - async move { - let mut conn = pool.get_async().await?; - queue_rebuilds(&mut conn, &config.build_queue, &build_queue).await?; - Ok(()) - } - }, - ); - Ok(()) -} - -pub fn start_background_repository_stats_updater(context: &Context) -> Result<(), Error> { - // This call will still skip github repositories updates and continue if no token is provided - // (gitlab doesn't require to have a token). The only time this can return an error is when - // creating a pool or if config fails, which shouldn't happen here because this is run right at - // startup. - let updater = context.repository_stats_updater.clone(); - let runtime = context.runtime.clone(); - start_async_cron_in_runtime( - &runtime, - "repository stats updater", - Duration::from_secs(60 * 60), - move || { - let updater = updater.clone(); - async move { - updater.update_all_crates().await?; - Ok(()) - } - }, - ); - Ok(()) -} - pub fn start_daemon(context: Context, enable_registry_watcher: bool) -> Result<(), Error> { let context = Arc::new(context); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index e98122938..aba76d5ec 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,17 +7,16 @@ pub(crate) use self::{ }; pub use self::{ daemon::start_daemon, - queue::{ - get_crate_pattern_and_priority, get_crate_priority, list_crate_priorities, - remove_crate_priority, set_crate_priority, - }, + // queue::{ + // get_crate_pattern_and_priority, get_crate_priority, list_crate_priorities, + // remove_crate_priority, set_crate_priority, + // }, queue_builder::queue_builder, }; mod copy; pub mod daemon; mod html; -mod queue; pub(crate) mod queue_builder; pub(crate) mod rustc_version; diff --git a/src/web/builds.rs b/src/web/builds.rs index 40d9ad279..e0c7eec33 100644 --- a/src/web/builds.rs +++ b/src/web/builds.rs @@ -8,9 +8,7 @@ use crate::{ cache::CachePolicy, error::{AxumNope, AxumResult, JsonAxumNope, JsonAxumResult}, extractors::{DbConnection, Path, rustdoc::RustdocParams}, - filters, - headers::CanonicalUrl, - match_version, + filters, match_version, page::templates::{RenderBrands, RenderRegular, RenderSolid}, }, }; @@ -25,6 +23,7 @@ use chrono::{DateTime, Utc}; use constant_time_eq::constant_time_eq; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; use docs_rs_database::types::{BuildId, version::Version}; +use docs_rs_headers::CanonicalUrl; use http::StatusCode; use std::sync::Arc; diff --git a/src/web/cache.rs b/src/web/cache.rs index 6900419b5..af587bbff 100644 --- a/src/web/cache.rs +++ b/src/web/cache.rs @@ -1,11 +1,4 @@ -use crate::{ - config::Config, - db::types::krate_name::KrateName, - web::{ - extractors::Path, - headers::{SURROGATE_CONTROL, SURROGATE_KEY, SurrogateKeys}, - }, -}; +use crate::{config::Config, web::extractors::Path}; use axum::{ Extension, extract::{MatchedPath, Request as AxumHttpRequest}, @@ -13,6 +6,8 @@ use axum::{ response::Response as AxumResponse, }; use axum_extra::headers::HeaderMapExt as _; +use docs_rs_database::types::krate_name::KrateName; +use docs_rs_headers::{SURROGATE_CONTROL, SURROGATE_KEY, SurrogateKeys}; use http::{ HeaderMap, HeaderValue, StatusCode, header::{CACHE_CONTROL, ETAG}, diff --git a/src/web/crate_details.rs b/src/web/crate_details.rs index 85408d3dc..8bcb055d6 100644 --- a/src/web/crate_details.rs +++ b/src/web/crate_details.rs @@ -1,5 +1,5 @@ use crate::{ - db::types::{BuildStatus, dependencies::ReleaseDependencyList, krate_name::KrateName}, + db::types::{BuildStatus, dependencies::ReleaseDependencyList}, impl_axum_webpage, registry_api::OwnerKind, utils::get_correct_docsrs_style_file, @@ -11,7 +11,6 @@ use crate::{ DbConnection, rustdoc::{PageKind, RustdocParams}, }, - headers::CanonicalUrl, match_version, page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, }, @@ -24,7 +23,9 @@ use axum::{ }; use chrono::{DateTime, Utc}; use docs_rs_cargo_metadata::Dependency; +use docs_rs_database::types::krate_name::KrateName; use docs_rs_database::types::{BuildId, CrateId, ReleaseId, version::Version}; +use docs_rs_headers::CanonicalUrl; use docs_rs_storage::{AsyncStorage, errors::PathNotFoundError}; use futures_util::stream::TryStreamExt; use log::warn; diff --git a/src/web/error.rs b/src/web/error.rs index 6946409d9..ee74f5825 100644 --- a/src/web/error.rs +++ b/src/web/error.rs @@ -1,4 +1,4 @@ -use crate::web::{AxumErrorPage, cache::CachePolicy, escaped_uri::EscapedURI, releases::Search}; +use crate::web::{AxumErrorPage, cache::CachePolicy, releases::Search}; use anyhow::{Result, anyhow}; use axum::{ Json, @@ -7,6 +7,7 @@ use axum::{ }; use docs_rs_database::PoolError; use docs_rs_storage::errors::PathNotFoundError; +use docs_rs_web_utils::escaped_uri::EscapedURI; use std::borrow::Cow; use tracing::error; diff --git a/src/web/extractors/rustdoc.rs b/src/web/extractors/rustdoc.rs index e839d8b2f..72f43f3d4 100644 --- a/src/web/extractors/rustdoc.rs +++ b/src/web/extractors/rustdoc.rs @@ -1,12 +1,6 @@ //! special rustdoc extractors -use crate::{ - db::types::krate_name::KrateName, - web::{ - MatchedRelease, MetaData, ReqVersion, error::AxumNope, escaped_uri::EscapedURI, - extractors::Path, url_decode, - }, -}; +use crate::web::{MatchedRelease, MetaData, ReqVersion, error::AxumNope, extractors::Path}; use anyhow::Result; use axum::{ RequestPartsExt, @@ -14,7 +8,9 @@ use axum::{ http::{Uri, request::Parts}, }; use docs_rs_database::types::BuildId; +use docs_rs_database::types::krate_name::KrateName; use docs_rs_storage::CompressionAlgorithm; +use docs_rs_web_utils::{escaped_uri::EscapedURI, url_decode}; use itertools::Itertools as _; use serde::Deserialize; diff --git a/src/web/features.rs b/src/web/features.rs index 633d5d5b8..0dd827b43 100644 --- a/src/web/features.rs +++ b/src/web/features.rs @@ -9,15 +9,14 @@ use crate::{ DbConnection, rustdoc::{PageKind, RustdocParams}, }, - filters, - headers::CanonicalUrl, - match_version, + filters, match_version, page::templates::{RenderBrands, RenderRegular, RenderSolid}, }, }; use anyhow::anyhow; use askama::Template; use axum::response::IntoResponse; +use docs_rs_headers::CanonicalUrl; use serde_json::Value; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; diff --git a/src/web/file.rs b/src/web/file.rs index 0f15da1a8..c20762afd 100644 --- a/src/web/file.rs +++ b/src/web/file.rs @@ -1,6 +1,6 @@ //! Database based file handler -use super::{cache::CachePolicy, headers::IfNoneMatch}; +use super::cache::CachePolicy; use crate::{Config, error::Result}; use axum::{ body::Body, @@ -12,6 +12,7 @@ use axum_extra::{ TypedHeader, headers::{ContentType, LastModified}, }; +use docs_rs_headers::IfNoneMatch; use docs_rs_storage::{AsyncStorage, Blob, StreamingBlob}; use std::time::SystemTime; use tokio_util::io::ReaderStream; diff --git a/src/web/mod.rs b/src/web/mod.rs index 12fd1f5b8..532b8e12f 100644 --- a/src/web/mod.rs +++ b/src/web/mod.rs @@ -3,7 +3,7 @@ pub mod page; use crate::{ - db::types::{BuildStatus, krate_name::KrateName}, + db::types::BuildStatus, utils::get_correct_docsrs_style_file, web::{ metrics::WebMetrics, @@ -13,7 +13,7 @@ use crate::{ use anyhow::{Context as _, Result, anyhow, bail}; use askama::Template; use axum_extra::middleware::option_layer; -use docs_rs_database::types::{CrateId, version::Version}; +use docs_rs_database::types::{CrateId, krate_name::KrateName, version::Version}; use serde::Serialize; use serde_json::Value; use tracing::{info, instrument}; diff --git a/src/web/page/web_page.rs b/src/web/page/web_page.rs index 07da81108..71c6bbbeb 100644 --- a/src/web/page/web_page.rs +++ b/src/web/page/web_page.rs @@ -69,7 +69,7 @@ macro_rules! impl_axum_webpage { $( let canonical_url = { - let canonical_url: fn(&Self) -> Option<$crate::web::headers::CanonicalUrl> = $canonical_url; + let canonical_url: fn(&Self) -> Option = $canonical_url; (canonical_url)(&self) }; if let Some(canonical_url) = canonical_url { diff --git a/src/web/releases.rs b/src/web/releases.rs index bc5d434ab..96a1c678c 100644 --- a/src/web/releases.rs +++ b/src/web/releases.rs @@ -5,7 +5,7 @@ use crate::{ Config, RegistryApi, impl_axum_webpage, utils::report_error, web::{ - ReqVersion, axum_redirect, encode_url_path, + ReqVersion, axum_redirect, error::{AxumNope, AxumResult}, extractors::{DbConnection, Path, rustdoc::RustdocParams}, match_version, @@ -24,6 +24,7 @@ use base64::{Engine, engine::general_purpose::STANDARD as b64}; use chrono::{DateTime, Utc}; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS, QueuedCrate}; use docs_rs_database::types::version::Version; +use docs_rs_web_utils::encode_url_path; use futures_util::stream::TryStreamExt; use itertools::Itertools; use serde::{Deserialize, Serialize}; diff --git a/src/web/rustdoc.rs b/src/web/rustdoc.rs index c1f694ca2..4b5529a21 100644 --- a/src/web/rustdoc.rs +++ b/src/web/rustdoc.rs @@ -10,13 +10,11 @@ use crate::{ crate_details::CrateDetails, csp::Csp, error::{AxumNope, AxumResult}, - escaped_uri::EscapedURI, extractors::{ DbConnection, Path, WantedCompression, rustdoc::{PageKind, RustdocParams}, }, file::StreamingFile, - headers::{IfNoneMatch, X_ROBOTS_TAG}, licenses, match_version, metrics::WebMetrics, page::{ @@ -41,11 +39,13 @@ use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS, QueuedCrate}; use docs_rs_cargo_metadata::Dependency; use docs_rs_database::types::version::Version; use docs_rs_headers::etag::ETagComputer; +use docs_rs_headers::{IfNoneMatch, X_ROBOTS_TAG}; use docs_rs_storage::{ AsyncStorage, CompressionAlgorithm, RustdocJsonFormatVersion, StreamingBlob, errors::PathNotFoundError, rustdoc_archive_path, rustdoc_json_path, }; use docs_rs_utils::BUILD_VERSION; +use docs_rs_web_utils::escaped_uri::EscapedURI; use http::{HeaderMap, HeaderValue, Uri, header::CONTENT_DISPOSITION, uri::Authority}; use serde::Deserialize; use std::{ diff --git a/src/web/source.rs b/src/web/source.rs index ff69859e8..4497520fe 100644 --- a/src/web/source.rs +++ b/src/web/source.rs @@ -9,8 +9,6 @@ use crate::{ rustdoc::{PageKind, RustdocParams}, }, file::StreamingFile, - headers::CanonicalUrl, - headers::IfNoneMatch, match_version, page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, }, @@ -20,6 +18,7 @@ use askama::Template; use axum::{Extension, response::IntoResponse}; use axum_extra::{TypedHeader, headers::HeaderMapExt}; use docs_rs_database::types::{BuildId, version::Version}; +use docs_rs_headers::{CanonicalUrl, IfNoneMatch}; use docs_rs_storage::{ AsyncStorage, errors::{PathNotFoundError, SizeLimitReached}, diff --git a/src/web/statics.rs b/src/web/statics.rs index 1719c9ee5..6faab497e 100644 --- a/src/web/statics.rs +++ b/src/web/statics.rs @@ -1,6 +1,4 @@ -use super::{ - cache::CachePolicy, headers::IfNoneMatch, metrics::request_recorder, routes::get_static, -}; +use super::{cache::CachePolicy, metrics::request_recorder, routes::get_static}; use axum::{ Router as AxumRouter, extract::{Extension, Request}, @@ -13,6 +11,7 @@ use axum_extra::{ typed_header::TypedHeader, }; use docs_rs_database::mimes::APPLICATION_OPENSEARCH_XML; +use docs_rs_headers::IfNoneMatch; use http::{StatusCode, Uri}; use tower_http::services::ServeDir; From a3172cfcb8a1d38b0a0734742498632e2ae3ccd0 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Wed, 10 Dec 2025 07:20:23 +0100 Subject: [PATCH 16/46] check --- crates/docs_rs_build_queue/src/lib.rs | 2 +- crates/docs_rs_build_queue/src/rebuilds.rs | 2 +- crates/docs_rs_web_utils/src/escaped_uri.rs | 2 +- src/bin/cratesfyi.rs | 230 +------------------- src/context.rs | 5 +- src/docbuilder/rustwide_builder.rs | 20 +- src/utils/daemon.rs | 55 ++++- 7 files changed, 86 insertions(+), 230 deletions(-) diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/docs_rs_build_queue/src/lib.rs index 03687047c..2ebea148e 100644 --- a/crates/docs_rs_build_queue/src/lib.rs +++ b/crates/docs_rs_build_queue/src/lib.rs @@ -1,6 +1,6 @@ mod config; mod metrics; -mod rebuilds; +pub mod rebuilds; pub use config::Config; diff --git a/crates/docs_rs_build_queue/src/rebuilds.rs b/crates/docs_rs_build_queue/src/rebuilds.rs index 0107782ba..55cfe21f2 100644 --- a/crates/docs_rs_build_queue/src/rebuilds.rs +++ b/crates/docs_rs_build_queue/src/rebuilds.rs @@ -1,4 +1,4 @@ -use crate::{AsyncBuildQueue, Config, PRIORITY_BROKEN_RUSTDOC, PRIORITY_CONTINUOUS}; +use crate::{AsyncBuildQueue, PRIORITY_BROKEN_RUSTDOC}; use anyhow::Result; use chrono::NaiveDate; use docs_rs_database::types::version::Version; diff --git a/crates/docs_rs_web_utils/src/escaped_uri.rs b/crates/docs_rs_web_utils/src/escaped_uri.rs index 873a8e680..273344397 100644 --- a/crates/docs_rs_web_utils/src/escaped_uri.rs +++ b/crates/docs_rs_web_utils/src/escaped_uri.rs @@ -200,7 +200,7 @@ impl EscapedURI { self.uri } - pub(crate) fn with_fragment(mut self, fragment: impl AsRef) -> Self { + pub fn with_fragment(mut self, fragment: impl AsRef) -> Self { self.fragment = Some(encode_url_path(fragment.as_ref())); self } diff --git a/src/bin/cratesfyi.rs b/src/bin/cratesfyi.rs index de8725694..ff99d2e6e 100644 --- a/src/bin/cratesfyi.rs +++ b/src/bin/cratesfyi.rs @@ -2,16 +2,16 @@ use anyhow::{Context as _, Result, anyhow}; use chrono::NaiveDate; use clap::{Parser, Subcommand, ValueEnum}; use docs_rs::{ - Config, Context, Index, PackageKind, RustwideBuilder, - db::{self, CrateId, Overrides, add_path_into_database, types::version::Version}, - queue_rebuilds_faulty_rustdoc, start_web_server, - utils::{ - ConfigName, daemon::start_background_service_metric_collector, get_config, - get_crate_pattern_and_priority, list_crate_priorities, queue_builder, - remove_crate_priority, set_crate_priority, - }, + Config, Context, PackageKind, RustwideBuilder, + db::{self, Overrides}, + start_web_server, + utils::queue_builder, +}; +use docs_rs_build_queue::rebuilds::queue_rebuilds_faulty_rustdoc; +use docs_rs_database::{ + service_config::{ConfigName, set_config}, + types::{CrateId, version::Version}, }; -use docs_rs_database::service_config::set_config; use futures_util::StreamExt; use std::{env, fmt::Write, net::SocketAddr, path::PathBuf, sync::Arc}; use tokio::runtime; @@ -137,27 +137,6 @@ enum QueueSubcommand { build_priority: i32, }, - /// Interactions with build queue priorities - DefaultPriority { - #[command(subcommand)] - subcommand: PrioritySubcommand, - }, - - /// Get the registry watcher's last seen reference - GetLastSeenReference, - - /// Set the registry watcher's last seen reference - #[command(arg_required_else_help(true))] - SetLastSeenReference { - /// The reference to set to, required unless flag used - #[arg(conflicts_with("head"))] - reference: Option, - - /// Fetch the current HEAD of the remote index and use it - #[arg(long, conflicts_with("reference"))] - head: bool, - }, - /// Queue rebuilds for broken nightly versions of rustdoc, either for a single date (start) or a range (start inclusive, end exclusive) RebuildBrokenNightly { /// Start date of nightly builds to rebuild (inclusive) @@ -181,35 +160,9 @@ impl QueueSubcommand { &crate_name, &crate_version, build_priority, - ctx.config.registry_url.as_deref(), + ctx.config.watcher.registry_url.as_deref(), )?, - Self::GetLastSeenReference => { - if let Some(reference) = ctx.build_queue.last_seen_reference()? { - println!("Last seen reference: {reference}"); - } else { - println!("No last seen reference available"); - } - } - - Self::SetLastSeenReference { reference, head } => { - let reference = match (reference, head) { - (Some(reference), false) => reference, - (None, true) => { - println!("Fetching changes to set reference to HEAD"); - ctx.runtime.block_on(async move { - let index = Index::from_config(&ctx.config).await?; - index.latest_commit_reference().await - })? - } - (_, _) => unreachable!(), - }; - - ctx.build_queue.set_last_seen_reference(reference)?; - println!("Set last seen reference: {reference}"); - } - - Self::DefaultPriority { subcommand } => subcommand.handle_args(ctx)?, Self::RebuildBrokenNightly { start_nightly_date, end_nightly_date } => { ctx.runtime.block_on(async move { @@ -252,50 +205,6 @@ enum PrioritySubcommand { }, } -impl PrioritySubcommand { - fn handle_args(self, ctx: Context) -> Result<()> { - ctx.runtime.block_on(async move { - let mut conn = ctx.pool.get_async().await?; - match self { - Self::List => { - for (pattern, priority) in list_crate_priorities(&mut conn).await? { - println!("{pattern:>20} : {priority:>3}"); - } - } - - Self::Get { crate_name } => { - if let Some((pattern, priority)) = - get_crate_pattern_and_priority(&mut conn, &crate_name).await? - { - println!("{pattern} : {priority}"); - } else { - println!("No priority found for {crate_name}"); - } - } - - Self::Set { pattern, priority } => { - set_crate_priority(&mut conn, &pattern, priority) - .await - .context("Could not set pattern's priority")?; - println!("Set pattern '{pattern}' to priority {priority}"); - } - - Self::Remove { pattern } => { - if let Some(priority) = remove_crate_priority(&mut conn, &pattern) - .await - .context("Could not remove pattern's priority")? - { - println!("Removed pattern '{pattern}' with priority {priority}"); - } else { - println!("Pattern '{pattern}' did not exist and so was not removed"); - } - } - } - Ok(()) - }) - } -} - #[derive(Debug, Clone, PartialEq, Eq, Subcommand)] enum BuildSubcommand { /// Builds documentation for a crate @@ -313,16 +222,6 @@ enum BuildSubcommand { local: Option, }, - /// update the currently installed rustup toolchain - UpdateToolchain { - /// Update the toolchain only if no toolchain is currently installed - #[arg(name = "ONLY_FIRST_TIME", long = "only-first-time")] - only_first_time: bool, - }, - - /// Adds essential files for the installed version of rustc - AddEssentialFiles, - SetToolchain { toolchain_name: String, }, @@ -353,7 +252,7 @@ impl BuildSubcommand { .build_local_package(&path) .context("Building documentation failed")?; } else { - let registry_url = ctx.config.registry_url.as_ref(); + let registry_url = ctx.config.watcher.registry_url.as_ref(); builder .build_package( &crate_name @@ -369,32 +268,6 @@ impl BuildSubcommand { } } - Self::UpdateToolchain { only_first_time } => { - let rustc_version = ctx.runtime.block_on({ - let pool = ctx.pool.clone(); - async move { - let mut conn = pool - .get_async() - .await - .context("failed to get a database connection")?; - - get_config::(&mut conn, ConfigName::RustcVersion).await - } - })?; - if only_first_time && rustc_version.is_some() { - println!("update-toolchain was already called in the past, exiting"); - return Ok(()); - } - - rustwide_builder()?.update_toolchain_and_add_essential_files()?; - } - - Self::AddEssentialFiles => { - rustwide_builder()? - .add_essential_files() - .context("failed to add essential files")?; - } - Self::SetToolchain { toolchain_name } => { ctx.runtime.block_on(async move { let mut conn = ctx @@ -440,36 +313,11 @@ enum DatabaseSubcommand { name: String, }, - AddDirectory { - /// Path of file or directory - #[arg(name = "DIRECTORY")] - directory: PathBuf, - }, - - /// Remove documentation from the database - Delete { - #[command(subcommand)] - command: DeleteSubcommand, - }, - - /// Blacklist operations - Blacklist { - #[command(subcommand)] - command: BlacklistSubcommand, - }, - /// Limit overrides operations Limits { #[command(subcommand)] command: LimitsSubcommand, }, - - /// Compares the database with the index and resolves inconsistencies - Synchronize { - /// Don't actually resolve the inconsistencies, just log them - #[arg(long)] - dry_run: bool, - }, } impl DatabaseSubcommand { @@ -522,43 +370,7 @@ impl DatabaseSubcommand { db::update_crate_data_in_database(&mut conn, &name, ®istry_data).await })?, - Self::AddDirectory { directory } => { - ctx.runtime - .block_on(add_path_into_database( - &ctx.async_storage, - &ctx.config.prefix, - directory, - )) - .context("Failed to add directory into database")?; - } - - Self::Delete { - command: DeleteSubcommand::Version { name, version }, - } => ctx - .runtime - .block_on(async move { - let mut conn = ctx.pool.get_async().await?; - db::delete_version(&mut conn, &ctx.async_storage, &ctx.config, &name, &version) - .await - }) - .context("failed to delete the version")?, - Self::Delete { - command: DeleteSubcommand::Crate { name }, - } => ctx - .runtime - .block_on(async move { - let mut conn = ctx.pool.get_async().await?; - db::delete_crate(&mut conn, &ctx.async_storage, &ctx.config, &name).await - }) - .context("failed to delete the crate")?, - Self::Blacklist { command } => command.handle_args(ctx)?, - Self::Limits { command } => command.handle_args(ctx)?, - - Self::Synchronize { dry_run } => { - ctx.runtime - .block_on(docs_rs::utils::consistency::run_check(&ctx, dry_run))?; - } } Ok(()) } @@ -679,23 +491,3 @@ impl BlacklistSubcommand { }) } } - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum DeleteSubcommand { - /// Delete a whole crate - Crate { - /// Name of the crate to delete - #[arg(name = "CRATE_NAME")] - name: String, - }, - /// Delete a single version of a crate (which may include multiple builds) - Version { - /// Name of the crate to delete - #[arg(name = "CRATE_NAME")] - name: String, - - /// The version of the crate to delete - #[arg(name = "VERSION")] - version: Version, - }, -} diff --git a/src/context.rs b/src/context.rs index 57fdc37c6..f909b6be1 100644 --- a/src/context.rs +++ b/src/context.rs @@ -14,7 +14,6 @@ pub struct Context { pub build_queue: Arc, pub storage: Arc, pub async_storage: Arc, - // pub cdn_metrics: Arc, pub pool: Pool, pub registry_api: Arc, pub repository_stats_updater: Arc, @@ -54,10 +53,9 @@ impl Context { AsyncStorage::new(pool.clone(), config.storage.clone(), &meter_provider).await?, ); - let cdn_metrics = Arc::new(CdnMetrics::new(&meter_provider)); let async_build_queue = Arc::new(AsyncBuildQueue::new( pool.clone(), - config.build_queue.clone(), + &config.build_queue, &meter_provider, )); @@ -72,7 +70,6 @@ impl Context { build_queue, storage, async_storage, - cdn_metrics, pool: pool.clone(), registry_api: Arc::new(RegistryApi::new( config.watcher.registry_api_host.clone(), diff --git a/src/docbuilder/rustwide_builder.rs b/src/docbuilder/rustwide_builder.rs index 4312c5f16..2bff7dce0 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/src/docbuilder/rustwide_builder.rs @@ -56,6 +56,22 @@ const DUMMY_CRATE_VERSION: Version = Version::new(1, 0, 0); pub const RUSTDOC_JSON_COMPRESSION_ALGORITHMS: &[CompressionAlgorithm] = &[CompressionAlgorithm::Zstd, CompressionAlgorithm::Gzip]; +pub fn load_metadata_from_rustwide( + workspace: &Workspace, + toolchain: &Toolchain, + source_dir: &Path, +) -> Result { + let res = Command::new(workspace, toolchain.cargo()) + .args(&["metadata", "--format-version", "1"]) + .cd(source_dir) + .log_output(false) + .run_capture()?; + let [metadata] = res.stdout_lines() else { + bail!("invalid output returned by `cargo metadata`") + }; + CargoMetadata::load_from_metadata(metadata) +} + /// read the format version from a rustdoc JSON file. pub fn read_format_version_from_rustdoc_json( reader: impl std::io::Read, @@ -530,7 +546,7 @@ impl RustwideBuilder { } pub fn build_local_package(&mut self, path: &Path) -> Result { - let metadata = CargoMetadata::load_from_rustwide(&self.workspace, &self.toolchain, path) + let metadata = load_metadata_from_rustwide(&self.workspace, &self.toolchain, path) .map_err(|err| { err.context(format!("failed to load local package {}", path.display())) })?; @@ -1147,7 +1163,7 @@ impl RustwideBuilder { create_essential_files: bool, collect_metrics: bool, ) -> Result { - let cargo_metadata = CargoMetadata::load_from_rustwide( + let cargo_metadata = load_metadata_from_rustwide( &self.workspace, &self.toolchain, &build.host_source_dir(), diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs index 48cad6466..f17db1a07 100644 --- a/src/utils/daemon.rs +++ b/src/utils/daemon.rs @@ -4,8 +4,8 @@ use crate::{Context, RustwideBuilder, utils::queue_builder, web::start_web_server}; use anyhow::{Error, anyhow}; -use docs_rs_utils::start_async_cron_in_runtime; -use docs_rs_watcher::service_metrics::OtelServiceMetrics; +use docs_rs_utils::{start_async_cron, start_async_cron_in_runtime}; +use docs_rs_watcher::{rebuilds::queue_rebuilds, service_metrics::OtelServiceMetrics}; use std::sync::Arc; use std::thread; use std::time::Duration; @@ -65,6 +65,57 @@ pub fn start_daemon(context: Context, enable_registry_watcher: bool) -> Result<( .map_err(|err| anyhow!("web server panicked: {:?}", err))? } +pub fn start_background_queue_rebuild(context: &Context) -> Result<(), Error> { + let runtime = context.runtime.clone(); + let pool = context.pool.clone(); + let config = context.config.clone(); + let build_queue = context.async_build_queue.clone(); + + if config.watcher.max_queued_rebuilds.is_none() { + info!("rebuild config incomplete, skipping rebuild queueing"); + return Ok(()); + } + + start_async_cron_in_runtime( + &runtime, + "background queue rebuilder", + Duration::from_secs(60 * 60), + move || { + let pool = pool.clone(); + let build_queue = build_queue.clone(); + let config = config.clone(); + async move { + let mut conn = pool.get_async().await?; + queue_rebuilds(&mut conn, &config.watcher, &build_queue).await?; + Ok(()) + } + }, + ); + Ok(()) +} + +pub fn start_background_repository_stats_updater(context: &Context) -> Result<(), Error> { + // This call will still skip github repositories updates and continue if no token is provided + // (gitlab doesn't require to have a token). The only time this can return an error is when + // creating a pool or if config fails, which shouldn't happen here because this is run right at + // startup. + let updater = context.repository_stats_updater.clone(); + let runtime = context.runtime.clone(); + start_async_cron_in_runtime( + &runtime, + "repository stats updater", + Duration::from_secs(60 * 60), + move || { + let updater = updater.clone(); + async move { + updater.update_all_crates().await?; + Ok(()) + } + }, + ); + Ok(()) +} + pub fn start_background_service_metric_collector(context: &Context) -> Result<(), Error> { let runtime = context.runtime.clone(); let build_queue = context.async_build_queue.clone(); From a1f7a085521693428f9cff801c272c43029d56da Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 01:03:05 +0100 Subject: [PATCH 17/46] registry api --- Cargo.lock | 14 + Cargo.toml | 5 - crates/docs_rs_registry_api/Cargo.toml | 14 + .../docs_rs_registry_api/src/lib.rs | 0 src/bin/cratesfyi.rs | 493 ------------------ src/error.rs | 3 - src/lib.rs | 3 - 7 files changed, 28 insertions(+), 504 deletions(-) create mode 100644 crates/docs_rs_registry_api/Cargo.toml rename src/registry_api.rs => crates/docs_rs_registry_api/src/lib.rs (100%) delete mode 100644 src/bin/cratesfyi.rs delete mode 100644 src/error.rs diff --git a/Cargo.lock b/Cargo.lock index c67706181..400e05446 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2091,6 +2091,20 @@ dependencies = [ "url", ] +[[package]] +name = "docs_rs_registry_api" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "docs_rs_database", + "docs_rs_utils", + "reqwest", + "serde", + "tracing", + "url", +] + [[package]] name = "docs_rs_storage" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 86296727d..c916462b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,8 +148,3 @@ ignored = ["phf"] [[bench]] name = "compression" harness = false - -[[bin]] -name = "cratesfyi" -test = false -doc = false diff --git a/crates/docs_rs_registry_api/Cargo.toml b/crates/docs_rs_registry_api/Cargo.toml new file mode 100644 index 000000000..60853f72e --- /dev/null +++ b/crates/docs_rs_registry_api/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "docs_rs_registry_api" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +chrono = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } +docs_rs_utils = { path = "../docs_rs_utils" } +reqwest = { workspace = true } +serde = { workspace = true } +tracing = { workspace = true } +url = { workspace = true } diff --git a/src/registry_api.rs b/crates/docs_rs_registry_api/src/lib.rs similarity index 100% rename from src/registry_api.rs rename to crates/docs_rs_registry_api/src/lib.rs diff --git a/src/bin/cratesfyi.rs b/src/bin/cratesfyi.rs deleted file mode 100644 index ff99d2e6e..000000000 --- a/src/bin/cratesfyi.rs +++ /dev/null @@ -1,493 +0,0 @@ -use anyhow::{Context as _, Result, anyhow}; -use chrono::NaiveDate; -use clap::{Parser, Subcommand, ValueEnum}; -use docs_rs::{ - Config, Context, PackageKind, RustwideBuilder, - db::{self, Overrides}, - start_web_server, - utils::queue_builder, -}; -use docs_rs_build_queue::rebuilds::queue_rebuilds_faulty_rustdoc; -use docs_rs_database::{ - service_config::{ConfigName, set_config}, - types::{CrateId, version::Version}, -}; -use futures_util::StreamExt; -use std::{env, fmt::Write, net::SocketAddr, path::PathBuf, sync::Arc}; -use tokio::runtime; -use tracing_log::LogTracer; - -fn main() { - // set the global log::logger for backwards compatibility - // through rustwide. - let _logging_guard = rustwide::logging::init_with(LogTracer::new()); - docs_rs_logging::init(); - - if let Err(err) = CommandLine::parse().handle_args() { - let mut msg = format!("Error: {err}"); - for cause in err.chain() { - write!(msg, "\n\nCaused by:\n {cause}").unwrap(); - } - eprintln!("{msg}"); - - let backtrace = err.backtrace().to_string(); - if !backtrace.is_empty() { - eprintln!("\nStack backtrace:\n{backtrace}"); - } - - // we need to drop the sentry guard here so all unsent - // errors are sent to sentry before - // process::exit kills everything. - drop(_logging_guard); - std::process::exit(1); - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)] -#[value(rename_all = "snake_case")] -enum Toggle { - Enabled, - Disabled, -} - -#[derive(Debug, Clone, PartialEq, Eq, Parser)] -#[command( - about = env!("CARGO_PKG_DESCRIPTION"), - version = docs_rs_utils::BUILD_VERSION, - rename_all = "kebab-case", -)] -enum CommandLine { - Build { - #[command(subcommand)] - subcommand: BuildSubcommand, - }, - - /// Starts web server - StartWebServer { - #[arg(name = "SOCKET_ADDR", default_value = "0.0.0.0:3000")] - socket_addr: SocketAddr, - }, - - StartBuildServer, - - /// Starts the daemon - Daemon { - /// Enable or disable the registry watcher to automatically enqueue newly published crates - #[arg(long = "registry-watcher", default_value = "enabled", value_enum)] - registry_watcher: Toggle, - }, - - /// Database operations - Database { - #[command(subcommand)] - subcommand: DatabaseSubcommand, - }, - - /// Interactions with the build queue - Queue { - #[command(subcommand)] - subcommand: QueueSubcommand, - }, -} - -impl CommandLine { - fn handle_args(self) -> Result<()> { - let config = Config::from_env()?; - let runtime = Arc::new(runtime::Builder::new_multi_thread().enable_all().build()?); - let ctx = runtime.block_on(Context::from_config(config))?; - - match self { - Self::Build { subcommand } => subcommand.handle_args(ctx)?, - Self::StartBuildServer => { - queue_builder(&ctx, RustwideBuilder::init(&ctx)?)?; - } - Self::StartWebServer { socket_addr } => { - // Blocks indefinitely - start_web_server(Some(socket_addr), &ctx)?; - } - Self::Daemon { registry_watcher } => { - docs_rs::utils::start_daemon(ctx, registry_watcher == Toggle::Enabled)?; - } - Self::Database { subcommand } => subcommand.handle_args(ctx)?, - Self::Queue { subcommand } => subcommand.handle_args(ctx)?, - } - - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum QueueSubcommand { - /// Add a crate to the build queue - Add { - /// Name of crate to build - #[arg(name = "CRATE_NAME")] - crate_name: String, - /// Version of crate to build - #[arg(name = "CRATE_VERSION")] - crate_version: Version, - /// Priority of build (new crate builds get priority 0) - #[arg( - name = "BUILD_PRIORITY", - short = 'p', - long = "priority", - default_value = "5", - allow_negative_numbers = true - )] - build_priority: i32, - }, - - /// Queue rebuilds for broken nightly versions of rustdoc, either for a single date (start) or a range (start inclusive, end exclusive) - RebuildBrokenNightly { - /// Start date of nightly builds to rebuild (inclusive) - #[arg(name = "START", short = 's', long = "start")] - start_nightly_date: NaiveDate, - - /// End date of nightly builds to rebuild (exclusive, optional) - #[arg(name = "END", short = 'e', long = "end")] - end_nightly_date: Option, - }, -} - -impl QueueSubcommand { - fn handle_args(self, ctx: Context) -> Result<()> { - match self { - Self::Add { - crate_name, - crate_version, - build_priority, - } => ctx.build_queue.add_crate( - &crate_name, - &crate_version, - build_priority, - ctx.config.watcher.registry_url.as_deref(), - )?, - - - Self::RebuildBrokenNightly { start_nightly_date, end_nightly_date } => { - ctx.runtime.block_on(async move { - let mut conn = ctx.pool.get_async().await?; - let queued_rebuilds_amount = queue_rebuilds_faulty_rustdoc(&mut conn, &ctx.async_build_queue, &start_nightly_date, &end_nightly_date).await?; - println!("Queued {queued_rebuilds_amount} rebuilds for broken nightly versions of rustdoc"); - Ok::<(), anyhow::Error>(()) - })? - } - } - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum PrioritySubcommand { - /// Get priority for a crate - /// - /// (returns only the first matching pattern, there may be other matching patterns) - Get { crate_name: String }, - - /// List priorities for all patterns - List, - - /// Set all crates matching a pattern to a priority level - Set { - /// See https://www.postgresql.org/docs/current/functions-matching.html for pattern syntax - #[arg(name = "PATTERN")] - pattern: String, - /// The priority to give crates matching the given `PATTERN` - #[arg(allow_negative_numbers = true)] - priority: i32, - }, - - /// Remove the prioritization of crates for a pattern - Remove { - /// See https://www.postgresql.org/docs/current/functions-matching.html for pattern syntax - #[arg(name = "PATTERN")] - pattern: String, - }, -} - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum BuildSubcommand { - /// Builds documentation for a crate - Crate { - /// Crate name - #[arg(name = "CRATE_NAME", requires("CRATE_VERSION"))] - crate_name: Option, - - /// Version of crate - #[arg(name = "CRATE_VERSION")] - crate_version: Option, - - /// Build a crate at a specific path - #[arg(short = 'l', long = "local", conflicts_with_all(&["CRATE_NAME", "CRATE_VERSION"]))] - local: Option, - }, - - SetToolchain { - toolchain_name: String, - }, - - /// Locks the daemon, preventing it from building new crates - Lock, - - /// Unlocks the daemon to continue building new crates - Unlock, -} - -impl BuildSubcommand { - fn handle_args(self, ctx: Context) -> Result<()> { - let rustwide_builder = || -> Result { RustwideBuilder::init(&ctx) }; - - match self { - Self::Crate { - crate_name, - crate_version, - local, - } => { - let mut builder = rustwide_builder()?; - - builder.update_toolchain_and_add_essential_files()?; - - if let Some(path) = local { - builder - .build_local_package(&path) - .context("Building documentation failed")?; - } else { - let registry_url = ctx.config.watcher.registry_url.as_ref(); - builder - .build_package( - &crate_name - .with_context(|| anyhow!("must specify name if not local"))?, - &crate_version - .with_context(|| anyhow!("must specify version if not local"))?, - registry_url - .map(|s| PackageKind::Registry(s.as_str())) - .unwrap_or(PackageKind::CratesIo), - true, - ) - .context("Building documentation failed")?; - } - } - - Self::SetToolchain { toolchain_name } => { - ctx.runtime.block_on(async move { - let mut conn = ctx - .pool - .get_async() - .await - .context("failed to get a database connection")?; - set_config(&mut conn, ConfigName::Toolchain, toolchain_name) - .await - .context("failed to set toolchain in database") - })?; - } - - Self::Lock => ctx.build_queue.lock().context("Failed to lock")?, - Self::Unlock => ctx.build_queue.unlock().context("Failed to unlock")?, - } - - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum DatabaseSubcommand { - /// Run database migration - Migrate { - /// The database version to migrate to - #[arg(name = "VERSION")] - version: Option, - }, - - /// temporary command to update the `crates.latest_version_id` field - UpdateLatestVersionId, - - /// Updates GitHub/GitLab stats for crates. - UpdateRepositoryFields, - - /// Backfill GitHub/GitLab stats for crates. - BackfillRepositoryStats, - - /// Updates info for a crate from the registry's API - UpdateCrateRegistryFields { - #[arg(name = "CRATE")] - name: String, - }, - - /// Limit overrides operations - Limits { - #[command(subcommand)] - command: LimitsSubcommand, - }, -} - -impl DatabaseSubcommand { - fn handle_args(self, ctx: Context) -> Result<()> { - match self { - Self::Migrate { version } => ctx - .runtime - .block_on(async { - let mut conn = ctx.pool.get_async().await?; - db::migrate(&mut conn, version).await - }) - .context("Failed to run database migrations")?, - - Self::UpdateLatestVersionId => ctx - .runtime - .block_on(async { - let mut list_conn = ctx.pool.get_async().await?; - let mut update_conn = ctx.pool.get_async().await?; - - let mut result_stream = sqlx::query!( - r#"SELECT id as "id: CrateId", name FROM crates ORDER BY name"# - ) - .fetch(&mut *list_conn); - - while let Some(row) = result_stream.next().await { - let row = row?; - - println!("handling crate {}", row.name); - - db::update_latest_version_id(&mut update_conn, row.id).await?; - } - - Ok::<(), anyhow::Error>(()) - }) - .context("Failed to update latest version id")?, - - Self::UpdateRepositoryFields => { - ctx.runtime - .block_on(ctx.repository_stats_updater.update_all_crates())?; - } - - Self::BackfillRepositoryStats => { - ctx.runtime - .block_on(ctx.repository_stats_updater.backfill_repositories())?; - } - - Self::UpdateCrateRegistryFields { name } => ctx.runtime.block_on(async move { - let mut conn = ctx.pool.get_async().await?; - let registry_data = ctx.registry_api.get_crate_data(&name).await?; - db::update_crate_data_in_database(&mut conn, &name, ®istry_data).await - })?, - - Self::Limits { command } => command.handle_args(ctx)?, - } - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum LimitsSubcommand { - /// Get sandbox limit overrides for a crate - Get { crate_name: String }, - - /// List sandbox limit overrides for all crates - List, - - /// Set sandbox limits overrides for a crate - Set { - crate_name: String, - #[arg(long)] - memory: Option, - #[arg(long)] - targets: Option, - #[arg(long)] - timeout: Option, - }, - - /// Remove sandbox limits overrides for a crate - Remove { crate_name: String }, -} - -impl LimitsSubcommand { - fn handle_args(self, ctx: Context) -> Result<()> { - ctx.runtime.block_on(async move { - let mut conn = ctx.pool.get_async().await?; - - match self { - Self::Get { crate_name } => { - let overrides = Overrides::for_crate(&mut conn, &crate_name).await?; - println!("sandbox limit overrides for {crate_name} = {overrides:?}"); - } - - Self::List => { - for (crate_name, overrides) in Overrides::all(&mut conn).await? { - println!("sandbox limit overrides for {crate_name} = {overrides:?}"); - } - } - - Self::Set { - crate_name, - memory, - targets, - timeout, - } => { - let overrides = Overrides::for_crate(&mut conn, &crate_name).await?; - println!("previous sandbox limit overrides for {crate_name} = {overrides:?}"); - let overrides = Overrides { - memory, - targets, - timeout: timeout - .map(|timeout| std::time::Duration::from_secs(timeout as _)), - }; - Overrides::save(&mut conn, &crate_name, overrides).await?; - let overrides = Overrides::for_crate(&mut conn, &crate_name).await?; - println!("new sandbox limit overrides for {crate_name} = {overrides:?}"); - } - - Self::Remove { crate_name } => { - let overrides = Overrides::for_crate(&mut conn, &crate_name).await?; - println!("previous overrides for {crate_name} = {overrides:?}"); - Overrides::remove(&mut conn, &crate_name).await?; - } - } - Ok(()) - }) - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Subcommand)] -enum BlacklistSubcommand { - /// List all crates on the blacklist - List, - - /// Add a crate to the blacklist - Add { - /// Crate name - #[arg(name = "CRATE_NAME")] - crate_name: String, - }, - - /// Remove a crate from the blacklist - Remove { - /// Crate name - #[arg(name = "CRATE_NAME")] - crate_name: String, - }, -} - -impl BlacklistSubcommand { - fn handle_args(self, ctx: Context) -> Result<()> { - ctx.runtime.block_on(async move { - let conn = &mut ctx.pool.get_async().await?; - match self { - Self::List => { - let crates = db::blacklist::list_crates(conn) - .await - .context("failed to list crates on blacklist")?; - - println!("{}", crates.join("\n")); - } - - Self::Add { crate_name } => db::blacklist::add_crate(conn, &crate_name) - .await - .context("failed to add crate to blacklist")?, - - Self::Remove { crate_name } => db::blacklist::remove_crate(conn, &crate_name) - .await - .context("failed to remove crate from blacklist")?, - } - Ok(()) - }) - } -} diff --git a/src/error.rs b/src/error.rs deleted file mode 100644 index a76e2cb7f..000000000 --- a/src/error.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Errors used in docs.rs - -pub(crate) use anyhow::Result; diff --git a/src/lib.rs b/src/lib.rs index 5cd338536..220e79279 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,6 @@ pub use self::config::Config; pub use self::context::Context; pub use self::docbuilder::PackageKind; pub use self::docbuilder::{BuildPackageSummary, RustwideBuilder}; -pub use self::registry_api::RegistryApi; pub use self::web::start_web_server; pub use font_awesome_as_a_crate::icons; @@ -15,9 +14,7 @@ mod config; mod context; pub mod db; mod docbuilder; -mod error; pub mod metrics; -mod registry_api; #[cfg(test)] mod test; pub mod utils; From 10d99e4f6ac5e364364a12d7c966f6bb16251a0e Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 01:23:57 +0100 Subject: [PATCH 18/46] IPW --- Cargo.lock | 37 +- Cargo.toml | 22 - assets/syntaxes/Packages/CSS/.python-version | 1 - .../syntaxes/Packages/CSS/syntax_test_css.css | 1186 ----------------- assets/syntaxes/Packages/Diff/.python-version | 1 - assets/syntaxes/Packages/HTML/.python-version | 1 - .../Packages/ShellScript/.python-version | 1 - .../docs_rs_cargo_metadata/src/db.rs | 0 crates/docs_rs_cargo_metadata/src/lib.rs | 2 + crates/docs_rs_database/Cargo.toml | 3 +- crates/docs_rs_database/build.rs | 4 + .../20231021111635_initial.down.sql | 0 .../migrations}/20231021111635_initial.up.sql | 0 ...104457_drop_releases_build_status.down.sql | 0 ...21104457_drop_releases_build_status.up.sql | 0 ...13734_drop_releases_rustc_version.down.sql | 0 ...1113734_drop_releases_rustc_version.up.sql | 0 ...nsure_no_buildless_releases_exist.down.sql | 0 ..._ensure_no_buildless_releases_exist.up.sql | 0 ...21124844_multi_stage_build_status.down.sql | 0 ...0221124844_multi_stage_build_status.up.sql | 0 .../20240227040753_add_owner_kind.down.sql | 0 .../20240227040753_add_owner_kind.up.sql | 0 ...309082057_release_status_view.sql.down.sql | 0 ...40309082057_release_status_view.sql.up.sql | 0 ...02914_release_status_materialized.down.sql | 0 ...1202914_release_status_materialized.up.sql | 0 ...3708_make_release_fields_optional.down.sql | 0 ...103708_make_release_fields_optional.up.sql | 0 ...182623_make_build_fields_optional.down.sql | 0 ...13182623_make_build_fields_optional.up.sql | 0 .../20240313184911_build_errors.down.sql | 0 .../20240313184911_build_errors.up.sql | 0 ...5_crate-version-name-field-length.down.sql | 0 ...105_crate-version-name-field-length.up.sql | 0 .../20240624085737_build-status-idx.down.sql | 0 .../20240624085737_build-status-idx.up.sql | 0 ...e-crate-version-name-field-length.down.sql | 0 ...eue-crate-version-name-field-length.up.sql | 0 ...20241018031600_documentation_size.down.sql | 0 .../20241018031600_documentation_size.up.sql | 0 ...8052241_builds-rustc-nightly-date.down.sql | 0 ...018052241_builds-rustc-nightly-date.up.sql | 0 ...021050229_builds-started-finished.down.sql | 0 ...41021050229_builds-started-finished.up.sql | 0 ...85600_releases-rustdoc-status-idx.down.sql | 0 ...6085600_releases-rustdoc-status-idx.up.sql | 0 ...0241219091521_owner-avatar-longer.down.sql | 0 .../20241219091521_owner-avatar-longer.up.sql | 0 ...20251202020754_remove-file-public.down.sql | 0 .../20251202020754_remove-file-public.up.sql | 0 ...858_remove-cdn-invalidation-queue.down.sql | 0 ...40858_remove-cdn-invalidation-queue.up.sql | 0 crates/docs_rs_database/src/types/mod.rs | 62 +- crates/docs_rs_storage/Cargo.toml | 4 + .../docs_rs_storage/benches}/compression.rs | 0 .../benches}/struct.CaptureMatches.html | 0 crates/docs_rs_web/Cargo.toml | 38 + .../Extras/JavaScript (Babel).sublime-syntax | 0 .../assets}/syntaxes/Extras/TOML/.gitignore | 0 .../Extras/TOML/Comments.YAML-tmPreferences | 0 .../Extras/TOML/Comments.tmPreferences | 0 .../assets}/syntaxes/Extras/TOML/LICENSE | 0 .../assets}/syntaxes/Extras/TOML/README.md | 0 .../Extras/TOML/Symbol List.tmPreferences | 0 .../syntaxes/Extras/TOML/TOML.sublime-syntax | 0 .../Extras/TOML/syntax_test_toml.toml | 0 .../01-bug-syntax-highlighting.md | 0 .../ISSUE_TEMPLATE/02-bug-file-indexing.md | 0 .../ISSUE_TEMPLATE/03-supporting-files.md | 0 .../01-syntax-update-small.md | 0 .../02-syntax-update-significant.md | 0 .../03-supporting-files.md | 0 .../assets}/syntaxes/Packages/.travis.yml | 0 .../syntaxes/Packages/ASP/ASP.sublime-syntax | 0 .../Packages/ASP/Comments.tmPreferences | 0 .../Packages/ASP/HTML-ASP.sublime-syntax | 0 .../ASP/Indentation Rules.tmPreferences | 0 .../ASP/Indexed Symbol List.tmPreferences | 0 .../Packages/ASP/Symbol List.tmPreferences | 0 .../syntaxes/Packages/ASP/syntax_test_asp.asp | 0 .../ActionScript/ActionScript.sublime-build | 0 .../ActionScript/ActionScript.sublime-syntax | 0 .../Packages/ActionScript/syntax_test_as.as | 0 .../AppleScript/AppleScript.sublime-syntax | 0 .../AppleScript/Comments.tmPreferences | 0 .../Batch File/Batch File.sublime-settings | 0 .../Batch File/Batch File.sublime-syntax | 0 .../Batch File/Comments.tmPreferences | 0 .../Batch File/Symbol List.tmPreferences | 0 .../Batch File/syntax_test_batch_file.bat | 0 .../syntaxes/Packages/C#/Build.sublime-syntax | 0 .../syntaxes/Packages/C#/C#.sublime-syntax | 0 .../Packages/C#/Comments.tmPreferences | 0 .../Packages/C#/Indentation.tmPreferences | 0 .../C#/Symbol List Classes.tmPreferences | 0 .../C#/Symbol List Constructors.tmPreferences | 0 .../C#/Symbol List Enums.tmPreferences | 0 ...mbol List Index Constructors.tmPreferences | 0 .../Symbol List Inner Function.tmPreferences | 0 .../C#/Symbol List Interfaces.tmPreferences | 0 .../C#/Symbol List Namespace.tmPreferences | 0 .../C#/Symbol List Region.tmPreferences | 0 .../C#/Symbol List Structs.tmPreferences | 0 .../Packages/C#/doc_params.sublime-snippet | 0 .../Packages/C#/doc_see.sublime-snippet | 0 .../Packages/C#/doc_summary.sublime-snippet | 0 .../Packages/C#/tests/syntax_test_C#7.cs | 0 .../Packages/C#/tests/syntax_test_Comments.cs | 0 .../C#/tests/syntax_test_GeneralStructure.cs | 0 .../Packages/C#/tests/syntax_test_Generics.cs | 0 .../C#/tests/syntax_test_HelloWorld.cs | 0 .../C#/tests/syntax_test_Operators.cs | 0 .../syntax_test_PreprocessorDirectives.cs | 0 .../Packages/C#/tests/syntax_test_Strings.cs | 0 .../Packages/C#/tests/syntax_test_Using.cs | 0 .../Packages/C#/tests/syntax_test_c#.cs | 0 .../Packages/C#/tests/syntax_test_query.cs | 0 .../Packages/C++/C Single File.sublime-build | 0 .../C Standard Includes.sublime-completions | 0 .../C++/C++ Single File.sublime-build | 0 .../C++ Standard Includes.sublime-completions | 0 .../Packages/C++/C++.sublime-settings | 0 .../syntaxes/Packages/C++/C++.sublime-syntax | 0 .../syntaxes/Packages/C++/C.sublime-syntax | 0 .../Packages/C++/Comments (C++).tmPreferences | 0 .../C++/Completion Rules.tmPreferences | 0 .../Packages/C++/Default.sublime-keymap | 0 .../Indentation Rules Comments.tmPreferences | 0 .../C++/Indentation Rules.tmPreferences | 0 .../#ifndef-#define-#endif.sublime-snippet | 0 .../#include-(#inc angle).sublime-snippet | 0 .../Snippets/#include-(#inc).sublime-snippet | 0 .../#include-(inc angle).sublime-snippet | 0 .../Snippets/#include-(inc).sublime-snippet | 0 ...egin()-$1.end()-(beginend).sublime-snippet | 0 .../030-for-int-loop-(fori).sublime-snippet | 0 .../C++/Snippets/Enumeration.sublime-snippet | 0 .../C++/Snippets/Typedef.sublime-snippet | 0 .../Snippets/class-..-(class).sublime-snippet | 0 .../do...while-loop-(do).sublime-snippet | 0 .../C++/Snippets/forv.sublime-snippet | 0 .../C++/Snippets/fprintf.sublime-snippet | 0 .../C++/Snippets/if-..-(if).sublime-snippet | 0 .../main()-(int main).sublime-snippet | 0 .../Snippets/main()-(main).sublime-snippet | 0 .../namespace-..-(namespace).sublime-snippet | 0 .../printf-..-(printf).sublime-snippet | 0 .../read-file-(readF).sublime-snippet | 0 .../Snippets/std-map-(map).sublime-snippet | 0 .../Snippets/std-vector-(v).sublime-snippet | 0 .../C++/Snippets/struct.sublime-snippet | 0 ...ate-typename-..-(template).sublime-snippet | 0 .../C++/Symbol Index Hide Ctors.tmPreferences | 0 ...mbol Index Include Constants.tmPreferences | 0 .../Packages/C++/Symbol Index.tmPreferences | 0 ... List - Indent Class Methods.tmPreferences | 0 ...bol List - Namespace Spacing.tmPreferences | 0 ...l List - Prefix Banner Items.tmPreferences | 0 ...mbol List Hide Forward Decls.tmPreferences | 0 .../Packages/C++/Symbol List.tmPreferences | 0 .../Packages/C++/syntax_test_accessor.c | 0 .../Packages/C++/syntax_test_accessor.cpp | 0 .../syntaxes/Packages/C++/syntax_test_c.c | 0 .../syntaxes/Packages/C++/syntax_test_cpp.cpp | 0 .../syntaxes/Packages/CSS/CSS.sublime-syntax | 0 .../Packages/CSS/Comments.tmPreferences | 0 .../CSS/Completion Rules.tmPreferences | 0 .../Packages/CSS/Default.sublime-keymap | 0 .../Packages/CSS/Symbol Index.tmPreferences | 0 .../CSS/Symbol List Group.tmPreferences | 0 .../Packages/CSS/Symbol List.tmPreferences | 0 .../syntaxes/Packages/CSS/css_completions.py | 0 .../Packages/Clojure/Clojure.sublime-settings | 0 .../Packages/Clojure/Clojure.sublime-syntax | 0 .../Clojure/ClojureSymbols.tmPreferences | 0 .../Packages/Clojure/Comment.tmPreferences | 0 .../Packages/Clojure/Default.sublime-keymap | 0 .../Clojure/tests/syntax_test_clojure.clj | 0 .../Clojure/tests/syntax_test_clojure_old.clj | 0 .../Packages/D/Comments.tmPreferences | 0 .../Packages/D/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/D/D dub.sublime-build | 0 .../syntaxes/Packages/D/D.sublime-build | 0 .../syntaxes/Packages/D/D.sublime-syntax | 0 .../Packages/D/DMD Output.sublime-syntax | 0 .../D/Indentation Rules.tmPreferences | 0 .../Packages/D/Snippets/class.sublime-snippet | 0 .../D/Snippets/constant.sublime-snippet | 0 .../D/Snippets/critical.sublime-snippet | 0 .../D/Snippets/debugm.sublime-snippet | 0 .../Packages/D/Snippets/enum.sublime-snippet | 0 .../D/Snippets/error-format.sublime-snippet | 0 .../Packages/D/Snippets/error.sublime-snippet | 0 .../Packages/D/Snippets/fatal.sublime-snippet | 0 .../Snippets/foreach-reverse.sublime-snippet | 0 .../D/Snippets/foreach.sublime-snippet | 0 .../D/Snippets/if-else.sublime-snippet | 0 .../Packages/D/Snippets/if.sublime-snippet | 0 .../D/Snippets/import.sublime-snippet | 0 .../Packages/D/Snippets/info.sublime-snippet | 0 .../D/Snippets/log-format.sublime-snippet | 0 .../Packages/D/Snippets/log.sublime-snippet | 0 .../D/Snippets/main-with-args.sublime-snippet | 0 .../Packages/D/Snippets/main.sublime-snippet | 0 .../D/Snippets/method.sublime-snippet | 0 .../D/Snippets/return.sublime-snippet | 0 .../D/Snippets/struct.sublime-snippet | 0 .../Packages/D/Snippets/trace.sublime-snippet | 0 .../try-catch-finally.sublime-snippet | 0 .../D/Snippets/try-catch.sublime-snippet | 0 .../D/Snippets/try-finally.sublime-snippet | 0 .../D/Snippets/unittest.sublime-snippet | 0 .../D/Snippets/version.sublime-snippet | 0 .../D/Snippets/warning.sublime-snippet | 0 .../Packages/D/Snippets/while.sublime-snippet | 0 ...Index Hide Special Functions.tmPreferences | 0 .../Packages/D/Symbol List.tmPreferences | 0 .../syntaxes/Packages/D/tests/syntax_test_d.d | 0 .../Packages/D/tests/syntax_test_old.d | 0 .../Packages/D/tests/syntax_test_shebang.d | 0 .../Packages/Diff/Context.sublime-menu | 0 .../Packages/Diff/Diff.sublime-syntax | 0 .../Packages/Diff/Side Bar.sublime-menu | 0 .../assets}/syntaxes/Packages/Diff/diff.py | 0 .../Packages/Diff/syntax_test_diff.diff | 0 .../Packages/Erlang/Comments.tmPreferences | 0 .../Erlang/Completion Rules.tmPreferences | 0 .../Packages/Erlang/Erlang.sublime-build | 0 .../Packages/Erlang/Erlang.sublime-settings | 0 .../Packages/Erlang/Erlang.sublime-syntax | 0 .../Erlang/HTML (Erlang).sublime-completions | 0 .../Erlang/HTML (Erlang).sublime-syntax | 0 .../Erlang/Indentation Rules.tmPreferences | 0 .../Indexed Reference List.tmPreferences | 0 .../Erlang/Indexed Symbol List.tmPreferences | 0 .../Behaviour-Directive.sublime-snippet | 0 .../Snippets/Case-Expression.sublime-snippet | 0 .../Snippets/Define-Directive.sublime-snippet | 0 .../Snippets/Export-Directive.sublime-snippet | 0 .../Snippets/Fun-Expression.sublime-snippet | 0 .../Snippets/If-Expression.sublime-snippet | 0 .../Snippets/Ifdef-Directive.sublime-snippet | 0 .../Snippets/Ifndef-Directive.sublime-snippet | 0 .../Snippets/Import-Directive.sublime-snippet | 0 .../Include-Directive.sublime-snippet | 0 .../Snippets/Module-Directive.sublime-snippet | 0 .../Receive-Expression.sublime-snippet | 0 .../Snippets/Record-Directive.sublime-snippet | 0 .../Snippets/Try-Expression.sublime-snippet | 0 .../Snippets/Undef-Directive.sublime-snippet | 0 .../Symbol List - Exports.tmPreferences | 0 ...l List - Function Definition.tmPreferences | 0 ...ist - Function Specification.tmPreferences | 0 .../Symbol List - Imports.tmPreferences | 0 .../Erlang/Symbol List - Macro.tmPreferences | 0 .../Erlang/Symbol List - Record.tmPreferences | 0 .../Erlang/Symbol List - Type.tmPreferences | 0 .../Packages/Erlang/syntax_test_erlang.erl | 0 .../Packages/Erlang/syntax_test_erlang.yaws | 0 .../Git Formats/Comments.tmPreferences | 0 ...ttributes - Attributes.sublime-completions | 0 .../Git Attributes - Diff.sublime-completions | 0 .../Git Attributes - EOL.sublime-completions | 0 ... Attributes - Encoding.sublime-completions | 0 ...it Attributes - Filter.sublime-completions | 0 ...Git Attributes - Merge.sublime-completions | 0 .../Git Attributes - Text.sublime-completions | 0 ...ttributes - Whitespace.sublime-completions | 0 .../Git Attributes.sublime-settings | 0 .../Git Formats/Git Attributes.sublime-syntax | 0 .../Git Formats/Git Commit.sublime-syntax | 0 .../Git Formats/Git Common.sublime-syntax | 0 ...t Config - Indentation Rules.tmPreferences | 0 .../Git Config - Symbol List.tmPreferences | 0 .../Git Formats/Git Config.sublime-syntax | 0 .../Git Formats/Git Ignore.sublime-syntax | 0 .../Git Formats/Git Link.sublime-syntax | 0 .../Git Formats/Git Log.sublime-settings | 0 .../Git Formats/Git Log.sublime-syntax | 0 .../Git Mailmap - Symbol List.tmPreferences | 0 .../Git Formats/Git Mailmap.sublime-syntax | 0 .../Git Formats/Git Rebase.sublime-syntax | 0 .../Git Config - Section.sublime-snippet | 0 .../Git Formats/syntax_test_git_attributes | 0 .../Git Formats/syntax_test_git_commit | 0 .../Git Formats/syntax_test_git_config | 0 .../Git Formats/syntax_test_git_ignore | 0 .../Packages/Git Formats/syntax_test_git_link | 0 .../Packages/Git Formats/syntax_test_git_log | 0 .../Git Formats/syntax_test_git_mailmap | 0 .../Git Formats/syntax_test_git_merge | 0 .../Git Formats/syntax_test_git_rebase | 0 .../Packages/Git Formats/syntax_test_git_tag | 0 .../Packages/Go/Default.sublime-keymap | 0 .../Packages/Go/Go.sublime-completions | 0 .../syntaxes/Packages/Go/Go.sublime-settings | 0 .../syntaxes/Packages/Go/Go.sublime-syntax | 0 .../Packages/Go/GoCommentRules.tmPreferences | 0 .../Go/Indents/GoCommentIndent.tmPreferences | 0 .../Go/Indents/GoIndent.tmPreferences | 0 .../Go/Indents/GoStringIndent.tmPreferences | 0 .../Go/Snippets/go-defun.sublime-snippet | 0 .../Go/Snippets/go-fori.sublime-snippet | 0 .../Go/Snippets/go-gofun.sublime-snippet | 0 .../Go/Snippets/go-iferr.sublime-snippet | 0 .../Go/Symbols/GoConstSymbols.tmPreferences | 0 .../Go/Symbols/GoFuncSymbols.tmPreferences | 0 .../Go/Symbols/GoTypeSymbols.tmPreferences | 0 .../Go/Symbols/GoVarSymbols.tmPreferences | 0 .../syntaxes/Packages/Go/syntax_test_go.go | 0 .../Packages/Graphviz/Comments.tmPreferences | 0 .../Attribute Values.sublime-completions | 0 .../Attributes.sublime-completions | 0 .../Completions/Graphs.sublime-completions | 0 .../Completions/Objects.sublime-completions | 0 .../Packages/Graphviz/DOT.sublime-syntax | 0 .../Packages/Graphviz/Graphviz.sublime-build | 0 .../Graphviz/Indentation Rules.tmPreferences | 0 .../Graphviz/Symbol List.tmPreferences | 0 .../Packages/Graphviz/syntax_test_dot.dot | 0 .../Packages/Groovy/Comments.tmPreferences | 0 .../Packages/Groovy/Groovy.sublime-syntax | 0 .../#!-usr-local-bin-groovy-w.sublime-snippet | 0 .../Snippets/Ant-__-replace.sublime-snippet | 0 .../Snippets/Block-Comment.sublime-snippet | 0 .../Snippets/Constructor.sublime-snippet | 0 .../Groovy/Snippets/Hash-Pair.sublime-snippet | 0 .../Thread_start-{-__-}.sublime-snippet | 0 .../Thread_startDaemon-{-__-}.sublime-snippet | 0 .../Snippets/all{-e-__-}.sublime-snippet | 0 .../Snippets/any{-e-__-}.sublime-snippet | 0 .../Snippets/as-BigDecimal.sublime-snippet | 0 .../Snippets/as-BigInteger.sublime-snippet | 0 .../Groovy/Snippets/as-Double.sublime-snippet | 0 .../Groovy/Snippets/as-Float.sublime-snippet | 0 .../Snippets/as-Immutable.sublime-snippet | 0 .../Groovy/Snippets/as-Set.sublime-snippet | 0 .../Groovy/Snippets/as-String.sublime-snippet | 0 .../Snippets/as-Synchronized.sublime-snippet | 0 .../Snippets/as-Writable.sublime-snippet | 0 .../Snippets/assert(__).sublime-snippet | 0 .../Snippets/assertEquals(__).sublime-snippet | 0 .../Snippets/assertFalse.sublime-snippet | 0 .../assertNotEquals(__).sublime-snippet | 0 .../assertNotNull(__).sublime-snippet | 0 .../Snippets/assertNull(__).sublime-snippet | 0 .../Snippets/assertSame.sublime-snippet | 0 .../Snippets/assertTrue.sublime-snippet | 0 .../Groovy/Snippets/case.sublime-snippet | 0 .../class-__-singleton.sublime-snippet | 0 .../Groovy/Snippets/class-__.sublime-snippet | 0 .../class-___-TestCase.sublime-snippet | 0 .../Snippets/collect-{-e-__-}.sublime-snippet | 0 .../Snippets/copy__-file.sublime-snippet | 0 ...__-fileset-include-exclude.sublime-snippet | 0 .../Snippets/copy__-fileset.sublime-snippet | 0 .../def-__-closure-=-{__}.sublime-snippet | 0 .../def-__-method()-{__}.sublime-snippet | 0 .../downto(num)-{-n-__-}.sublime-snippet | 0 .../Snippets/each-{-e-__-}.sublime-snippet | 0 .../eachByte-{-byte-__-}.sublime-snippet | 0 .../eachDir-{-dir-__-}.sublime-snippet | 0 .../Snippets/eachDirMatch.sublime-snippet | 0 .../Snippets/eachDirRecurse.sublime-snippet | 0 .../eachFile-{-file-__-}.sublime-snippet | 0 .../eachFileMatch-{-file-__-}.sublime-snippet | 0 ...achFileRecurse-{-file-__-}.sublime-snippet | 0 .../eachKey-{-key-__-}.sublime-snippet | 0 .../eachLine-{-line-__-}.sublime-snippet | 0 ...hMatch(regex)-{-match-__-}.sublime-snippet | 0 .../eachObject-{-obj-__-}.sublime-snippet | 0 .../eachValue-{-val-__-}.sublime-snippet | 0 .../eachWithIndex-{-e-i-__-}.sublime-snippet | 0 .../Groovy/Snippets/else.sublime-snippet | 0 .../Snippets/elseif-___.sublime-snippet | 0 .../Snippets/every-{-e-__-}.sublime-snippet | 0 .../Snippets/final-method.sublime-snippet | 0 .../Groovy/Snippets/final-var.sublime-snippet | 0 .../Snippets/find-{-e-__-}.sublime-snippet | 0 .../Snippets/findAll-{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/for-in.sublime-snippet | 0 ...ep(-pattern-)-{-match-__-}.sublime-snippet | 0 .../Groovy/Snippets/if-else.sublime-snippet | 0 .../Groovy/Snippets/if.sublime-snippet | 0 .../Groovy/Snippets/import.sublime-snippet | 0 .../Groovy/Snippets/mkdir.sublime-snippet | 0 ...w-File(__)_eachLine-{-__-}.sublime-snippet | 0 .../Groovy/Snippets/package.sublime-snippet | 0 .../Groovy/Snippets/print.sublime-snippet | 0 .../Groovy/Snippets/println.sublime-snippet | 0 .../private-final-method.sublime-snippet | 0 .../private-final-var.sublime-snippet | 0 .../Snippets/private-method.sublime-snippet | 0 ...rivate-static-final-String.sublime-snippet | 0 ...rivate-static-final-method.sublime-snippet | 0 .../private-static-method.sublime-snippet | 0 .../private-static-var.sublime-snippet | 0 .../Snippets/private-var.sublime-snippet | 0 ...laceAll(regex)-{-match-__}.sublime-snippet | 0 .../reverseEach-{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/run-after.sublime-snippet | 0 .../Groovy/Snippets/setUp().sublime-snippet | 0 .../shouldFail(__)-{-__-}.sublime-snippet | 0 ...(secs)-{-__-on-interrupt-}.sublime-snippet | 0 .../Snippets/sleep(secs).sublime-snippet | 0 .../Snippets/sort-{-__-}.sublime-snippet | 0 ...eparator)-{-line-__-}-copy.sublime-snippet | 0 .../static-final-method.sublime-snippet | 0 .../Snippets/static-final-var.sublime-snippet | 0 .../static-main-method.sublime-snippet | 0 .../Snippets/static-method.sublime-snippet | 0 .../Snippets/static-var.sublime-snippet | 0 .../step(to-amount)-{-n-__-}.sublime-snippet | 0 .../Snippets/switch__case.sublime-snippet | 0 .../switch__case__default.sublime-snippet | 0 .../Snippets/tearDown().sublime-snippet | 0 .../Groovy/Snippets/test-case.sublime-snippet | 0 .../Snippets/times-{-n-__-}.sublime-snippet | 0 .../Groovy/Snippets/to-Array.sublime-snippet | 0 .../Snippets/to-BigDecimal.sublime-snippet | 0 .../Snippets/to-BigInteger.sublime-snippet | 0 .../Snippets/to-Boolean.sublime-snippet | 0 .../Snippets/to-Character.sublime-snippet | 0 .../Groovy/Snippets/to-Double.sublime-snippet | 0 .../Groovy/Snippets/to-Float.sublime-snippet | 0 .../Snippets/to-Integer.sublime-snippet | 0 .../Groovy/Snippets/to-List.sublime-snippet | 0 .../Groovy/Snippets/to-String.sublime-snippet | 0 .../Groovy/Snippets/to-URI.sublime-snippet | 0 .../Groovy/Snippets/to-URL.sublime-snippet | 0 .../try-__-catch__-finally.sublime-snippet | 0 .../Snippets/try-__-catch__.sublime-snippet | 0 .../upto(num)-{-n-__-}.sublime-snippet | 0 .../Groovy/Snippets/var.sublime-snippet | 0 .../Snippets/while-___-{___}.sublime-snippet | 0 .../withInputStream-{-in-__-}.sublime-snippet | 0 ...ithOutputStream-{-out-__-}.sublime-snippet | 0 .../withPrintWriter-{-pw-__}.sublime-snippet | 0 .../withReader-{-r-__-}.sublime-snippet | 0 .../withStream-{-in-__-}.sublime-snippet | 0 ...withStreams-{-Socket-s-__}.sublime-snippet | 0 ...thWriter(charset)-{-w-__-}.sublime-snippet | 0 .../withWriter-{-w-__}.sublime-snippet | 0 ...iterAppend(charset)-{-__-}.sublime-snippet | 0 ...mbol List%3A Class Variables.tmPreferences | 0 .../Symbol List%3A Classes.tmPreferences | 0 .../Symbol List%3A Methods.tmPreferences | 0 .../Symbol List%3A Variables.tmPreferences | 0 .../Packages/Groovy/syntax_test_groovy.groovy | 0 .../Groovy/tests/syntax_test_Strings.groovy | 0 .../Packages/HTML/Comments.tmPreferences | 0 .../Packages/HTML/HTML.sublime-syntax | 0 .../HTML/Indentation Rules.tmPreferences | 0 .../Snippets/html (begin tag).sublime-snippet | 0 .../HTML/Snippets/html.sublime-snippet | 0 .../HTML/Symbol List - ID.tmPreferences | 0 .../Packages/HTML/encode_html_entities.py | 0 .../Packages/HTML/html_completions.py | 0 .../Packages/HTML/syntax_test_html.html | 0 .../Packages/Haskell/Comments.tmPreferences | 0 .../Packages/Haskell/Haskell.sublime-build | 0 .../Packages/Haskell/Haskell.sublime-syntax | 0 .../Haskell/Indent Patterns.tmPreferences | 0 .../Haskell/Literate Haskell.sublime-syntax | 0 .../Haskell/Snippets/Case.sublime-snippet | 0 .../Haskell/Snippets/Instance.sublime-snippet | 0 .../Haskell/Snippets/Lambda.sublime-snippet | 0 .../Haskell/Snippets/Main.sublime-snippet | 0 .../Haskell/Snippets/module.sublime-snippet | 0 .../Haskell/Symbol List.tmPreferences | 0 .../Packages/Haskell/syntax_test_haskell.hs | 0 .../Packages/JSON/JSON Indent.tmPreferences | 0 .../Packages/JSON/JSON.sublime-syntax | 0 .../Packages/JSON/syntax_test_json.json | 0 .../syntaxes/Packages/Java/Ant.sublime-build | 0 .../Java/Comments - Properties.tmPreferences | 0 .../Packages/Java/Comments.tmPreferences | 0 .../Java/Completion Rules.tmPreferences | 0 .../Indentation Rules Annex.tmPreferences | 0 .../Java/Indentation Rules.tmPreferences | 0 .../Java/Indexed Symbol List.tmPreferences | 0 .../Java Server Pages (JSP).sublime-syntax | 0 .../Packages/Java/Java.sublime-completions | 0 .../Packages/Java/Java.sublime-syntax | 0 .../Packages/Java/JavaC.sublime-build | 0 .../Packages/Java/JavaDoc.sublime-syntax | 0 .../Java/JavaProperties.sublime-syntax | 0 .../Java/Snippets/abstract.sublime-snippet | 0 .../Java/Snippets/assert.sublime-snippet | 0 .../Java/Snippets/break.sublime-snippet | 0 .../Java/Snippets/case.sublime-snippet | 0 .../Java/Snippets/catch.sublime-snippet | 0 .../Java/Snippets/class.sublime-snippet | 0 .../Snippets/constant-string.sublime-snippet | 0 .../Java/Snippets/constant.sublime-snippet | 0 .../Java/Snippets/default.sublime-snippet | 0 .../Java/Snippets/else-if.sublime-snippet | 0 .../Java/Snippets/else.sublime-snippet | 0 .../Java/Snippets/final.sublime-snippet | 0 .../Java/Snippets/for-(each).sublime-snippet | 0 .../Java/Snippets/for.sublime-snippet | 0 .../Packages/Java/Snippets/if.sublime-snippet | 0 ...-junit_framework_TestCase;.sublime-snippet | 0 .../Java/Snippets/import.sublime-snippet | 0 .../Java/Snippets/interface.sublime-snippet | 0 .../Java/Snippets/java_beans_.sublime-snippet | 0 .../Java/Snippets/java_io.sublime-snippet | 0 .../Java/Snippets/java_math.sublime-snippet | 0 .../Java/Snippets/java_net_.sublime-snippet | 0 .../Java/Snippets/java_util_.sublime-snippet | 0 .../Snippets/method-(main).sublime-snippet | 0 .../Java/Snippets/method.sublime-snippet | 0 .../Java/Snippets/package.sublime-snippet | 0 .../Java/Snippets/print.sublime-snippet | 0 .../Java/Snippets/println.sublime-snippet | 0 .../Java/Snippets/private.sublime-snippet | 0 .../Java/Snippets/protected.sublime-snippet | 0 .../Java/Snippets/public.sublime-snippet | 0 .../Java/Snippets/return.sublime-snippet | 0 .../Java/Snippets/static.sublime-snippet | 0 .../Java/Snippets/switch.sublime-snippet | 0 .../Snippets/synchronized.sublime-snippet | 0 .../Java/Snippets/test-case.sublime-snippet | 0 .../Java/Snippets/test.sublime-snippet | 0 .../Java/Snippets/throw.sublime-snippet | 0 .../Java/Snippets/variable.sublime-snippet | 0 .../Java/Snippets/while.sublime-snippet | 0 .../Java/Symbol List - Classes.tmPreferences | 0 .../Symbol List - Constants.tmPreferences | 0 ...l List - Inner Class Methods.tmPreferences | 0 .../Symbol List - Inner Classes.tmPreferences | 0 ... - Inner Inner Class Methods.tmPreferences | 0 ...l List - Inner Inner Classes.tmPreferences | 0 .../Java/Symbol List - Method.tmPreferences | 0 .../Java/Symbol List - Modules.tmPreferences | 0 .../Symbol List - Properties.tmPreferences | 0 .../Packages/Java/syntax_test_java.java | 0 .../syntax_test_java_properties.properties | 0 .../Packages/Java/syntax_test_jsp.jsp | 0 .../JavaScript/Comments.tmPreferences | 0 .../JavaScript/Completion Rules.tmPreferences | 0 .../JavaScript/Default.sublime-keymap | 0 .../JavaScript/Indexed Symbols.tmPreferences | 0 .../JavaScript Indent.tmPreferences | 0 .../JavaScript/JavaScript.sublime-syntax | 0 ...ar Expressions (JavaScript).sublime-syntax | 0 .../Snippets/Get-Elements.sublime-snippet | 0 .../Snippets/Object-Method.sublime-snippet | 0 .../Snippets/Object-Value-JS.sublime-snippet | 0 .../Object-key-key-value.sublime-snippet | 0 .../Prototype-(proto).sublime-snippet | 0 .../for-()-{}-(faster).sublime-snippet | 0 .../Snippets/for-()-{}.sublime-snippet | 0 .../Snippets/function-(fun).sublime-snippet | 0 .../Snippets/function.sublime-snippet | 0 .../Snippets/if-___-else.sublime-snippet | 0 .../JavaScript/Snippets/if.sublime-snippet | 0 .../setTimeout-function.sublime-snippet | 0 .../Symbol List Banned.tmPreferences | 0 .../Symbol List Function.tmPreferences | 0 .../JavaScript/tests/syntax_test_js.js | 0 .../tests/syntax_test_js_bindings.js | 0 .../JavaScript/tests/syntax_test_js_regexp.js | 0 .../tests/syntax_test_js_support_builtin.js | 0 .../tests/syntax_test_js_support_console.js | 0 .../tests/syntax_test_js_support_dom.js | 0 .../tests/syntax_test_js_support_node.js | 0 .../assets}/syntaxes/Packages/LICENSE | 0 .../Packages/LaTeX/Bibtex.sublime-syntax | 0 .../Packages/LaTeX/Comments.tmPreferences | 0 .../LaTeX/Indentation Rules.tmPreferences | 0 .../Packages/LaTeX/LaTeX Log.sublime-syntax | 0 .../Packages/LaTeX/LaTeX.sublime-settings | 0 .../Packages/LaTeX/LaTeX.sublime-syntax | 0 .../LaTeX/Snippets/Cases.sublime-snippet | 0 .../LaTeX/Snippets/Chapter.sublime-snippet | 0 .../Snippets/Description.sublime-snippet | 0 .../Snippets/Displaymath-($$).sublime-snippet | 0 .../LaTeX/Snippets/Enumerate.sublime-snippet | 0 .../LaTeX/Snippets/Equation.sublime-snippet | 0 .../LaTeX/Snippets/Figure.sublime-snippet | 0 .../Item[description].sublime-snippet | 0 .../LaTeX/Snippets/Itemize.sublime-snippet | 0 .../LaTeX/Snippets/Listing.sublime-snippet | 0 .../LaTeX/Snippets/Matrix.sublime-snippet | 0 .../LaTeX/Snippets/Page.sublime-snippet | 0 .../LaTeX/Snippets/Paragraph.sublime-snippet | 0 .../LaTeX/Snippets/Part.sublime-snippet | 0 .../LaTeX/Snippets/Section.sublime-snippet | 0 .../LaTeX/Snippets/Split.sublime-snippet | 0 .../Snippets/Sub-Paragraph.sublime-snippet | 0 .../LaTeX/Snippets/Table.sublime-snippet | 0 .../LaTeX/Snippets/Tabular.sublime-snippet | 0 .../Snippets/begin{}-end{}.sublime-snippet | 0 .../section-..-(section).sublime-snippet | 0 .../subsection-..-(sub).sublime-snippet | 0 .../subsubsection-..-(ssub).sublime-snippet | 0 .../Symbol List - Commands.tmPreferences | 0 .../LaTeX/Symbol List - Labels.tmPreferences | 0 .../Symbol List - Sections.tmPreferences | 0 .../Packages/LaTeX/TeX.sublime-syntax | 0 .../Packages/LaTeX/syntax_test_latex.tex | 0 .../Packages/Lisp/Comments.tmPreferences | 0 .../Packages/Lisp/Lisp.sublime-syntax | 0 .../Packages/Lisp/Snippets/'(.sublime-snippet | 0 .../Lisp/Snippets/defconstant.sublime-snippet | 0 .../Lisp/Snippets/defmacro.sublime-snippet | 0 .../Snippets/defparameter.sublime-snippet | 0 .../Lisp/Snippets/defun.sublime-snippet | 0 .../Lisp/Snippets/defvar.sublime-snippet | 0 .../Packages/Lisp/Snippets/if.sublime-snippet | 0 .../Lisp/Snippets/let.sublime-snippet | 0 .../Lisp/Snippets/let1.sublime-snippet | 0 .../Lisp/Snippets/setf.sublime-snippet | 0 .../Packages/Lisp/syntax_test_lisp.lisp | 0 .../Packages/Lua/Comments.tmPreferences | 0 .../Lua/Completion Rules.tmPreferences | 0 .../Packages/Lua/Indent.tmPreferences | 0 .../syntaxes/Packages/Lua/Lua.sublime-build | 8 +- .../syntaxes/Packages/Lua/Lua.sublime-syntax | 0 .../for-i-v-in-ipairs().sublime-snippet | 0 .../Lua/Snippets/for-i=1-10.sublime-snippet | 0 .../for-k-v-in-pairs().sublime-snippet | 0 .../Snippets/function-(fun).sublime-snippet | 0 .../function-(function).sublime-snippet | 0 .../Lua/Snippets/local-x-=-1.sublime-snippet | 0 .../Lua/Snippets/table.concat.sublime-snippet | 0 .../Lua/Snippets/table.sort.sublime-snippet | 0 .../Packages/Lua/Symbol List.tmPreferences | 0 .../Packages/Lua/tests/syntax_test_lua.lua | 0 .../Lua/tests/syntax_test_lua_support.lua | 0 .../Packages/Makefile/Comments.tmPreferences | 0 .../Makefile/Make Output.sublime-syntax | 0 .../Packages/Makefile/Make.sublime-build | 0 .../Makefile/Makefile.sublime-settings | 0 .../Packages/Makefile/Makefile.sublime-syntax | 0 .../Makefile/Miscellaneous.tmPreferences | 0 .../Makefile/syntax_test_makefile.mak | 0 .../Markdown/Indent%3A Raw.tmPreferences | 0 .../Packages/Markdown/Markdown.sublime-syntax | 0 .../Markdown/MultiMarkdown.sublime-syntax | 0 .../Symbol List - Heading.tmPreferences | 0 ...Symbol List - Reference Link.tmPreferences | 0 .../Packages/Markdown/syntax_test_markdown.md | 0 .../Markdown/syntax_test_multimarkdown.md | 0 .../Packages/Matlab/Indent.tmPreferences | 0 .../Packages/Matlab/Matlab.sublime-syntax | 0 .../Matlab/Miscellaneous.tmPreferences | 0 .../Snippets/Octave-function.sublime-snippet | 0 .../Matlab/Snippets/^.sublime-snippet | 0 .../Matlab/Snippets/case.sublime-snippet | 0 .../Matlab/Snippets/clear.sublime-snippet | 0 .../Snippets/disp-sprintf.sublime-snippet | 0 .../Matlab/Snippets/disp.sublime-snippet | 0 .../Matlab/Snippets/dlmwrite.sublime-snippet | 0 .../Matlab/Snippets/else.sublime-snippet | 0 .../Matlab/Snippets/elseif.sublime-snippet | 0 .../Matlab/Snippets/error.sublime-snippet | 0 .../Matlab/Snippets/exp.sublime-snippet | 0 .../Matlab/Snippets/fprintf.sublime-snippet | 0 .../Matlab/Snippets/get.sublime-snippet | 0 .../Matlab/Snippets/griddata.sublime-snippet | 0 .../Matlab/Snippets/if-elseif.sublime-snippet | 0 .../Matlab/Snippets/line.sublime-snippet | 0 .../Matlab/Snippets/set.sublime-snippet | 0 .../Snippets/small-function.sublime-snippet | 0 .../Matlab/Snippets/sprintf.sublime-snippet | 0 ...h___case___otherwise___end.sublime-snippet | 0 .../Matlab/Snippets/title.sublime-snippet | 0 .../Matlab/Snippets/unix.sublime-snippet | 0 ...unwind_protect-cleanup-end.sublime-snippet | 0 .../Matlab/Snippets/warning.sublime-snippet | 0 .../Matlab/Snippets/while.sublime-snippet | 0 .../Matlab/Snippets/xlabel.sublime-snippet | 0 .../Matlab/Snippets/xtick.sublime-snippet | 0 .../Matlab/Snippets/ylabel.sublime-snippet | 0 .../Matlab/Snippets/ytick.sublime-snippet | 0 .../Matlab/Snippets/zlabel.sublime-snippet | 0 .../Packages/Matlab/Symbols.tmPreferences | 0 .../Packages/Matlab/syntax_test_matlab.m | 0 .../Packages/OCaml/Indent rules.tmPreferences | 0 .../OCaml/Miscellaneous.tmPreferences | 0 .../Packages/OCaml/OCaml.sublime-syntax | 0 .../Packages/OCaml/OCamllex.sublime-syntax | 0 .../Packages/OCaml/OCamlyacc.sublime-syntax | 0 .../OCaml/Snippets/Document.sublime-snippet | 0 .../OCaml/Snippets/For-Loop.sublime-snippet | 0 .../OCaml/Snippets/While-Loop.sublime-snippet | 0 .../OCaml/Snippets/begin.sublime-snippet | 0 .../OCaml/Snippets/class.sublime-snippet | 0 .../OCaml/Snippets/fun.sublime-snippet | 0 .../OCaml/Snippets/func.sublime-snippet | 0 .../Snippets/function-label.sublime-snippet | 0 .../OCaml/Snippets/let-in.sublime-snippet | 0 .../OCaml/Snippets/let.sublime-snippet | 0 .../Snippets/match-pattern.sublime-snippet | 0 .../OCaml/Snippets/match.sublime-snippet | 0 .../Snippets/method-(method).sublime-snippet | 0 .../Snippets/module-signature.sublime-snippet | 0 .../Snippets/module-type.sublime-snippet | 0 .../OCaml/Snippets/module.sublime-snippet | 0 .../OCaml/Snippets/try.sublime-snippet | 0 .../Snippets/type-(type).sublime-snippet | 0 .../OCaml/Snippets/untitled.sublime-snippet | 0 .../Symbol List%3A Classes.tmPreferences | 0 .../Symbol List%3A Exceptions.tmPreferences | 0 ... Ocamllex pattern definition.tmPreferences | 0 ... Ocamllex pattern references.tmPreferences | 0 ...ymbol List%3A Ocamllex rules.tmPreferences | 0 ...yacc non-terminal definition.tmPreferences | 0 ...lyacc non-terminal reference.tmPreferences | 0 ...A Ocamlyacc token definition.tmPreferences | 0 ...3A Ocamlyacc token reference.tmPreferences | 0 .../OCaml/Symbol List%3A Types.tmPreferences | 0 .../Symbol List%3A Variants.tmPreferences | 0 .../OCaml/Symbol List_ Classes.tmPreferences | 0 .../Symbol List_ Exceptions.tmPreferences | 0 ... Ocamllex pattern definition.tmPreferences | 0 ... Ocamllex pattern references.tmPreferences | 0 .../Symbol List_ Ocamllex rules.tmPreferences | 0 ...yacc non-terminal definition.tmPreferences | 0 ...lyacc non-terminal reference.tmPreferences | 0 ..._ Ocamlyacc token definition.tmPreferences | 0 ...t_ Ocamlyacc token reference.tmPreferences | 0 .../OCaml/Symbol List_ Types.tmPreferences | 0 .../OCaml/Symbol List_ Variants.tmPreferences | 0 .../Packages/OCaml/camlp4.sublime-syntax | 0 .../syntaxes/Packages/OCaml/syntax_test_ml.ml | 0 .../Objective-C/Default.sublime-keymap | 0 .../Objective-C/Objective-C++.sublime-syntax | 0 .../Objective-C/Objective-C.sublime-syntax | 0 ...mbol Index Include Constants.tmPreferences | 0 .../Objective-C/Symbol Index.tmPreferences | 0 .../Objective-C/syntax_test_accessor.m | 0 .../Objective-C/syntax_test_accessor.mm | 0 .../Objective-C/syntax_test_objc++.mm | 0 .../Packages/Objective-C/syntax_test_objc.m | 0 .../Packages/PHP/Comments.tmPreferences | 0 .../PHP/Completion Rules.tmPreferences | 0 ...entation Rules - heredoc end.tmPreferences | 0 .../PHP/Indentation Rules Annex.tmPreferences | 0 .../PHP/Indentation Rules.tmPreferences | 0 .../Packages/PHP/PHP Source.sublime-syntax | 0 .../Packages/PHP/PHP.sublime-completions | 0 .../syntaxes/Packages/PHP/PHP.sublime-syntax | 0 .../Regular Expressions (PHP).sublime-syntax | 0 .../PHP/Snippets/$GLOBALS[''].sublime-snippet | 0 .../PHP/Snippets/$_COOKIE[''].sublime-snippet | 0 .../PHP/Snippets/$_ENV[''].sublime-snippet | 0 .../PHP/Snippets/$_FILES[''].sublime-snippet | 0 .../PHP/Snippets/$_GET[''].sublime-snippet | 0 .../PHP/Snippets/$_POST[''].sublime-snippet | 0 .../Snippets/$_REQUEST[''].sublime-snippet | 0 .../PHP/Snippets/$_SERVER[''].sublime-snippet | 0 .../Snippets/$_SESSION[''].sublime-snippet | 0 .../PHP/Snippets/Constructor.sublime-snippet | 0 .../Snippets/PHPDoc-class-var.sublime-snippet | 0 .../PHP/Snippets/PHPDoc-class.sublime-snippet | 0 ...PHPDoc-constant-definition.sublime-snippet | 0 .../PHPDoc-function-signature.sublime-snippet | 0 .../Snippets/PHPDoc-function.sublime-snippet | 0 .../Snippets/PHPDoc-interface.sublime-snippet | 0 .../Snippets/Start-Docblock.sublime-snippet | 0 .../PHP/Snippets/class-{-}.sublime-snippet | 0 .../PHP/Snippets/define(-).sublime-snippet | 0 .../PHP/Snippets/defined(-).sublime-snippet | 0 .../PHP/Snippets/do-while(-).sublime-snippet | 0 .../PHP/Snippets/echo-___.sublime-snippet | 0 .../PHP/Snippets/elseif(-).sublime-snippet | 0 .../PHP/Snippets/for(-).sublime-snippet | 0 .../PHP/Snippets/foreach(-).sublime-snippet | 0 .../Snippets/function-xx(-).sublime-snippet | 0 .../Snippets/if(-)-else(-).sublime-snippet | 0 .../PHP/Snippets/if(-).sublime-snippet | 0 .../PHP/Snippets/if-a-b;.sublime-snippet | 0 .../PHP/Snippets/include(-).sublime-snippet | 0 .../Snippets/include_once(-).sublime-snippet | 0 .../PHP/Snippets/new-array(-).sublime-snippet | 0 .../PHP/Snippets/php-$this.sublime-snippet | 0 .../Snippets/php-echo-$this.sublime-snippet | 0 .../PHP/Snippets/php-echo-___.sublime-snippet | 0 ...php-echo-htmlentities(___).sublime-snippet | 0 .../PHP/Snippets/php-else.sublime-snippet | 0 ...h-(___)-___-php-endforeach.sublime-snippet | 0 ...___-php-else-___-php-endif.sublime-snippet | 0 ...php-if-(___)-___-php-endif.sublime-snippet | 0 .../Packages/PHP/Snippets/php.sublime-snippet | 0 .../PHP/Snippets/require(-).sublime-snippet | 0 .../Snippets/require_once(-).sublime-snippet | 0 .../Snippets/return-$retVal;.sublime-snippet | 0 .../Snippets/return-FALSE;.sublime-snippet | 0 .../PHP/Snippets/return-TRUE;.sublime-snippet | 0 .../Snippets/switch(-)-case.sublime-snippet | 0 .../PHP/Snippets/switch(-).sublime-snippet | 0 .../PHP/Snippets/throw.sublime-snippet | 0 ...-___-}-catch-(___)-{-___-}.sublime-snippet | 0 .../PHP/Snippets/while(-).sublime-snippet | 0 .../Packages/PHP/Symbol List.tmPreferences | 0 .../syntaxes/Packages/PHP/syntax_test_php.php | 0 .../Pascal/Miscellaneous.tmPreferences | 0 .../Packages/Pascal/Pascal.sublime-syntax | 0 .../syntaxes/Packages/Pascal/syntax_test.pas | 0 .../Packages/Perl/Comments.tmPreferences | 0 .../syntaxes/Packages/Perl/Perl.sublime-build | 0 .../Packages/Perl/Perl.sublime-syntax | 0 .../Conditional-if-(if).sublime-snippet | 0 ...Conditional-if..else-(ife).sublime-snippet | 0 ...nal-if..elsif..else-(ifee).sublime-snippet | 0 ...Conditional-one-line-(xif).sublime-snippet | 0 ...itional-one-line-(xunless).sublime-snippet | 0 ...nditional-one-line-(xwhen).sublime-snippet | 0 ...onditional-unless-(unless).sublime-snippet | 0 ...nal-unless..else-(unlesse).sublime-snippet | 0 ...ss..elsif..else-(unlessee).sublime-snippet | 0 .../Conditional-when-(when).sublime-snippet | 0 .../Snippets/Function-(sub).sublime-snippet | 0 .../Snippets/Loop-for-(for).sublime-snippet | 0 .../Loop-foreach-(fore).sublime-snippet | 0 .../Loop-one-line-(xfor).sublime-snippet | 0 .../Loop-one-line-(xfore).sublime-snippet | 0 .../Loop-one-line-(xuntil).sublime-snippet | 0 .../Loop-one-line-(xwhile).sublime-snippet | 0 .../Loop-while-(while).sublime-snippet | 0 .../Perl/Snippets/Test.sublime-snippet | 0 .../Perl/Snippets/class.sublime-snippet | 0 .../Perl/Snippets/eval.sublime-snippet | 0 .../Perl/Snippets/slurp.sublime-snippet | 0 .../Packages/Perl/syntax_test_perl.pl | 0 .../Python/Completion Rules.tmPreferences | 0 .../Packages/Python/Default.sublime-keymap | 0 .../Python/Miscellaneous.tmPreferences | 0 .../Packages/Python/Python.sublime-build | 0 .../Packages/Python/Python.sublime-syntax | 0 ...egular Expressions (Python).sublime-syntax | 0 .../Python/Snippets/New-Class.sublime-snippet | 0 .../Snippets/New-Property.sublime-snippet | 0 .../Try-Except-Else-Finally.sublime-snippet | 0 .../Snippets/Try-Except-Else.sublime-snippet | 0 .../Try-Except-Finally.sublime-snippet | 0 .../Snippets/Try-Except.sublime-snippet | 0 .../Python/Snippets/__magic__.sublime-snippet | 0 .../Python/Snippets/for.sublime-snippet | 0 .../Python/Snippets/function.sublime-snippet | 0 .../if-__name__-==-'__main__'.sublime-snippet | 0 .../Python/Snippets/if.sublime-snippet | 0 .../Python/Snippets/method.sublime-snippet | 0 .../Python/Snippets/while.sublime-snippet | 0 .../Python/Symbol Index.tmPreferences | 0 .../Packages/Python/Symbol List.tmPreferences | 0 .../Packages/Python/syntax_test_python.py | 0 .../Python/syntax_test_python_strings.py | 0 .../Packages/R/Comments.tmPreferences | 0 .../Packages/R/R Console.sublime-syntax | 0 .../syntaxes/Packages/R/R.sublime-build | 0 .../syntaxes/Packages/R/R.sublime-settings | 0 .../syntaxes/Packages/R/R.sublime-syntax | 0 .../R/Rd (R Documentation).sublime-syntax | 0 .../R/Snippets/Add-Tick-Marks.sublime-snippet | 0 .../R/Snippets/Attach.sublime-snippet | 0 .../R/Snippets/Cummulative.sublime-snippet | 0 .../R/Snippets/Density.sublime-snippet | 0 .../R/Snippets/Detach.sublime-snippet | 0 .../Divide-Into-Intervals.sublime-snippet | 0 .../R/Snippets/Factor.sublime-snippet | 0 .../R/Snippets/For-Loop.sublime-snippet | 0 .../R/Snippets/Function.sublime-snippet | 0 .../R/Snippets/Ifelse.sublime-snippet | 0 .../R/Snippets/Length.sublime-snippet | 0 .../R/Snippets/Load-Dataset.sublime-snippet | 0 .../R/Snippets/Polygonal-Line.sublime-snippet | 0 .../R/Snippets/Read-From-File.sublime-snippet | 0 .../Sequence-(from-to-by).sublime-snippet | 0 .../Packages/R/Snippets/Sort.sublime-snippet | 0 .../R/Snippets/Source.sublime-snippet | 0 .../R/Snippets/na_omit.sublime-snippet | 0 .../R/Symbol List - Methods.tmPreferences | 0 ... Sections (Rd Documentation).tmPreferences | 0 .../R/Symbol List - Sections.tmPreferences | 0 .../syntaxes/Packages/R/syntax_test_r.R | 0 .../assets}/syntaxes/Packages/README.md | 0 .../Rails/HTML (Rails).sublime-syntax | 0 .../Rails/JavaScript (Rails).sublime-syntax | 0 .../Rails/Ruby Haml Comments.tmPreferences | 0 .../Packages/Rails/Ruby Haml.sublime-syntax | 0 .../Rails/Ruby on Rails.sublime-syntax | 0 .../Packages/Rails/SQL (Rails).sublime-syntax | 0 .../Rails/Snippets/$LABEL.sublime-snippet | 0 ...s_identify(%3Asymbol)-%%3E.sublime-snippet | 0 .../180-rails-form_tag.sublime-snippet | 0 .../Create-binary-column.sublime-snippet | 0 .../Create-boolean-column.sublime-snippet | 0 .../Create-controller-class.sublime-snippet | 0 .../Create-date-column.sublime-snippet | 0 .../Create-datetime-column.sublime-snippet | 0 .../Create-decimal-column.sublime-snippet | 0 .../Create-float-column.sublime-snippet | 0 ...eate-functional-test-class.sublime-snippet | 0 .../Create-integer-column.sublime-snippet | 0 ...Create-lock_version-column.sublime-snippet | 0 .../Create-references-column.sublime-snippet | 0 .../Create-string-column.sublime-snippet | 0 .../Create-text-column.sublime-snippet | 0 .../Create-time-column.sublime-snippet | 0 .../Create-timestamp-column.sublime-snippet | 0 .../Create-timestamps-columns.sublime-snippet | 0 ...ration-Create-Column-(mcc).sublime-snippet | 0 ...ate-Column-Continue-(mccc).sublime-snippet | 0 ...n-Drop-Create-Table-(mdct).sublime-snippet | 0 ...move-and-Add-Column-(mrac).sublime-snippet | 0 ...DEFAULT_LOGGER.debug-(rdb).sublime-snippet | 0 .../Table-column(s)-rename.sublime-snippet | 0 ...Assert-Redirected-To-(art).sublime-snippet | 0 ...Test-Assert-Response-(are).sublime-snippet | 0 .../Snippets/after_create.sublime-snippet | 0 .../Snippets/after_destroy.sublime-snippet | 0 .../Rails/Snippets/after_save.sublime-snippet | 0 .../Snippets/after_update.sublime-snippet | 0 .../Snippets/after_validation.sublime-snippet | 0 ...after_validation_on_create.sublime-snippet | 0 ...after_validation_on_update.sublime-snippet | 0 ...ert(var-=-assigns(%3Avar)).sublime-snippet | 0 .../assert_difference.sublime-snippet | 0 .../assert_no_difference.sublime-snippet | 0 ...edirected_to-(nested-path).sublime-snippet | 0 ...ed_to-(nested-path-plural).sublime-snippet | 0 ...ssert_redirected_to-(path).sublime-snippet | 0 ...edirected_to-(path-plural).sublime-snippet | 0 .../Snippets/assert_select.sublime-snippet | 0 .../Snippets/before_create.sublime-snippet | 0 .../Snippets/before_destroy.sublime-snippet | 0 .../Snippets/before_save.sublime-snippet | 0 .../Snippets/before_update.sublime-snippet | 0 .../before_validation.sublime-snippet | 0 ...efore_validation_on_create.sublime-snippet | 0 ...efore_validation_on_update.sublime-snippet | 0 .../Snippets/belongs_to-(bt).sublime-snippet | 0 .../Snippets/cattr_accessor.sublime-snippet | 0 .../def-create-resource.sublime-snippet | 0 .../Snippets/def-get-request.sublime-snippet | 0 .../Snippets/def-post-request.sublime-snippet | 0 .../Rails/Snippets/end.sublime-snippet | 0 .../Rails/Snippets/find(id).sublime-snippet | 0 .../Snippets/for-loop-erb.sublime-snippet | 0 .../form_for-check_box.sublime-snippet | 0 .../form_for-checkbox.sublime-snippet | 0 .../form_for-file_field-2.sublime-snippet | 0 .../form_for-file_field.sublime-snippet | 0 .../form_for-hidden_field-2.sublime-snippet | 0 .../form_for-hidden_field.sublime-snippet | 0 .../Snippets/form_for-label-2.sublime-snippet | 0 .../Snippets/form_for-label.sublime-snippet | 0 .../form_for-password_field-2.sublime-snippet | 0 .../form_for-password_field.sublime-snippet | 0 .../form_for-radio_box-2.sublime-snippet | 0 .../form_for-radio_box.sublime-snippet | 0 .../form_for-submit-2.sublime-snippet | 0 .../Snippets/form_for-submit.sublime-snippet | 0 .../form_for-text_area-2.sublime-snippet | 0 .../form_for-text_area.sublime-snippet | 0 .../form_for-text_field-2.sublime-snippet | 0 .../form_for-text_field.sublime-snippet | 0 .../form_for-with-errors.sublime-snippet | 0 .../Rails/Snippets/form_for.sublime-snippet | 0 ...nd_belongs_to_many-(habtm).sublime-snippet | 0 .../Snippets/has_many-(hm).sublime-snippet | 0 .../has_many-(through).sublime-snippet | 0 ...s_many-dependent-=-destroy.sublime-snippet | 0 .../Snippets/has_one-(ho).sublime-snippet | 0 .../Snippets/image_submit_tag.sublime-snippet | 0 .../javascript_include_tag.sublime-snippet | 0 .../Rails/Snippets/lia.sublime-snippet | 0 .../Rails/Snippets/liai.sublime-snippet | 0 .../Rails/Snippets/lic.sublime-snippet | 0 .../Rails/Snippets/lica.sublime-snippet | 0 .../Rails/Snippets/licai.sublime-snippet | 0 .../link_to-(nested-path).sublime-snippet | 0 ...nk_to-(nested-path-plural).sublime-snippet | 0 .../Snippets/link_to-(path).sublime-snippet | 0 .../link_to-(path-plural).sublime-snippet | 0 .../Snippets/link_to-model.sublime-snippet | 0 .../Snippets/logger_debug.sublime-snippet | 0 .../Snippets/logger_error.sublime-snippet | 0 .../Snippets/logger_fatal.sublime-snippet | 0 .../Snippets/logger_info.sublime-snippet | 0 .../Snippets/logger_warn.sublime-snippet | 0 .../map(-%3Asym_proc).sublime-snippet | 0 .../Snippets/map_catch_all.sublime-snippet | 0 .../Snippets/map_named_route.sublime-snippet | 0 .../Snippets/map_resource.sublime-snippet | 0 .../Snippets/map_resources.sublime-snippet | 0 .../Snippets/map_with_options.sublime-snippet | 0 .../Snippets/mattr_accessor.sublime-snippet | 0 .../named_scope-lambda.sublime-snippet | 0 .../Snippets/named_scope.sublime-snippet | 0 .../Snippets/rails-flash.sublime-snippet | 0 .../Rails/Snippets/rea.sublime-snippet | 0 .../Rails/Snippets/reai.sublime-snippet | 0 .../Rails/Snippets/rec.sublime-snippet | 0 .../Rails/Snippets/reca.sublime-snippet | 0 .../Rails/Snippets/recai.sublime-snippet | 0 .../redirect_to-(nested-path).sublime-snippet | 0 ...ct_to-(nested-path-plural).sublime-snippet | 0 .../redirect_to-(path).sublime-snippet | 0 .../redirect_to-(path-plural).sublime-snippet | 0 .../render-(action)...-(ra).sublime-snippet | 0 ...nder-(action-layout)-(ral).sublime-snippet | 0 .../render-(file)-(rf).sublime-snippet | 0 ...(file-use_full_path)-(rfu).sublime-snippet | 0 .../render-(inline)-(ri).sublime-snippet | 0 ...nder-(inline-locals)-(ril).sublime-snippet | 0 ...render-(inline-type)-(rit).sublime-snippet | 0 .../render-(layout)-(rl).sublime-snippet | 0 .../render-(nothing)-(rn).sublime-snippet | 0 ...der-(nothing-status)-(rns).sublime-snippet | 0 .../render-(partial)-(rp).sublime-snippet | 0 ...(partial-collection)-(rpc).sublime-snippet | 0 ...der-(partial-locals)-(rpl).sublime-snippet | 0 ...der-(partial-object)-(rpo).sublime-snippet | 0 ...der-(partial-status)-(rps).sublime-snippet | 0 .../render-(text)-(rt).sublime-snippet | 0 ...render-(text-layout)-(rtl).sublime-snippet | 0 ...ext-layout=%3Etrue)-(rtlt).sublime-snippet | 0 ...render-(text-status)-(rts).sublime-snippet | 0 .../Snippets/render-(update).sublime-snippet | 0 .../Rails/Snippets/respond_to.sublime-snippet | 0 ...Cvariable%7C-%E2%80%A6-end.sublime-snippet | 0 .../stylesheet_link_tag.sublime-snippet | 0 .../Rails/Snippets/submit_tag.sublime-snippet | 0 .../Snippets/t_binary-(tcbi).sublime-snippet | 0 .../Snippets/t_boolean-(tcb).sublime-snippet | 0 .../Snippets/t_date-(tcda).sublime-snippet | 0 .../t_datetime-(tcdt).sublime-snippet | 0 .../Snippets/t_decimal-(tcd).sublime-snippet | 0 .../Snippets/t_float-(tcf).sublime-snippet | 0 .../Snippets/t_integer-(tci).sublime-snippet | 0 .../t_lock_version-(tcl).sublime-snippet | 0 .../t_references-(tcr).sublime-snippet | 0 .../Snippets/t_rename-(tre).sublime-snippet | 0 .../Snippets/t_string-(tcs).sublime-snippet | 0 .../Snippets/t_text-(tct).sublime-snippet | 0 .../Snippets/t_time-(tcti).sublime-snippet | 0 .../t_timestamp-(tcts).sublime-snippet | 0 .../t_timestamps-(tctss).sublime-snippet | 0 ...validates_acceptance_of-if.sublime-snippet | 0 .../validates_acceptance_of.sublime-snippet | 0 .../validates_associated-(va).sublime-snippet | 0 ...dates_associated-if-(vaif).sublime-snippet | 0 ...dates_confirmation_of-(vc).sublime-snippet | 0 ..._confirmation_of-if-(vcif).sublime-snippet | 0 ...alidates_exclusion_of-(ve).sublime-snippet | 0 ...tes_exclusion_of-if-(veif).sublime-snippet | 0 .../validates_format_of-if.sublime-snippet | 0 .../validates_format_of.sublime-snippet | 0 .../validates_inclusion_of-if.sublime-snippet | 0 .../validates_inclusion_of.sublime-snippet | 0 .../validates_length_of-(vl).sublime-snippet | 0 .../validates_length_of-if.sublime-snippet | 0 ...lidates_numericality_of-if.sublime-snippet | 0 .../validates_numericality_of.sublime-snippet | 0 ...validates_presence_of-(vp).sublime-snippet | 0 ...es_presence_of-if-(vpif)-2.sublime-snippet | 0 ...lidates_uniqueness_of-(vu).sublime-snippet | 0 ...es_uniqueness_of-if-(vuif).sublime-snippet | 0 .../Snippets/verify-(verify).sublime-snippet | 0 .../verify-redirect-(verify).sublime-snippet | 0 .../Snippets/wants_format.sublime-snippet | 0 .../Rails/Snippets/xhr-delete.sublime-snippet | 0 .../Rails/Snippets/xhr-get.sublime-snippet | 0 .../Rails/Snippets/xhr-post.sublime-snippet | 0 .../Rails/Snippets/xhr-put.sublime-snippet | 0 .../Rails/Template (ERB).tmPreferences | 0 .../Rails/Template (Haml).tmPreferences | 0 .../Rails/syntax_test_html_rails.html.erb | 0 .../Packages/Rails/syntax_test_rails.rb | 0 .../Comments.tmPreferences | 0 .../Regular Expressions/RegExp.sublime-syntax | 0 .../Regular Expressions/syntax_test_regexp.re | 0 .../RestructuredText/Comments.tmPreferences | 0 .../reStructuredText.sublime-syntax | 0 .../syntax_test_restructuredtext.rst | 0 .../Packages/Ruby/Comments.tmPreferences | 0 .../Ruby/Completion Rules.tmPreferences | 0 .../Packages/Ruby/Default.sublime-keymap | 0 .../Packages/Ruby/Miscellaneous.tmPreferences | 0 .../syntaxes/Packages/Ruby/Ruby.sublime-build | 0 .../Packages/Ruby/Ruby.sublime-syntax | 0 .../#!;usr;local;bin;ruby-w.sublime-snippet | 0 .../Snippets/060-ruby-if-else.sublime-snippet | 0 .../Ruby/Snippets/070-ruby-if.sublime-snippet | 0 .../Snippets/080-ruby-case.sublime-snippet | 0 .../Add-'#-=-'-Marker.sublime-snippet | 0 ...ray.new(10)-{-i-..-}-(Arr).sublime-snippet | 0 ...nchmark_bmbm(__)-do-__-end.sublime-snippet | 0 ...-..-)-do-file-..-end-(Dir).sublime-snippet | 0 .../Ruby/Snippets/Dir[-__-].sublime-snippet | 0 .....-)-do-line-..-end-(File).sublime-snippet | 0 ...ile_open(-__-)-{-file-__-}.sublime-snippet | 0 .../Snippets/File_read(-__-).sublime-snippet | 0 ...key-hash[key]-=-..-}-(Has).sublime-snippet | 0 ...arshal.dump(obj-file)-(Md).sublime-snippet | 0 .../Marshal.load(obj)-(Ml).sublime-snippet | 0 .../Snippets/PStore_new(-__-).sublime-snippet | 0 .../RDoc-documentation-block.sublime-snippet | 0 .../Wrap-in-Begin-Rescue-End.sublime-snippet | 0 .../YAML.dump(..-file)-(Yd-).sublime-snippet | 0 .../YAML.load(file)-(Yl-).sublime-snippet | 0 .../alias_method-..-(am).sublime-snippet | 0 .../all-{-e-..-}-(all).sublime-snippet | 0 .../any-{-e-..-}-(any).sublime-snippet | 0 .../application_code-..-(app).sublime-snippet | 0 .../Snippets/assert(..)-(as).sublime-snippet | 0 .../Snippets/assert_equal.sublime-snippet | 0 ...assert_in_delta(..)-(asid).sublime-snippet | 0 ...ert_instance_of(..)-(asio).sublime-snippet | 0 .../assert_kind_of(..)-(asko).sublime-snippet | 0 .../assert_match(..)-(asm).sublime-snippet | 0 .../assert_nil(..)-(asn).sublime-snippet | 0 ...assert_no_match(..)-(asnm).sublime-snippet | 0 ...ssert_not_equal(..)-(asne).sublime-snippet | 0 .../assert_not_nil(..)-(asnn).sublime-snippet | 0 ...assert_not_same(..)-(asns).sublime-snippet | 0 ...g_raised(..)-{-..-}-(asnr).sublime-snippet | 0 ...thing_thrown-{-..-}-(asnt).sublime-snippet | 0 .../assert_operator(..)-(aso).sublime-snippet | 0 ...ert_raise(..)-{-..-}-(asr).sublime-snippet | 0 ...sert_respond_to(..)-(asrt).sublime-snippet | 0 .../assert_same(..)-(ass).sublime-snippet | 0 .../assert_send(..)-(ass).sublime-snippet | 0 ...rt_throws(..)-{-..-}-(ast).sublime-snippet | 0 .../attr_accessor-..-(rw).sublime-snippet | 0 .../attr_reader-..-(r).sublime-snippet | 0 .../attr_writer-..-(w).sublime-snippet | 0 ...-initialize-..-end-(class).sublime-snippet | 0 ...Class-..-initialize-..-end.sublime-snippet | 0 ...truct-..-initialize-..-end.sublime-snippet | 0 ...Unit;;TestCase-..-end-(tc).sublime-snippet | 0 .../class-..-end-(cla).sublime-snippet | 0 ...class-..-initialize-..-end.sublime-snippet | 0 ...-initialize-..-end-(class).sublime-snippet | 0 .../class-self-__-end.sublime-snippet | 0 .../class_from_name()-(clafn).sublime-snippet | 0 .../classify-{-e-..-}-(clas).sublime-snippet | 0 .../collect-{-e-..-}-(col).sublime-snippet | 0 .../deep_copy(..)-(dee).sublime-snippet | 0 .../Ruby/Snippets/def-end.sublime-snippet | 0 ...method_missing-..-end-(mm).sublime-snippet | 0 .../def-self-..-end-(defs).sublime-snippet | 0 .../def-test_-..-end-(t).sublime-snippet | 0 .../def_delegator-..-(defd).sublime-snippet | 0 .../def_delegators-..-(defds).sublime-snippet | 0 .../Snippets/def_initialize.sublime-snippet | 0 .../delete_if-{-e-..-}-(deli).sublime-snippet | 0 .../detect-{-e-..-}-(det).sublime-snippet | 0 .../Ruby/Snippets/directory().sublime-snippet | 0 .../do-obj-..-end-(doo).sublime-snippet | 0 .../downto(0)-{-n-..-}-(dow).sublime-snippet | 0 .../each-{-e-..-}-(ea).sublime-snippet | 0 ...ach_byte-{-byte-..-}-(eab).sublime-snippet | 0 ...ach_char-{-chr-..-}-(eac-).sublime-snippet | 0 ...ns(..)-{-group-..-}-(eac-).sublime-snippet | 0 .../each_index-{-i-..-}-(eai).sublime-snippet | 0 .../each_key-{-key-..-}-(eak).sublime-snippet | 0 ...ach_line-{-line-..-}-(eal).sublime-snippet | 0 ...pair-{-name-val-..-}-(eap).sublime-snippet | 0 ...h_slice-{-group-..-}-(eas).sublime-snippet | 0 ...ach_value-{-val-..-}-(eav).sublime-snippet | 0 ...th_index-{-e-i-..-}-(eawi).sublime-snippet | 0 .../Ruby/Snippets/elsif-___.sublime-snippet | 0 .../extend-Forwardable-(Forw).sublime-snippet | 0 ...tch(name)-{-key-..-}-(fet).sublime-snippet | 0 ...fill(range)-{-i-..-}-(fil).sublime-snippet | 0 .../find-{-e-..-}-(fin).sublime-snippet | 0 .../find_all-{-e-..-}-(fina).sublime-snippet | 0 .../flatten_once-(fla).sublime-snippet | 0 .../Snippets/flunk(..)-(fl).sublime-snippet | 0 ...ttern;)-{-match-..-}-(gre).sublime-snippet | 0 ...b(;..;)-{-match-..-}-(gsu).sublime-snippet | 0 .../Snippets/hash-pair-(-).sublime-snippet | 0 ...clude-Comparable-..-(Comp).sublime-snippet | 0 ...clude-Enumerable-..-(Enum).sublime-snippet | 0 ...init)-{-mem-var-..-}-(inj).sublime-snippet | 0 .../lambda-{-args-..-}-(lam).sublime-snippet | 0 .../Ruby/Snippets/loop-{-__-}.sublime-snippet | 0 .../map-{-e-..-}-(map).sublime-snippet | 0 ...h_index-{-e-i-..-}-(mapwi).sublime-snippet | 0 .../max-{-a-b-..-}-(max).sublime-snippet | 0 .../min-{-a-b-..-}-(min).sublime-snippet | 0 ...ule-..-ClassMethods-..-end.sublime-snippet | 0 .../Snippets/module-..-end.sublime-snippet | 0 ...-..-module_function-..-end.sublime-snippet | 0 .../namespace-__-do-__-end.sublime-snippet | 0 ...rl-w-)-do-doc-..-end-(ope).sublime-snippet | 0 .../open-yield-block-({).sublime-snippet | 0 ...option_parse-{-..-}-(optp).sublime-snippet | 0 .../partition-{-e-..-}-(par).sublime-snippet | 0 .../path_from_here(-__-).sublime-snippet | 0 .../Snippets/randomize-(ran).sublime-snippet | 0 .../reject-{-e-..-}-(rej).sublime-snippet | 0 .../Snippets/require-..-(req).sublime-snippet | 0 .../require-tc_..-..-(ts).sublime-snippet | 0 .../Snippets/require_gem-__.sublime-snippet | 0 .../results_report(__)-{-__-}.sublime-snippet | 0 ...everse_each-{-e-..-}-(rea).sublime-snippet | 0 ...n(;..;)-{-match-..-}-(sca).sublime-snippet | 0 .../select-{-e-..-}-(sel).sublime-snippet | 0 .../Snippets/service_object.sublime-snippet | 0 .../singleton_class().sublime-snippet | 0 .../sort-{-a-b-..-}-(sor).sublime-snippet | 0 .../sort_by-{-e-..-}-(sorb).sublime-snippet | 0 .../step(2)-{-e-..-}-(ste).sublime-snippet | 0 ...b(;..;)-{-match-..-}-(sub).sublime-snippet | 0 ...dependent-tasks]-do-__-end.sublime-snippet | 0 .../times-{-n-..-}-(tim).sublime-snippet | 0 ...ransaction(-__-)-do-__-end.sublime-snippet | 0 .../unix_filter-..-(uni).sublime-snippet | 0 .../Snippets/unless-(unless).sublime-snippet | 0 .../Snippets/until-___-end.sublime-snippet | 0 .../Ruby/Snippets/untitled.sublime-snippet | 0 ...to(1.0;0.0)-{-n-..-}-(upt).sublime-snippet | 0 .../usage_if()-(usai).sublime-snippet | 0 .../usage_unless()-(usau).sublime-snippet | 0 .../Ruby/Snippets/when.sublime-snippet | 0 .../Snippets/while-___-end.sublime-snippet | 0 .../Ruby/Snippets/xmlread(__).sublime-snippet | 0 .../Snippets/xpath(__)-{-__-}.sublime-snippet | 0 .../yields-RDoc-comment.sublime-snippet | 0 ...ip(enums)-{-row-..-}-(zip).sublime-snippet | 0 .../Symbols - Classes - Modules.tmPreferences | 0 .../Ruby/Symbols - Methods.tmPreferences | 0 .../Packages/Ruby/syntax_test_ruby.rb | 0 .../Packages/Rust/Cargo.sublime-build | 0 .../Packages/Rust/Cargo.sublime-syntax | 0 .../Packages/Rust/Default.sublime-keymap | 0 .../syntaxes/Packages/Rust/LICENSE.txt | 0 .../syntaxes/Packages/Rust/Rust.sublime-build | 0 .../Packages/Rust/Rust.sublime-syntax | 0 .../Packages/Rust/RustComment.tmPreferences | 0 .../Packages/Rust/RustIndent.tmPreferences | 0 .../Packages/Rust/RustSymbols.tmPreferences | 0 .../Rust/Snippets/Err.sublime-snippet | 0 .../Packages/Rust/Snippets/Ok.sublime-snippet | 0 .../Rust/Snippets/Some.sublime-snippet | 0 .../Rust/Snippets/assert.sublime-snippet | 0 .../Rust/Snippets/assert_eq.sublime-snippet | 0 .../Rust/Snippets/bench.sublime-snippet | 0 .../Rust/Snippets/const.sublime-snippet | 0 .../Rust/Snippets/else.sublime-snippet | 0 .../Rust/Snippets/enum.sublime-snippet | 0 .../Snippets/extern-crate.sublime-snippet | 0 .../Rust/Snippets/extern-fn.sublime-snippet | 0 .../Rust/Snippets/extern-mod.sublime-snippet | 0 .../Rust/Snippets/fmt.sublime-snippet | 0 .../Packages/Rust/Snippets/fn.sublime-snippet | 0 .../Rust/Snippets/for.sublime-snippet | 0 .../Rust/Snippets/if-let.sublime-snippet | 0 .../Packages/Rust/Snippets/if.sublime-snippet | 0 .../Rust/Snippets/impl-trait.sublime-snippet | 0 .../Rust/Snippets/impl.sublime-snippet | 0 .../Rust/Snippets/let.sublime-snippet | 0 .../Rust/Snippets/loop.sublime-snippet | 0 .../Rust/Snippets/macro_rules.sublime-snippet | 0 .../Rust/Snippets/main.sublime-snippet | 0 .../Rust/Snippets/match.sublime-snippet | 0 .../Rust/Snippets/mod.sublime-snippet | 0 .../Rust/Snippets/panic.sublime-snippet | 0 .../Rust/Snippets/print.sublime-snippet | 0 .../Rust/Snippets/println.sublime-snippet | 0 .../Rust/Snippets/static.sublime-snippet | 0 .../Snippets/struct-tuple.sublime-snippet | 0 .../Rust/Snippets/struct-unit.sublime-snippet | 0 .../Rust/Snippets/struct.sublime-snippet | 0 .../Rust/Snippets/test.sublime-snippet | 0 .../Rust/Snippets/trait.sublime-snippet | 0 .../Rust/Snippets/type.sublime-snippet | 0 .../Rust/Snippets/while-let.sublime-snippet | 0 .../Rust/Snippets/while.sublime-snippet | 0 .../Packages/Rust/syntax_test_rust.rs | 0 .../Packages/SQL/Comments.tmPreferences | 0 .../Packages/SQL/Miscellaneous.tmPreferences | 0 .../syntaxes/Packages/SQL/SQL.sublime-syntax | 0 .../syntaxes/Packages/SQL/syntax_test_sql.sql | 0 .../Packages/Scala/Comments.tmPreferences | 0 .../Packages/Scala/Dedent-case.tmPreferences | 0 .../Packages/Scala/Indent-case.tmPreferences | 0 .../Packages/Scala/Indent.tmPreferences | 0 .../Packages/Scala/Scala.sublime-syntax | 0 .../Scala/Snippets/adt.sublime-snippet | 0 .../Scala/Snippets/app.sublime-snippet | 0 .../Scala/Snippets/case.sublime-snippet | 0 .../Scala/Snippets/cc.sublime-snippet | 0 .../Scala/Snippets/co.sublime-snippet | 0 .../Scala/Snippets/def.sublime-snippet | 0 .../Scala/Snippets/match.sublime-snippet | 0 .../Packages/Scala/Snippets/p.sublime-snippet | 0 .../Scala/Snippets/try.sublime-snippet | 0 .../Scala/Snippets/tryf.sublime-snippet | 0 .../Scala/Snippets/val.sublime-snippet | 0 .../Scala/Snippets/var.sublime-snippet | 0 .../Scala/Symbols-class.tmPreferences | 0 .../Packages/Scala/Symbols-def.tmPreferences | 0 .../Scala/Symbols-namespace.tmPreferences | 0 .../Packages/Scala/Symbols-type.tmPreferences | 0 .../Packages/Scala/Symbols-val.tmPreferences | 0 .../Packages/Scala/Symbols-var.tmPreferences | 0 .../Packages/Scala/Symbols.tmPreferences | 0 .../syntaxes/Packages/Scala/info.plist | 0 .../Packages/Scala/syntax_test_scala.scala | 0 .../Packages/ShellScript/Bash.sublime-syntax | 0 .../ShellScript/Comments.tmPreferences | 0 .../Completion Rules.tmPreferences | 0 .../ShellScript/Indentation.tmPreferences | 0 .../syntaxes/Packages/ShellScript/Makefile | 0 .../Shell-Unix-Generic.sublime-syntax | 0 .../Packages/ShellScript/ShellScript.py | 0 .../ShellScript/ShellScript.sublime-build | 0 .../#!-usr-bin-env-(!env).sublime-snippet | 0 .../case-..-esac-(case).sublime-snippet | 0 .../Snippets/elif-..-(elif).sublime-snippet | 0 .../for-...-done-(for).sublime-snippet | 0 .../for-in-done-(forin).sublime-snippet | 0 .../Snippets/if-...-then-(if).sublime-snippet | 0 .../Snippets/until-(done).sublime-snippet | 0 .../Snippets/while-(done).sublime-snippet | 0 .../Symbol List - Aliases.tmPreferences | 0 .../Symbol List - Expansions.tmPreferences | 0 .../Symbol List - Functions.tmPreferences | 0 .../Symbol List - Variables.tmPreferences | 0 ...commands-builtin-shell-bash.sublime-syntax | 0 .../commands-builtin-shell-bash.yml | 0 .../ShellScript/test/syntax_test_bash.sh | 0 .../test/syntax_test_shell_unix_generic.sh | 0 .../ShellScript/tools/update-commands.py | 0 .../Packages/TCL/Comments.tmPreferences | 0 .../Packages/TCL/HTML (Tcl).sublime-syntax | 0 .../TCL/Snippets/for...-(for).sublime-snippet | 0 .../foreach...-(foreach).sublime-snippet | 0 .../TCL/Snippets/if...-(if).sublime-snippet | 0 .../Snippets/proc...-(proc).sublime-snippet | 0 .../switch...-(switch).sublime-snippet | 0 .../Snippets/while...-(while).sublime-snippet | 0 .../Symbol List Indent NS Proc.tmPreferences | 0 .../Packages/TCL/Symbol List.tmPreferences | 0 .../syntaxes/Packages/TCL/Tcl.sublime-syntax | 0 .../syntaxes/Packages/TCL/syntax_test_tcl.tcl | 0 .../Packages/Text/Plain text.tmLanguage | 0 .../Text/Snippets/lorem.sublime-snippet | 0 .../Textile/Snippets/Acronym.sublime-snippet | 0 .../Snippets/Block-Quotes.sublime-snippet | 0 .../Snippets/Heading-1.sublime-snippet | 0 .../Snippets/Heading-2.sublime-snippet | 0 .../Snippets/Heading-3.sublime-snippet | 0 .../Snippets/Heading-4.sublime-snippet | 0 .../Snippets/Heading-5.sublime-snippet | 0 .../Snippets/Heading-6.sublime-snippet | 0 .../Textile/Snippets/Image.sublime-snippet | 0 .../Snippets/Linked-Image.sublime-snippet | 0 .../Packages/Textile/Textile.sublime-syntax | 0 .../Textile/syntax_test_textile.textile | 0 .../Packages/XML/Comments.tmPreferences | 0 .../Packages/XML/Miscellaneous.tmPreferences | 0 .../XML/Snippets/xml-cdata.sublime-snippet | 0 .../Snippets/xml-declaration.sublime-snippet | 0 .../XML/Snippets/xml-long-tag.sublime-snippet | 0 .../XML/Snippets/xml-model.sublime-snippet | 0 .../Snippets/xml-short-tag.sublime-snippet | 0 .../Snippets/xml-stylesheet.sublime-snippet | 0 .../Packages/XML/XML.sublime-settings | 0 .../syntaxes/Packages/XML/XML.sublime-syntax | 0 .../syntaxes/Packages/XML/syntax_test_xml.xml | 0 .../Packages/YAML/Comments.tmPreferences | 0 .../YAML/Indentation Rules.tmPreferences | 0 .../Packages/YAML/Symbol List.tmPreferences | 0 .../Packages/YAML/YAML.sublime-settings | 0 .../Packages/YAML/YAML.sublime-syntax | 0 .../syntaxes/Packages/YAML/preview.yaml | 0 .../YAML/tests/syntax_test_block.yaml | 0 .../YAML/tests/syntax_test_directives.yaml | 0 .../YAML/tests/syntax_test_flow-plain.yaml | 0 .../Packages/YAML/tests/syntax_test_flow.yaml | 0 .../YAML/tests/syntax_test_general.yaml | 0 .../YAML/tests/syntax_test_properties.yaml | 0 .../YAML/tests/syntax_test_types.yaml | 0 build.rs => crates/docs_rs_web/build.rs | 0 .../docs_rs_web/src}/build_details.rs | 21 +- {src/web => crates/docs_rs_web/src}/builds.rs | 0 {src/web => crates/docs_rs_web/src}/cache.rs | 0 .../docs_rs_web/src}/crate_details.rs | 0 {src/web => crates/docs_rs_web/src}/csp.rs | 0 {src/web => crates/docs_rs_web/src}/error.rs | 0 .../docs_rs_web/src}/extractors/context.rs | 0 .../docs_rs_web/src}/extractors/mod.rs | 0 .../docs_rs_web/src}/extractors/path.rs | 0 .../docs_rs_web/src}/extractors/rustdoc.rs | 0 .../docs_rs_web/src}/features.rs | 0 {src/web => crates/docs_rs_web/src}/file.rs | 0 .../docs_rs_web/src}/highlight.rs | 0 .../mod.rs => crates/docs_rs_web/src/lib.rs | 16 +- .../docs_rs_web/src}/licenses.rs | 0 .../docs_rs_web/src}/markdown.rs | 0 .../web => crates/docs_rs_web/src}/metrics.rs | 13 +- .../docs_rs_web/src}/page/mod.rs | 0 .../docs_rs_web/src}/page/templates.rs | 3 +- .../docs_rs_web/src}/page/web_page.rs | 3 +- .../docs_rs_web/src}/releases.rs | 0 {src/web => crates/docs_rs_web/src}/routes.rs | 0 .../web => crates/docs_rs_web/src}/rustdoc.rs | 0 .../web => crates/docs_rs_web/src}/sitemap.rs | 0 {src/web => crates/docs_rs_web/src}/source.rs | 0 .../web => crates/docs_rs_web/src}/statics.rs | 0 {src/web => crates/docs_rs_web/src}/status.rs | 0 .../docs_rs_web/static}/FiraSans-LICENSE.txt | 0 .../docs_rs_web/static}/FiraSans-Medium.woff | Bin .../docs_rs_web/static}/FiraSans-Medium.woff2 | Bin .../docs_rs_web/static}/FiraSans-Regular.woff | Bin .../static}/FiraSans-Regular.woff2 | Bin .../static}/SourceCodePro-It.ttf.woff | Bin .../static}/SourceCodePro-It.ttf.woff2 | Bin .../static}/SourceCodePro-LICENSE.md | 0 .../static}/SourceCodePro-Regular.ttf.woff | Bin .../static}/SourceCodePro-Regular.ttf.woff2 | Bin .../static}/SourceCodePro-Semibold.ttf.woff | Bin .../static}/SourceCodePro-Semibold.ttf.woff2 | Bin .../static}/SourceSerif4-Bold.ttf.woff | Bin .../static}/SourceSerif4-Bold.ttf.woff2 | Bin .../static}/SourceSerif4-It.ttf.woff | Bin .../static}/SourceSerif4-It.ttf.woff2 | Bin .../static}/SourceSerif4-LICENSE.md | 0 .../static}/SourceSerif4-Regular.ttf.woff | Bin .../static}/SourceSerif4-Regular.ttf.woff2 | Bin .../docs_rs_web/static}/clipboard.svg | 0 .../docs_rs_web/static}/fa-brands-400.ttf | Bin .../docs_rs_web/static}/fa-brands-400.woff2 | Bin .../docs_rs_web/static}/fa-regular-400.ttf | Bin .../docs_rs_web/static}/fa-regular-400.woff2 | Bin .../docs_rs_web/static}/fa-solid-900.ttf | Bin .../docs_rs_web/static}/fa-solid-900.woff2 | Bin .../static}/fa-v4compatibility.ttf | Bin .../static}/fa-v4compatibility.woff2 | Bin .../docs_rs_web/static}/favicon.ico | Bin .../docs_rs_web/static}/index.js | 0 .../docs_rs_web/static}/keyboard.js | 0 {static => crates/docs_rs_web/static}/menu.js | 0 .../docs_rs_web/static}/opensearch.xml | 0 .../docs_rs_web/static}/robots.txt | 0 .../docs_rs_web/static}/source.js | 0 .../docs_rs_web/static}/trigger-rebuild.png | Bin .../docs_rs_web/templates}/about-base.html | 0 .../docs_rs_web/templates}/base.html | 0 .../templates}/core/Cargo.toml.example | 0 .../templates}/core/about/badges.html | 0 .../templates}/core/about/builds.html | 0 .../templates}/core/about/download.html | 0 .../templates}/core/about/index.html | 0 .../templates}/core/about/metadata.html | 0 .../templates}/core/about/redirections.html | 0 .../templates}/core/about/rustdoc-json.html | 0 .../docs_rs_web/templates}/core/home.html | 0 .../templates}/core/sitemap/_item.xml | 0 .../templates}/core/sitemap/index.xml | 0 .../templates}/crate/build_details.html | 0 .../docs_rs_web/templates}/crate/builds.html | 0 .../docs_rs_web/templates}/crate/details.html | 0 .../templates}/crate/features.html | 0 .../docs_rs_web/templates}/crate/source.html | 0 .../docs_rs_web/templates}/error.html | 0 .../templates}/header/global_alert.html | 0 .../templates}/header/package_navigation.html | 0 .../docs_rs_web/templates}/header/topbar.html | 0 .../templates}/header/topbar_begin.html | 0 .../templates}/header/topbar_end.html | 0 .../docs_rs_web/templates}/macros.html | 0 .../templates}/releases/activity.html | 0 .../templates}/releases/build_queue.html | 0 .../docs_rs_web/templates}/releases/feed.xml | 0 .../templates}/releases/header.html | 0 .../templates}/releases/releases.html | 0 .../templates}/releases/search_results.html | 0 .../docs_rs_web/templates}/rustdoc/body.html | 0 .../docs_rs_web/templates}/rustdoc/head.html | 0 .../templates}/rustdoc/platforms.html | 0 .../templates}/rustdoc/releases.html | 0 .../templates}/rustdoc/topbar.html | 0 .../templates}/rustdoc/vendored.html | 0 .../templates}/storage-change-detection.html | 0 .../docs_rs_web/templates}/style/_navbar.scss | 0 .../templates}/style/_rustdoc-common.scss | 0 .../templates}/style/_syntax-themes.scss | 0 .../docs_rs_web/templates}/style/_syntax.scss | 0 .../docs_rs_web/templates}/style/_themes.scss | 0 .../docs_rs_web/templates}/style/_utils.scss | 0 .../docs_rs_web/templates}/style/_vars.scss | 0 .../templates}/style/rustdoc-2021-12-05.scss | 0 .../templates}/style/rustdoc-2025-08-20.scss | 0 .../docs_rs_web/templates}/style/rustdoc.scss | 0 .../docs_rs_web/templates}/style/style.scss | 0 .../docs_rs_web/templates}/theme.js | 0 .../docs_rs_web/vendor}/chartjs/LICENSE | 18 +- .../docs_rs_web/vendor}/chartjs/chart.min.js | 0 .../docs_rs_web/vendor}/pure-css/LICENSE | 0 src/db/types/mod.rs | 63 - src/metrics/mod.rs | 12 - src/utils/daemon.rs | 140 -- static/ayu-highlight.css | 79 -- static/font-awesome.css | 31 - vendor/chartjs/chart.min.css | 1 - vendor/pure-css/css/grids-responsive-min.css | 7 - vendor/pure-css/css/pure-min.css | 11 - 1508 files changed, 182 insertions(+), 1606 deletions(-) delete mode 100644 assets/syntaxes/Packages/CSS/.python-version delete mode 100644 assets/syntaxes/Packages/CSS/syntax_test_css.css delete mode 100644 assets/syntaxes/Packages/Diff/.python-version delete mode 100644 assets/syntaxes/Packages/HTML/.python-version delete mode 100644 assets/syntaxes/Packages/ShellScript/.python-version rename src/db/types/dependencies.rs => crates/docs_rs_cargo_metadata/src/db.rs (100%) create mode 100644 crates/docs_rs_database/build.rs rename {migrations => crates/docs_rs_database/migrations}/20231021111635_initial.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20231021111635_initial.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221104457_drop_releases_build_status.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221104457_drop_releases_build_status.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221113734_drop_releases_rustc_version.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221113734_drop_releases_rustc_version.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221114302_ensure_no_buildless_releases_exist.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221114302_ensure_no_buildless_releases_exist.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221124844_multi_stage_build_status.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240221124844_multi_stage_build_status.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240227040753_add_owner_kind.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240227040753_add_owner_kind.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240309082057_release_status_view.sql.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240309082057_release_status_view.sql.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240311202914_release_status_materialized.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240311202914_release_status_materialized.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240313103708_make_release_fields_optional.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240313103708_make_release_fields_optional.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240313182623_make_build_fields_optional.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240313182623_make_build_fields_optional.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240313184911_build_errors.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240313184911_build_errors.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240519141105_crate-version-name-field-length.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240519141105_crate-version-name-field-length.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240624085737_build-status-idx.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20240624085737_build-status-idx.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241018031600_documentation_size.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241018031600_documentation_size.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241018052241_builds-rustc-nightly-date.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241018052241_builds-rustc-nightly-date.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241021050229_builds-started-finished.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241021050229_builds-started-finished.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241106085600_releases-rustdoc-status-idx.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241106085600_releases-rustdoc-status-idx.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241219091521_owner-avatar-longer.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20241219091521_owner-avatar-longer.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20251202020754_remove-file-public.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20251202020754_remove-file-public.up.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20251202040858_remove-cdn-invalidation-queue.down.sql (100%) rename {migrations => crates/docs_rs_database/migrations}/20251202040858_remove-cdn-invalidation-queue.up.sql (100%) rename {benches => crates/docs_rs_storage/benches}/compression.rs (100%) rename {benches => crates/docs_rs_storage/benches}/struct.CaptureMatches.html (100%) create mode 100644 crates/docs_rs_web/Cargo.toml rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/JavaScript (Babel).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/.gitignore (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/Comments.YAML-tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/LICENSE (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/README.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/TOML.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Extras/TOML/syntax_test_toml.toml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/.travis.yml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/ASP.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ASP/syntax_test_asp.asp (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ActionScript/ActionScript.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ActionScript/syntax_test_as.as (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/AppleScript/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Batch File/Batch File.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Batch File/Batch File.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Batch File/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Batch File/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Batch File/syntax_test_batch_file.bat (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Build.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/C#.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Indentation.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Enums.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Region.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/Symbol List Structs.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/doc_params.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/doc_see.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/doc_summary.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_C#7.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_Comments.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_Generics.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_Operators.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_Strings.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_Using.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_c#.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C#/tests/syntax_test_query.cs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C Single File.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C Standard Includes.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C++ Single File.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C++.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C++.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/C.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Comments (C++).tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/forv.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/struct.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol Index.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/syntax_test_accessor.c (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/syntax_test_accessor.cpp (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/syntax_test_c.c (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/C++/syntax_test_cpp.cpp (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/CSS.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/Symbol Index.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/Symbol List Group.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/CSS/css_completions.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/Clojure.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/Clojure.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/Comment.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/D dub.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/D.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/D.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/DMD Output.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/constant.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/critical.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/debugm.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/enum.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/error-format.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/error.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/fatal.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/foreach.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/if-else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/import.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/info.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/log-format.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/log.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/main.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/return.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/struct.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/trace.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/unittest.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/version.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/warning.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Snippets/while.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/tests/syntax_test_d.d (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/tests/syntax_test_old.d (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/D/tests/syntax_test_shebang.d (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Diff/Context.sublime-menu (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Diff/Diff.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Diff/Side Bar.sublime-menu (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Diff/diff.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Diff/syntax_test_diff.diff (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Erlang.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Erlang.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Erlang.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/syntax_test_erlang.erl (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Erlang/syntax_test_erlang.yaws (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Common.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Config.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Link.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Log.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Log.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_attributes (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_commit (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_config (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_ignore (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_link (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_log (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_mailmap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_merge (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_rebase (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Git Formats/syntax_test_git_tag (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Go.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Go.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Go.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/GoCommentRules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Go/syntax_test_go.go (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/DOT.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Graphviz.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Graphviz/syntax_test_dot.dot (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Groovy.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/syntax_test_groovy.groovy (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/HTML.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/Snippets/html.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/encode_html_entities.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/html_completions.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/HTML/syntax_test_html.html (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Haskell.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Haskell.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Haskell/syntax_test_haskell.hs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JSON/JSON Indent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JSON/JSON.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JSON/syntax_test_json.json (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Ant.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Comments - Properties.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Java.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Java.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/JavaC.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/JavaDoc.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/JavaProperties.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/assert.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/break.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/catch.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/constant.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/default.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/final.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/for.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/import.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/interface.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/package.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/print.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/println.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/private.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/protected.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/public.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/return.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/static.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/switch.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/test.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/throw.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/variable.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Snippets/while.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Method.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/syntax_test_java.java (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/syntax_test_java_properties.properties (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Java/syntax_test_jsp.jsp (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LICENSE (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/LaTeX.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/TeX.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/LaTeX/syntax_test_latex.tex (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Lisp.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lisp/syntax_test_lisp.lisp (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Indent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Lua.sublime-build (96%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Lua.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/tests/syntax_test_lua.lua (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/Make Output.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/Make.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/Makefile.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/Makefile.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Makefile/syntax_test_makefile.mak (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/Markdown.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/syntax_test_markdown.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Indent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Matlab.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/Symbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Matlab/syntax_test_matlab.m (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Indent rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/OCaml.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/OCamllex.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/camlp4.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/OCaml/syntax_test_ml.ml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/syntax_test_accessor.m (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/syntax_test_accessor.mm (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/syntax_test_objc++.mm (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Objective-C/syntax_test_objc.m (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/PHP Source.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/PHP.sublime-completions (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/PHP.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/php.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/PHP/syntax_test_php.php (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Pascal/Pascal.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Pascal/syntax_test.pas (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Perl.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Perl.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Perl/syntax_test_perl.pl (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Python.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Python.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/for.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/method.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Snippets/while.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Symbol Index.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/syntax_test_python.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Python/syntax_test_python_strings.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/R Console.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/R.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/R.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/R.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Attach.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Density.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Detach.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Factor.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Function.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Length.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Sort.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/Source.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Symbol List - Methods.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/Symbol List - Sections.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/R/syntax_test_r.R (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/README.md (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Template (ERB).tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/Template (Haml).tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rails/syntax_test_rails.rb (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Regular Expressions/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/RestructuredText/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Ruby.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Ruby.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Ruby/syntax_test_ruby.rb (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Cargo.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Cargo.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Default.sublime-keymap (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/LICENSE.txt (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Rust.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Rust.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/RustComment.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/RustIndent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/RustSymbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/const.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/else.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/for.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/if.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/let.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/main.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/match.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/print.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/println.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/static.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/test.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/type.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/Snippets/while.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Rust/syntax_test_rust.rs (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/SQL/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/SQL/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/SQL/SQL.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/SQL/syntax_test_sql.sql (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Dedent-case.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Indent-case.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Indent.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Scala.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/app.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/case.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/co.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/def.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/match.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/p.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/try.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/val.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Snippets/var.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols-class.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols-def.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols-type.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols-val.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols-var.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/Symbols.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/info.plist (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Scala/syntax_test_scala.scala (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Bash.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Indentation.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Makefile (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/ShellScript.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/ShellScript.sublime-build (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/ShellScript/tools/update-commands.py (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/Tcl.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/TCL/syntax_test_tcl.tcl (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Text/Plain text.tmLanguage (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/Textile.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/Textile/syntax_test_textile.textile (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Miscellaneous.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/XML.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/XML.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/XML/syntax_test_xml.xml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/Comments.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/Indentation Rules.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/Symbol List.tmPreferences (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/YAML.sublime-settings (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/YAML.sublime-syntax (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/preview.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_block.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_general.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml (100%) rename {assets => crates/docs_rs_web/assets}/syntaxes/Packages/YAML/tests/syntax_test_types.yaml (100%) rename build.rs => crates/docs_rs_web/build.rs (100%) rename {src/web => crates/docs_rs_web/src}/build_details.rs (97%) rename {src/web => crates/docs_rs_web/src}/builds.rs (100%) rename {src/web => crates/docs_rs_web/src}/cache.rs (100%) rename {src/web => crates/docs_rs_web/src}/crate_details.rs (100%) rename {src/web => crates/docs_rs_web/src}/csp.rs (100%) rename {src/web => crates/docs_rs_web/src}/error.rs (100%) rename {src/web => crates/docs_rs_web/src}/extractors/context.rs (100%) rename {src/web => crates/docs_rs_web/src}/extractors/mod.rs (100%) rename {src/web => crates/docs_rs_web/src}/extractors/path.rs (100%) rename {src/web => crates/docs_rs_web/src}/extractors/rustdoc.rs (100%) rename {src/web => crates/docs_rs_web/src}/features.rs (100%) rename {src/web => crates/docs_rs_web/src}/file.rs (100%) rename {src/web => crates/docs_rs_web/src}/highlight.rs (100%) rename src/web/mod.rs => crates/docs_rs_web/src/lib.rs (99%) rename {src/web => crates/docs_rs_web/src}/licenses.rs (100%) rename {src/web => crates/docs_rs_web/src}/markdown.rs (100%) rename {src/web => crates/docs_rs_web/src}/metrics.rs (94%) rename {src/web => crates/docs_rs_web/src}/page/mod.rs (100%) rename {src/web => crates/docs_rs_web/src}/page/templates.rs (99%) rename {src/web => crates/docs_rs_web/src}/page/web_page.rs (98%) rename {src/web => crates/docs_rs_web/src}/releases.rs (100%) rename {src/web => crates/docs_rs_web/src}/routes.rs (100%) rename {src/web => crates/docs_rs_web/src}/rustdoc.rs (100%) rename {src/web => crates/docs_rs_web/src}/sitemap.rs (100%) rename {src/web => crates/docs_rs_web/src}/source.rs (100%) rename {src/web => crates/docs_rs_web/src}/statics.rs (100%) rename {src/web => crates/docs_rs_web/src}/status.rs (100%) rename {static => crates/docs_rs_web/static}/FiraSans-LICENSE.txt (100%) rename {static => crates/docs_rs_web/static}/FiraSans-Medium.woff (100%) rename {static => crates/docs_rs_web/static}/FiraSans-Medium.woff2 (100%) rename {static => crates/docs_rs_web/static}/FiraSans-Regular.woff (100%) rename {static => crates/docs_rs_web/static}/FiraSans-Regular.woff2 (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-It.ttf.woff (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-It.ttf.woff2 (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-LICENSE.md (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-Regular.ttf.woff (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-Regular.ttf.woff2 (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-Semibold.ttf.woff (100%) rename {static => crates/docs_rs_web/static}/SourceCodePro-Semibold.ttf.woff2 (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-Bold.ttf.woff (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-Bold.ttf.woff2 (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-It.ttf.woff (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-It.ttf.woff2 (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-LICENSE.md (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-Regular.ttf.woff (100%) rename {static => crates/docs_rs_web/static}/SourceSerif4-Regular.ttf.woff2 (100%) rename {static => crates/docs_rs_web/static}/clipboard.svg (100%) rename {static => crates/docs_rs_web/static}/fa-brands-400.ttf (100%) rename {static => crates/docs_rs_web/static}/fa-brands-400.woff2 (100%) rename {static => crates/docs_rs_web/static}/fa-regular-400.ttf (100%) rename {static => crates/docs_rs_web/static}/fa-regular-400.woff2 (100%) rename {static => crates/docs_rs_web/static}/fa-solid-900.ttf (100%) rename {static => crates/docs_rs_web/static}/fa-solid-900.woff2 (100%) rename {static => crates/docs_rs_web/static}/fa-v4compatibility.ttf (100%) rename {static => crates/docs_rs_web/static}/fa-v4compatibility.woff2 (100%) rename {static => crates/docs_rs_web/static}/favicon.ico (100%) rename {static => crates/docs_rs_web/static}/index.js (100%) rename {static => crates/docs_rs_web/static}/keyboard.js (100%) rename {static => crates/docs_rs_web/static}/menu.js (100%) rename {static => crates/docs_rs_web/static}/opensearch.xml (100%) rename {static => crates/docs_rs_web/static}/robots.txt (100%) rename {static => crates/docs_rs_web/static}/source.js (100%) rename {static => crates/docs_rs_web/static}/trigger-rebuild.png (100%) rename {templates => crates/docs_rs_web/templates}/about-base.html (100%) rename {templates => crates/docs_rs_web/templates}/base.html (100%) rename {templates => crates/docs_rs_web/templates}/core/Cargo.toml.example (100%) rename {templates => crates/docs_rs_web/templates}/core/about/badges.html (100%) rename {templates => crates/docs_rs_web/templates}/core/about/builds.html (100%) rename {templates => crates/docs_rs_web/templates}/core/about/download.html (100%) rename {templates => crates/docs_rs_web/templates}/core/about/index.html (100%) rename {templates => crates/docs_rs_web/templates}/core/about/metadata.html (100%) rename {templates => crates/docs_rs_web/templates}/core/about/redirections.html (100%) rename {templates => crates/docs_rs_web/templates}/core/about/rustdoc-json.html (100%) rename {templates => crates/docs_rs_web/templates}/core/home.html (100%) rename {templates => crates/docs_rs_web/templates}/core/sitemap/_item.xml (100%) rename {templates => crates/docs_rs_web/templates}/core/sitemap/index.xml (100%) rename {templates => crates/docs_rs_web/templates}/crate/build_details.html (100%) rename {templates => crates/docs_rs_web/templates}/crate/builds.html (100%) rename {templates => crates/docs_rs_web/templates}/crate/details.html (100%) rename {templates => crates/docs_rs_web/templates}/crate/features.html (100%) rename {templates => crates/docs_rs_web/templates}/crate/source.html (100%) rename {templates => crates/docs_rs_web/templates}/error.html (100%) rename {templates => crates/docs_rs_web/templates}/header/global_alert.html (100%) rename {templates => crates/docs_rs_web/templates}/header/package_navigation.html (100%) rename {templates => crates/docs_rs_web/templates}/header/topbar.html (100%) rename {templates => crates/docs_rs_web/templates}/header/topbar_begin.html (100%) rename {templates => crates/docs_rs_web/templates}/header/topbar_end.html (100%) rename {templates => crates/docs_rs_web/templates}/macros.html (100%) rename {templates => crates/docs_rs_web/templates}/releases/activity.html (100%) rename {templates => crates/docs_rs_web/templates}/releases/build_queue.html (100%) rename {templates => crates/docs_rs_web/templates}/releases/feed.xml (100%) rename {templates => crates/docs_rs_web/templates}/releases/header.html (100%) rename {templates => crates/docs_rs_web/templates}/releases/releases.html (100%) rename {templates => crates/docs_rs_web/templates}/releases/search_results.html (100%) rename {templates => crates/docs_rs_web/templates}/rustdoc/body.html (100%) rename {templates => crates/docs_rs_web/templates}/rustdoc/head.html (100%) rename {templates => crates/docs_rs_web/templates}/rustdoc/platforms.html (100%) rename {templates => crates/docs_rs_web/templates}/rustdoc/releases.html (100%) rename {templates => crates/docs_rs_web/templates}/rustdoc/topbar.html (100%) rename {templates => crates/docs_rs_web/templates}/rustdoc/vendored.html (100%) rename {templates => crates/docs_rs_web/templates}/storage-change-detection.html (100%) rename {templates => crates/docs_rs_web/templates}/style/_navbar.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/_rustdoc-common.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/_syntax-themes.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/_syntax.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/_themes.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/_utils.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/_vars.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/rustdoc-2021-12-05.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/rustdoc-2025-08-20.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/rustdoc.scss (100%) rename {templates => crates/docs_rs_web/templates}/style/style.scss (100%) rename {templates => crates/docs_rs_web/templates}/theme.js (100%) rename {vendor => crates/docs_rs_web/vendor}/chartjs/LICENSE (99%) rename {vendor => crates/docs_rs_web/vendor}/chartjs/chart.min.js (100%) rename {vendor => crates/docs_rs_web/vendor}/pure-css/LICENSE (100%) delete mode 100644 src/db/types/mod.rs delete mode 100644 src/utils/daemon.rs delete mode 100644 static/ayu-highlight.css delete mode 100644 static/font-awesome.css delete mode 100644 vendor/chartjs/chart.min.css delete mode 100644 vendor/pure-css/css/grids-responsive-min.css delete mode 100644 vendor/pure-css/css/pure-min.css diff --git a/Cargo.lock b/Cargo.lock index 400e05446..af9d0ebde 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1877,17 +1877,13 @@ name = "docs-rs" version = "0.6.0" dependencies = [ "anyhow", - "askama", "async-stream", "aws-smithy-runtime", "aws-smithy-types", - "axum", - "axum-extra", "base64 0.22.1", "bincode 2.0.1", "chrono", "clap", - "comrak", "constant_time_eq", "criterion", "derive_more 2.0.1", @@ -1907,7 +1903,6 @@ dependencies = [ "font-awesome-as-a-crate", "futures-util", "getrandom 0.3.4", - "grass", "hex", "hostname", "http 1.4.0", @@ -1917,7 +1912,6 @@ dependencies = [ "kuchikiki", "log", "lol_html", - "md5", "mime", "mockito", "num_cpus", @@ -1926,8 +1920,6 @@ dependencies = [ "opentelemetry-resource-detectors", "opentelemetry_sdk", "path-slash", - "phf 0.13.1", - "phf_codegen 0.13.1", "pretty_assertions", "rand 0.9.2", "rayon", @@ -1942,7 +1934,6 @@ dependencies = [ "slug", "sqlx", "strum", - "syntect", "sysinfo", "tempfile", "test-case", @@ -2190,6 +2181,34 @@ dependencies = [ "url", ] +[[package]] +name = "docs_rs_web" +version = "0.1.0" +dependencies = [ + "anyhow", + "askama", + "axum", + "axum-extra", + "chrono", + "comrak", + "docs_rs_build_queue", + "docs_rs_database", + "docs_rs_opentelemetry", + "docs_rs_storage", + "docs_rs_utils", + "futures-util", + "grass", + "http 1.4.0", + "md5", + "phf 0.13.1", + "phf_codegen 0.13.1", + "serde", + "serde_json", + "syntect", + "tracing", + "walkdir", +] + [[package]] name = "docs_rs_web_utils" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c916462b6..b156d2861 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ authors = ["Onur Aslan ", "The Rust Project Developers"] readme = "README.md" license = "MIT" repository = "https://github.com/rust-lang/docs.rs" -build = "build.rs" edition = "2024" [workspace] @@ -52,15 +51,11 @@ mockito = "1.0.2" [dependencies] anyhow = { workspace = true } -askama = { workspace = true } async-stream = { workspace = true } -axum = { version = "0.8.1", features = ["macros"] } -axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middleware"] } base64 = "0.22" bincode = { workspace = true } chrono = { workspace = true } clap = { workspace = true } -comrak = { version = "0.48.0", default-features = false } constant_time_eq = "0.4.2" derive_more = { workspace = true } docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } @@ -92,7 +87,6 @@ opentelemetry-otlp = { workspace = true } opentelemetry-resource-detectors = { workspace = true } opentelemetry_sdk = { workspace = true } path-slash = "0.2.0" -phf = "0.13.1" rayon = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } @@ -105,7 +99,6 @@ serde_with = { workspace = true } slug = "0.1.1" sqlx = { workspace = true } strum = { workspace = true } -syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } tempfile = { workspace = true } thiserror = { workspace = true } @@ -133,18 +126,3 @@ pretty_assertions = "1.4.0" rand = "0.9" test-case = { workspace = true } tower = { version = "0.5.1", features = ["util"] } - -[build-dependencies] -anyhow = { version = "1.0.42", features = ["backtrace"] } -grass = { version = "0.13.1", default-features = false } -md5 = "0.8.0" -phf_codegen = "0.13" -syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } -walkdir = "2" - -[package.metadata.cargo-machete] -ignored = ["phf"] - -[[bench]] -name = "compression" -harness = false diff --git a/assets/syntaxes/Packages/CSS/.python-version b/assets/syntaxes/Packages/CSS/.python-version deleted file mode 100644 index 3767b4b17..000000000 --- a/assets/syntaxes/Packages/CSS/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.14 \ No newline at end of file diff --git a/assets/syntaxes/Packages/CSS/syntax_test_css.css b/assets/syntaxes/Packages/CSS/syntax_test_css.css deleted file mode 100644 index 1a80aa677..000000000 --- a/assets/syntaxes/Packages/CSS/syntax_test_css.css +++ /dev/null @@ -1,1186 +0,0 @@ -/* SYNTAX TEST "Packages/CSS/CSS.sublime-syntax" */ - - /* What am I, a comment? */ -/* ^^ punctuation.definition.comment.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^ comment.block.css */ -/* ^^ punctuation.definition.comment.css */ - - /** - * -/* ^ comment.block.css punctuation.definition.comment.css */ - -.test-strings { - content: "double"; -/* ^^^^^^^^ string.quoted.double.css */ -/* ^ punctuation.definition.string.begin.css */ -/* ^ punctuation.definition.string.end.css */ - content: 'single'; -/* ^^^^^^^^ string.quoted.single.css */ -/* ^ punctuation.definition.string.begin.css */ -/* ^ punctuation.definition.string.end.css */ - - content: 'invalid; -/* ^ invalid.illegal.newline.css */ -} - -.test-punctuation { -/* ^ punctuation.section.property-list.css */ - top: 1px; -/* ^ punctuation.separator.key-value.css */ -/* ^ punctuation.terminator.rule.css */ - - top: cubic-bezier(0.2, 0, 0.13, 2); -/* ^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -/* ^ punctuation.separator.sequence.css*/ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css*/ -/* ^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ - - top: url("image"); -/* ^ punctuation.definition.group.begin.css */ -/* ^ punctuation.definition.group.end.css */ -} -/* < punctuation.section.property-list.css */ - - .classname {} -/*^^ - meta.selector.css */ -/* ^^^^^^^^^^^ meta.selector.css */ -/* ^^ - meta.selector.css */ -/* ^ punctuation.definition.entity.css */ -/* ^^^^^^^^^ entity.other.attribute-name.class.css */ - - #id {} -/*^^ - meta.selector.css */ -/* ^^^^ meta.selector.css */ -/* ^^ - meta.selector.css */ -/* ^ punctuation.definition.entity.css */ -/* ^^ entity.other.attribute-name.id.css */ - - html, h1 {} -/*^^ - meta.selector.css */ -/* ^^^^^^^^^ meta.selector.css */ -/* ^^ - meta.selector.css */ -/* ^^^^ entity.name.tag.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^ entity.name.tag.css */ -/* ^ punctuation.section.property-list.css */ -/* ^ punctuation.section.property-list.css */ - - @charset "UTF-8"; -/* ^^^^^^^^^^^^^^^^ meta.at-rule.charset.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^ keyword.control.at-rule.charset.css */ - - @import "x" print; -/* ^^^^^^^^^^^^^^^^^ meta.at-rule.import.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^ keyword.control.at-rule.import.css */ -/* ^^^^^ support.constant.media.css */ - - @namespace svg "http://www.w3.org/1999/xhtml"; -/* ^^^^^^^^^^^^^^^^^ meta.at-rule.namespace.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^ keyword.control.at-rule.namespace.css */ -/* ^^^ entity.other.namespace-prefix.css */ - - @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"); /* only needed once */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.namespace.css */ -/* ^^^ support.function.url.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.function-call.css */ -/* ^ meta.group.css punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.group.css string.quoted.double.css */ -/* ^ meta.group.css punctuation.definition.group.end.css */ - - @page :left {} -/* ^^^^^^^^^^^^^^ meta.at-rule.page.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^ keyword.control.at-rule.page.css */ - - @media only screen {} -/* ^^^^^^^^^^^^^^^^^^^ meta.at-rule.media.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^ keyword.control.at-rule.media.css */ -/* ^^^^ keyword.operator.logic.media.css */ -/* ^^^^^^ support.constant.media.css */ - - @media (min-width: 700px) {} -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.media.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^ keyword.control.at-rule.media.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^ support.type.property-name.media.css */ -/* ^^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ - - @media (min-width: 700px) and (max-width: 2000px) {} -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.media.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^ keyword.control.at-rule.media.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^ support.type.property-name.media.css */ -/* ^^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^^^ keyword.operator.logic */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^ support.type.property-name.media.css */ -/* ^^^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ - - @media only screen and (-webkit-min-device-pixel-ratio: /* comment */ 1.3), -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.media.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^ keyword.control.at-rule.media.css */ -/* ^^^^ keyword.operator.logic.media.css */ -/* ^^^^^^ support.constant.media.css */ -/* ^^^ keyword.operator.logic.media.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ support.type.property-name.media.css */ -/* ^ punctuation.separator.key-value.css */ -/* ^^^^^^^^^^^^^ comment.block.css */ -/* ^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^ punctuation.definition.arbitrary-repetition */ - only screen and (-o-min-device-pixel-ratio: 13/10), -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.media.css */ -/* ^^^^ keyword.operator.logic.media.css */ -/* ^^^^^^ support.constant.media.css */ -/* ^^^ keyword.operator.logic.media.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^ support.type.property-name.media.css */ -/* ^ punctuation.separator.key-value.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.operator */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^ punctuation.definition.arbitrary-repetition */ - only screen and (min-resolution: 120dpi) -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.media.css */ -/* ^^^^ keyword.operator.logic.media.css */ -/* ^^^^^^ support.constant.media.css */ -/* ^^^ keyword.operator.logic.media.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^^^^^ support.type.property-name.media.css */ -/* ^ punctuation.separator.key-value.css */ -/* ^^^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ -{} - - @custom-media --a-b (width: 1px); -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.custom-media.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^^^^ keyword.control.at-rule.custom-media.css */ -/* ^^^^^ support.type.property-name.media.css */ - - @keyframes beat, bounce {} -/* ^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.keyframe.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^ keyword.control.at-rule.keyframe.css */ -/* ^^^^ entity.other.animation-name.css */ -/* ^ punctuation.definition.arbitrary-repetition.css */ -/* ^^^^^^ entity.other.animation-name.css */ - -@keyframes test-keyframes-keywords { - from, to {} -/* ^^^^ keyword.keyframe-selector.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^ keyword.keyframe-selector.css */ - - 0%, 100% {} -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.other.unit.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.other.unit.css */ - - .99%, 100.99% {} -/* ^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -/* ^ keyword.other.unit.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -/* ^ keyword.other.unit.css */ - - 0%, to {} -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.other.unit.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^ keyword.keyframe-selector.css */ -} - - @document url(http://) { } -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.document.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^ keyword.control.at-rule.document.css */ -/* ^^^ meta.block.css */ -/* ^ punctuation.definition.block.begin.css */ -/* ^ punctuation.definition.block.end.css */ - -@document url(http://www), -/* ^^^ support.function.url.css */ -/* ^^^^^^^^^^ string.unquoted.css */ -/* ^ punctuation.separator.sequence.css */ - url-prefix("http://www"), -/* ^^^^^^^^^^ support.function.url-prefix.css */ -/* ^^^^^^^^^^^^ string.quoted.double.css */ - domain(mozilla.org), -/* ^^^^^^ support.function.domain.css */ -/* ^^^^^^^^^^^ string.unquoted.css */ - regexp("https:.*") -/* ^^^^^^ support.function.regexp.css */ -/* ^^^^^^^^^^ string.quoted.double.css */ -{ - .class { -/* ^^^^^^ meta.at-rule.document.css entity.other.attribute-name.class.css */ - display: none; -/* ^^^^^^^ meta.at-rule.document.css meta.property-name.css */ - } -} - - @font-face { -/* ^^^^^^^^^^^ meta.at-rule.font-face.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^ keyword.control.at-rule.font-face.css */ - - src: local(Font), - /* */ -/* ^^^^^ comment.block.css */ -} - - @font-face -/* ^^^^^^^^^^^ meta.at-rule.font-face.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^ keyword.control.at-rule.font-face.css */ -{ - font-family: monospace, -/* ^^^^^^^^^^^ support.type.property-name.css */ -/* ^^^^^^^^^ support.constant.font-name.css */ - /* */ -/* ^^^^^ comment.block.css */ -} - - @supports not ( and ( top: 2px ) ) { } -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.at-rule.supports.css */ -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^ keyword.control.at-rule.supports.css */ -/* ^^^ keyword.operator.logic.css */ -/* ^ meta.group.css punctuation.definition.group.begin.css */ -/* ^^^ keyword.operator.logic.css */ -/* ^ meta.group.css punctuation.definition.group.begin.css */ -/* ^^^ support.type.property-name.css */ -/* ^ punctuation.separator.key-value.css */ -/* ^^ constant.numeric.integer.decimal.css keyword.other.unit.css */ -/* ^ meta.group.css punctuation.definition.group.end.css */ -/* ^ meta.group.css punctuation.definition.group.end.css */ -/* ^^^ meta.block.css */ -/* ^ punctuation.definition.block.begin.css */ -/* ^ punctuation.definition.block.end.css */ - -@supports (--foo: green) { -/* ^^^^^ support.type.custom-property.css */ - .class { -/* ^^^^^^ meta.at-rule.supports.css entity.other.attribute-name.class.css */ - display: none; -/* ^^^^^^^ meta.at-rule.supports.css meta.property-name.css */ - } -} - -@supports (display: grid) {span { display: grid; }} -/* ^^^^ meta.at-rule.supports.css meta.selector.css */ - - @counter-style {} -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^^^^^^ keyword.control.at-rule.counter-style.css */ - -@counter-style none {} -/* ^^^^ invalid.illegal.counter-style-name.css */ - -@counter-style decimal {} -/* ^^^^^^^ invalid.illegal.counter-style-name.css */ - - @counter-style name { -/* ^ punctuation.definition.keyword.css */ -/* ^^^^^^^^^^^^^^ keyword.control.at-rule.counter-style.css */ -/* ^^^^ entity.other.counter-style-name.css */ - symbols: "‣"; -/* ^^^^^^^ meta.at-rule.counter-style.css support.type.property-name.css */ - suffix: " "; -/* ^^^^^^ meta.at-rule.counter-style.css support.type.property-name.css */ -/* ^^^ string.quoted.double.css */ -} - - @counter-style blacknwhite -/* ^^^^^^^^^^^^^^ meta.at-rule.counter-style.css keyword.control.at-rule.counter-style.css */ -/* ^^^^^^^^^^^ entity.other.counter-style-name.css */ -{ - system: cyclic; - negative: "(" ")"; - prefix: "/"; - symbols: ◆ ◇; - suffix: "/ "; - range: 2 4; - speak-as: "bullets"; -/*^^^^^^^^ support.type.property-name.css */ -} - -.test-var { --test-var: arial; } -/* ^^^^^^^^^^ support.type.custom-property.css */ -/* ^^ punctuation.definition.custom-property */ -/* ^^^^^^^^ support.type.custom-property.name.css */ - -.test-deprecated-var { var-deprecated- } -/* ^^^^^^^^^^^^^^^ invalid.deprecated.custom-property.css */ -/* ^^^^ keyword.other.custom-property.prefix.css */ - -.test-types { - top: 20; -/* ^^ constant.numeric.integer.decimal.css */ - top: +.95e-20; -/* ^^^^^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ - top: -1.5e+93%; -/* ^^^^^^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -} - -.test-units { - top: 1px; -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^ keyword.other.unit.css */ - top: 1.1em; -/* ^^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -/* ^^ keyword.other.unit.css */ - top: -100%; -/* ^^^^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.other.unit.css */ - top: 1.1.1deg; -/* ^^^^^^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -/* ^ punctuation.separator.decimal.css */ -/* ^^^ keyword.other.unit.css */ -} - -.test-properties { -/* ^ meta.property-list.css */ - - -webkit-transform: none; -/* ^^^^^^^^^^^^^^^^^ meta.property-name.css */ -/* ^^^^^^^^ support.type.vendor-prefix.css */ -/* ^^^^^^^^^ support.type.property-name.css */ -/* ^^^^ meta.property-value.css support.constant.property-value.css */ -} - -.test-operators { - top: calc(1px + 1px); - /* ^ keyword.operator.css */ - top: calc(1px - 1px); - /* ^ keyword.operator.css */ - top: calc(1px / 1px); - /* ^ keyword.operator.css */ - top: calc(1px * 1px); - /* ^ keyword.operator.css */ - - top: calc(1px+1px); - /* ^ -keyword.operator.css */ - top: calc(1px-1px); - /* ^ -keyword.operator.css */ - top: calc(1px/1px); - /* ^ keyword.operator.css */ - top: calc(1px*1px); - /* ^ keyword.operator.css */ -} - -.test-important { - top: 1px !important; -/* ^^^^^^^^^^ keyword.other.important.css */ - top: 1px!important; -/* ^^^^^^^^^^ keyword.other.important.css */ -} - -/* Test Functional Pseudo Class Meta Scopes */ -.test:nth-child(even) {} -/*^^^^^^^^^^^^^^^^^^^^ meta.selector.css */ -/* ^ - meta.selector.css */ -/* ^^^^^^^^^^^^^^^^ meta.function-call.css */ -/* ^^^^^^ meta.group.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^ punctuation.definition.group.end.css */ - -.test:nth-child(+2n + 3) {} -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^ - constant */ -/* ^^^ constant.numeric.integer.decimal.css */ - -.test:nth-child(-n+ 3) {} -/* ^^^^^ constant.numeric.integer.decimal.css */ - -.test:nth-child(-n +3) {} -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ - constant */ -/* ^^ constant.numeric.integer.decimal.css */ - -.test:nth-child(+3) {} -/* ^^ constant.numeric.integer.decimal.css */ - -.test:nth-child(+ 3) {} -/* ^^^ invalid.illegal.numeric.css */ - -.test:nth-child(+ 3n) {} -/* ^^^ invalid.illegal.numeric.css */ - -.test:nth-child(+3 n) {} -/* ^^^^ invalid.illegal.numeric.css */ - -.test-pseudo-classes:nth-child(2):hover {} -/*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.selector.css */ -/* ^ - meta.selector.css */ -/* ^^^^^^^^^ entity.other.pseudo-class.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.entity.css */ -/* ^^^^^ entity.other.pseudo-class.css */ - -.test-pseudo-class-numerics:nth-last-of-type(-n+3) {} -/* ^^^^^^^^^^^^^^^^^ entity.other.pseudo-class.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - -.test-pseudo-class-keywords:nth-of-type(odd) {} -/* ^^^^^^^^^^^^ entity.other.pseudo-class.css */ -/* ^^^ keyword.other.pseudo-class.css */ - -.test-pseudo-class-strings:dir(ltr) {} -/* ^^^^ entity.other.pseudo-class.css */ -/* ^^^ string.unquoted.css */ - -.test-pseudo-class-tag:not(*) {} -/* ^^^^ entity.other.pseudo-class.css */ -/* ^ entity.name.tag.wildcard.css */ - -.test-pseudo-elements::before {} -/* ^^ punctuation.definition.entity.css */ -/* ^^^^^^^^ entity.other.pseudo-element.css */ - -.test-pseudo-elements:after {} -/* ^ punctuation.definition.entity.css */ -/* ^^^^^^ entity.other.pseudo-element.css */ - -.test-pseudo-elements::-webkit-slider-runnable-track -/* ^^ punctuation.definition.entity.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ entity.other.pseudo-element.css */ - -.test-unicode { top: "\2764 \273e"; } -/* ^^^^^ constant.character.escape.css */ -/* ^^^^^ constant.character.escape.css */ - -.test-unicode-range { - unicode-range: U+0025-00FF, U+4??; -/* ^^^^^^^^^^^ support.unicode-range.css */ -/* ^^ support.constant.unicode-range.prefix.css */ -/* ^^^^^^^^^ constant.codepoint-range.css */ -/* ^ punctuation.section.range.css */ -/* ^^^^^ support.unicode-range.css */ -/* ^^ support.constant.unicode-range.prefix.css */ -/* ^^^ constant.codepoint-range.css */ -} - -.test-escape-character { top: "\nstring\n"; } -/* ^^ constant.character.escape.css */ -/* ^^ constant.character.escape.css */ - -.test-attribute-selectors[disabled][type=button] {} -/* ^^^^^^^^^^ meta.attribute-selector.css */ -/* ^ punctuation.definition.entity.css */ -/* ^^^^^^^^ entity.other.attribute-name.css */ -/* ^ punctuation.definition.entity.css */ -/* ^^^^ entity.other.attribute-name.css */ -/* ^^^^^^ string.unquoted.css */ - -.test-attribute-selectors-namespaces[n|a=""][*|a=""][|att] {} -/* ^ entity.other.namespace-prefix.css */ -/* ^ punctuation.separator.namespace.css */ -/* ^ entity.name.namespace.wildcard.css */ -/* ^ punctuation.separator.namespace.css */ -/* ^ punctuation.separator.namespace.css */ - -.test-attribute-selectors-operators[a=""][a~=""][a|=""][a^=""][a$=""][a*=""] {} -/* ^ keyword.operator.attribute-selector.css */ -/* ^^ keyword.operator.attribute-selector.css */ -/* ^^ keyword.operator.attribute-selector.css */ -/* ^^ keyword.operator.attribute-selector.css */ -/* ^^ keyword.operator.attribute-selector.css */ -/* ^^ keyword.operator.attribute-selector.css */ - -.test-attribute-selectors-whitespace[a = ""] {} -/* ^ entity.other.attribute-name.css */ -/* ^ keyword.operator.attribute-selector.css */ - -.test-attribute-selectors-flags[a="" i] {} -/* ^ keyword.other.css */ - - *.test-universal-selector {} -/* ^ entity.name.tag.wildcard.css */ - -.test-combinators >>> a >> a > a + b ~ a {} -/* ^^^ punctuation.separator.combinator.css */ -/* ^^ punctuation.separator.combinator.css */ -/* ^ punctuation.separator.combinator.css */ -/* ^ punctuation.separator.combinator.css */ -/* ^ punctuation.separator.combinator.css */ - -.test-invalid-combinators +>> a +++ a ~+> {} -/* ^^^ invalid.illegal.combinator.css */ -/* ^^^ invalid.illegal.combinator.css */ -/* ^^^ invalid.illegal.combinator.css */ - -.test-generic-font-families { - font: serif; -/* ^^^^^ support.constant.font-name.css */ - font: sans-serif; -/* ^^^^^^^^^^ support.constant.font-name.css */ - font: cursive; -/* ^^^^^^^ support.constant.font-name.css */ - font: fantasy; -/* ^^^^^^^ support.constant.font-name.css */ - font: monospace; -/* ^^^^^^^^^ support.constant.font-name.css */ -} - -.test-unquoted-font-name { - font: m700, aria; -/* ^^^^ string.unquoted */ -/* ^ punctuation.separator */ -/* ^^ - string */ -/* ^^^^ string.unquoted */ - font: inherit; -/* ^ - string */ - font: initial; -/* ^ - string */ - font: unset; -/* ^ - string */ - font: italic; -/* ^ - string */ - font: small-caps; -/* ^ - string */ - font: 2em m700, sans-serif; -/* ^ - string */ -/* ^^^^ string.unquoted */ -/* ^ punctuation.separator */ -/* ^ - string */ - font-weight: bold; -/* ^^^^^^^^^^^ meta.property-name support.type.property-name */ -} - -.test-color-values { - color: aqua; -/* ^^^^ support.constant.color.w3c-standard-color-name.css */ - - color: aliceblue; -/* ^^^^^^^^^ support.constant.color.w3c-extended-color-keywords.css */ - - color: currentColor; -/* ^^^^^^^^^^^^ support.constant.color.w3c-special-color-keyword.css */ - - color: transparent; -/* ^^^^^^^^^^^ support.constant.color.w3c-special-color-keyword.css */ - - color: #b4da55; -/* ^ punctuation.definition.constant.css */ -/* ^^^^^^^ constant.other.color.rgb-value.css */ - - color: #137; -/* ^^^^ constant.other.color.rgb-value.css */ - - color: #0f0a; -/* ^^^^^ constant.other.color.rgba-value.css */ - - color: #a1b2c3d4; -/* ^^^^^^^^^ constant.other.color.rgba-value.css */ - - color: #E5F6A7B8; -/* ^ punctuation.definition.constant.css */ -/* ^^^^^^^^^ constant.other.color.rgba-value.css */ -} - -.test-function-meta { - top: filter(param1, 20px); -/* ^^^^^^^^^^^^^^^^^^^^ meta.function-call.css */ -/* ^^^^^^^^^^^^^^ meta.group.css */ -} - -.test-color-functions { - top: rgb(1, 4.5%); -/* ^^^ support.function.color.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: rgba(); -/* ^^^^ support.function.color.css */ - - top: hsl(1deg, 4.5%); -/* ^^^ support.function.color.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: hsla(); -/* ^^^^ support.function.color.css */ - - top: hwb(); -/* ^^^ support.function.color.css */ - - - top: gray(1, 4.5%); -/* ^^^^ support.function.color.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: device-cmyk(0.5, 1%, red()); -/* ^^^^^^^^^^^ support.function.color.css */ -/* ^^^ constant.numeric.float.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^^^ support.function.color.css */ - - top: color(w() s()); -/* ^^^^^ support.function.color.css */ -/* ^ support.function.color.css */ -/* ^ support.function.color.css */ - - top: alpha(- 1.5%); -/* ^^^^^ support.function.color.css */ -/* ^ keyword.operator.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: h(+ 1.5deg); -/* ^ support.function.color.css */ -/* ^ keyword.operator.css */ -/* ^^^^^^ constant.numeric.float.decimal.css */ - - top: w(* 1.5%); -/* ^ support.function.color.css */ -/* ^ keyword.operator.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: shade(1.5%); -/* ^^^^^ support.function.color.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: blenda(red 50% hsl); -/* ^^^^^^ support.function.color.css */ -/* ^^^ support.constant.color.w3c-standard-color-name.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^ keyword.other.color-space.css */ - - background-color: color(var(--background) blend(var(--foreground) 80%)); -/* ^^^^^^^^^^^^^^^^ support.type.property-name.css */ -/* ^ punctuation.separator.key-value.css */ -/* ^^^^^ support.function.color.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^ support.function.var.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^^^ support.type.custom-property */ -/* ^ punctuation.definition.group.end.css */ -/* ^^^^^ support.function.color.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^ support.function.var.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^^^ support.type.custom-property.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^ punctuation.definition.group.end.css */ -/* ^ punctuation.terminator.rule.css */ -} - -.test-transform-functions { - top: rotate(0); -/* ^^^^^^ support.function.transform.css */ -/* ^ constant.numeric.integer.decimal.css */ - - top: rotate(1) -/* ^^^^^^ support.function.transform.css */ -/* ^ - constant.numeric.integer.decimal.css */ - - top: rotate3d(-1, 2deg); -/* ^^^^^^^^ support.function.transform.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - - top: matrix3d(1, 0); -/* ^^^^^^^^ support.function.transform.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^ constant.numeric.integer.decimal.css */ - - top: translate3d(1, 2px, 3%); -/* ^^^^^^^^^^^ support.function.transform.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^ constant.numeric.integer.decimal.css */ - - top: translateY(2px); -/* ^^^^^^^^^^ support.function.transform.css */ -/* ^^^ constant.numeric.integer.decimal.css */ - - top: translateX(1%); -/* ^^^^^^^^^^ support.function.transform */ -/* ^^ constant.numeric.integer.decimal.css */ - - top: translateZ(0); -/* ^^^^^^^^^^ support.function.transform */ -/* ^ constant.numeric.integer.decimal.css */ - - top: skewY(1deg); -/* ^^^^^ support.function.transform.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - - top: skew(1deg, 2turn); -/* ^^^^ support.function.transform.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^^ constant.numeric.integer.decimal.css */ - - top: perspective(17px); -/* ^^^^^^^^^^^ support.function.transform.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - - top: scaleY(2); -/* ^^^^^^ support.function.transform.css */ -/* ^ constant.numeric.integer.decimal.css */ - - top: skewY(1rad) rotate(1turn); -/* ^^^^^^ support.function.transform.css */ -/* ^^^^^ constant.numeric.integer.decimal.css */ - - transform: translate(var(--center), 0) scale(var(--ripple-scale), 1); -/* ^^^^^^^^^ support.function.transform */ -/* ^^^ support.function.var */ -/* ^^^^^^^^ support.type.custom-property */ -/* ^ constant.numeric */ -/* ^^^ support.function.var */ -} - -.test-timing-functions { - top: cubic-bezier(0.42, 0, 0.58, 1); -/* ^^^^^^^^^^^^ support.function.timing.css */ -/* ^^^^ constant.numeric.float.decimal.css */ - - top: steps(020, start); -/* ^^^^^ support.function.timing.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^^^ support.keyword.timing-direction.css */ - - top: steps(1, end); -/* ^^^ support.keyword.timing-direction.css */ - - top: steps(1, middle); -/* ^^^^^^ support.keyword.timing-direction.css */ -} - -.test-shape-functions { - top: circle(at top 5.5e20em); -/* ^^^^^^ support.function.shape.css */ -/* ^^ keyword.other.css */ -/* ^^^ support.constant.property-value.css */ -/* ^^^^^^^^ constant.numeric.float.decimal.css */ - - top: ellipse(at 0%); -/* ^^^^^^^ support.function.shape.css */ -/* ^^ keyword.other.css */ -/* ^^ constant.numeric.integer.decimal.css */ - - top: ellipse(closest-side); -/* ^^^^^^^^^^^^ support.constant.property-value.css */ - - top: inset(1.1px round 50%); -/* ^^^^^ support.function.shape.css */ -/* ^^^^^ constant.numeric.float.decimal.css */ -/* ^^^^^ keyword.other.css */ - - top: rect(auto); -/* ^^^^ support.function.shape.css */ -/* ^^^^ support.constant.property-value.css */ - - top: rect(1px); -/* ^^^ constant.numeric.integer.decimal.css */ -} - -.test-calc-function { - top: calc(1.1px + 2rad); -/* ^^^^ support.function.calc.css */ -/* ^^^^^ constant.numeric.float.decimal.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - - top: calc(attr(start, 1) - 1); - /* ^^^^ support.function.attr.css */ - - top: calc(calc() * calc()); -/* ^^^^ support.function.calc.css */ -/* ^^^^ support.function.calc.css */ -/* ^ keyword.operator.css */ -/* ^^^^ support.function.calc.css */ - top: calc(100% - (1 * 10px) / 1 - (1 * 10px) / 1 - (1 * 10px) / 1); -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.function-call.css */ -/* ^^^^ support.function.calc.css */ -/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ meta.group.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.operator.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^^^^^^^^^ meta.group.css meta.group.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^ keyword.operator.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^^ keyword.other.unit.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^ keyword.operator.css */ -/* ^ keyword.operator.css */ -/* ^ punctuation.definition.group.begin.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ keyword.operator.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^ keyword.operator.css */ -/* ^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.definition.group.end.css */ -/* ^ punctuation.terminator.rule.css - meta.group */ -} - -.test-toggle-function { - top: toggle(5px red preserve-3d); -/* ^^^^^^ support.function.toggle.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^ support.constant.color.w3c-standard-color-name.css */ -/* ^^^^^^^^^^^ support.constant.property-value.css */ -} - -.test-attr-function { - top: attr(*|c); -/* ^^^^ support.function.attr.css */ -/* ^ entity.name.namespace.wildcard.css */ -/* ^ punctuation.separator.namespace.css */ -/* ^ entity.other.attribute-name.css */ - - top: attr(n|size); -/* ^ entity.other.namespace-prefix.css */ -/* ^^^^ entity.other.attribute-name.css */ - - top: attr(size px, auto); -/* ^^^^ entity.other.attribute-name.css */ -/* ^^ keyword.other.unit.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^ support.constant.property-value.css */ - - top: attr(preserve-3d); -/* ^^^^^^^^^^^ entity.other.attribute-name.css */ -} - -.test-url-function { - top: url("a"); -/* ^^^ support.function.url.css */ -/* ^^^ string.quoted.double.css */ - - top: url(a); -/* ^ string.unquoted.css */ -} - -.test-image-functions { - top: image("a"); -/* ^^^^^ support.function.image.css */ -/* ^^^ string.quoted.double.css */ - - top: image(a); -/* ^ string.unquoted.css */ - - top: image("a", rgb(0, 0, 0)); -/* ^ punctuation.separator.sequence.css */ -/* ^ constant.numeric.integer.decimal.css */ - - top: image-set("a" 1x, a 4dpi); -/* ^^^ string.quoted.double.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ keyword.other.unit.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^ string.unquoted.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - - top: cross-fade(50% "a", b); -/* ^^^^^^^^^^ support.function.image.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^ string.quoted.double.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^ string.unquoted.css */ -} - -.test-gradient-functions { - top: linear-gradient(); -/* ^^^^^^^^^^^^^^^ support.function.gradient.css */ - - top: linear-gradient(45deg, white); -/* ^^^^^ constant.numeric.integer.decimal.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^^ support.constant.color.w3c-standard-color-name.css */ - - top: linear-gradient(to top left); -/* ^^ keyword.other.css */ -/* ^^^ support.constant.property-value.css */ -/* ^^^^ support.constant.property-value.css */ - - top: linear-gradient(0%, 100%); -/* ^^ constant.numeric.integer.decimal.css */ -/* ^^^^ constant.numeric.integer.decimal.css */ - - top: repeating-linear-gradient(); -/* ^^^^^^^^^^^^^^^^^^^^^^^^^ support.function.gradient.css */ - - top: radial-gradient(); -/* ^^^^^^^^^^^^^^^ support.function.gradient.css */ - - top: radial-gradient(circle at top left); -/* ^^^^^^ keyword.other.css */ -/* ^^ keyword.other.css */ -/* ^^^ support.constant.property-value.css */ -/* ^^^^ support.constant.property-value.css */ - - top: radial-gradient(red, blue); -/* ^^^ support.constant.color.w3c-standard-color-name.css */ -/* ^ punctuation.separator.sequence.css */ - - top: repeating-radial-gradient(); -/* ^^^^^^^^^^^^^^^^^^^^^^^^^ support.function.gradient.css */ -} - -.test-counter-functions { - top: counter(name, decimal-leading-zero); -/* ^^^^^^^ support.function.counter.css */ -/* ^^^^ entity.other.counter-name.css string.unquoted.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^^^^^^^^^^^^^^^^^ support.constant.property-value.counter-style.css */ - - top: counters(name, "str", none); -/* ^^^^^^^^ support.function.counter.css */ -/* ^^^^ entity.other.counter-name.css string.unquoted.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^^ string.quoted.double.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^^ support.constant.property-value.counter-style.css */ - - top: symbols(fixed "\2020" url()); -/* ^^^^^^^ support.function.counter.css */ -/* ^^^^^ support.constant.symbol-type.css */ -/* ^^^^^^^ string.quoted.double.css */ -/* ^^^ support.function.url.css */ -} - -.test-grid-functions { - grid: repeat(20) / auto-flow 1fr; -/* ^^^^^^ support.function.grid.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^^^^^^^^^ support.constant.property-value.css */ - - top: repeat(auto-fit, 2fr minmax(auto) 5%); -/* ^^^^^^^^ support.keyword.repetitions.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^^^^ support.function.grid.css */ -/* ^^^^ support.constant.property-value.css */ -/* ^^ constant.numeric.integer.decimal.css */ - - top: minmax(min-content, 1fr, 10%); -/* ^^^^^^ support.function.grid.css */ -/* ^^^^^^^^^^^ support.constant.property-value.css */ -/* ^ punctuation.separator.sequence.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^ constant.numeric.integer.decimal.css */ - grid-template: repeat(2, var(--size)) / repeat(2, 50%); -/* ^^^ support.function.var.css */ -/* ^^^^^^ support.type.custom-property.css */ -/* ^^ punctuation.definition.group.end.css */ -/* ^^^^^^ support.function.grid.css */ - grid-template-columns: - [a-line-name] auto -/* ^ punctuation.section.begin.css */ -/* ^^^^^^^^^^^ string.unquoted.line-name.css */ -/* ^ punctuation.section.end.css */ - [b] minmax(min-content, 1fr) - [b c d] repeat(2, [e] 40px) -/* ^ punctuation.section.begin.css */ -/* ^ string.unquoted.line-name.css */ -/* ^ - string.unquoted.line-name.css */ -/* ^ string.unquoted.line-name.css */ -/* ^ punctuation.section.end.css */ -/* ^ punctuation.section.begin.css */ -/* ^ string.unquoted.line-name.css */ -/* ^ punctuation.section.end.css */ - repeat(5, auto); -} - -.test-filter-functions { - top: filter(url(), blur()); -/* ^^^^^^ support.function.filter.css */ -/* ^^^ support.function.url.css */ -/* ^^^^ support.function.filter.css */ - - top: blur(1px); -/* ^^^^ support.function.filter.css */ - - top: sepia(1% 1); -/* ^^^^^ support.function.filter.css */ -/* ^^ constant.numeric.integer.decimal.css */ -/* ^ constant.numeric.integer.decimal.css */ - - top: drop-shadow(1px rgb()); -/* ^^^^^^^^^^^ support.function.filter.css */ -/* ^^^ constant.numeric.integer.decimal.css */ -/* ^^^ support.function.color.css */ - - top: hue-rotate(1turn); -/* ^^^^^^^^^^ support.function.filter.css */ -/* ^^^^^ constant.numeric.integer.decimal.css */ -} - -/* Test Font Functions: format() & local() */ -@font-face { - src: format("embedded-opentype"); -/* ^^^^^^ support.function.font-face.css */ -/* ^^^^^^^^^^^^^^^^^^^ string.quoted.double.css */ - - src: local(Gentium-Bold); -/* ^^^^^ support.function.font-face.css */ -/* ^^^^^^^^^^^^ string.unquoted.css */ - - src: local('Gentium-Bold'); -/* ^^^^^ support.function.font-face.css */ -/* ^^^^^^^^^^^^^^ string.quoted.single.css */ -/* ^ punctuation.definition.string.begin.css */ -/* ^ punctuation.definition.string.end.css */ - - src: local("Gentium-Bold"); -/* ^^^^^ support.function.font-face.css */ -/* ^^^^^^^^^^^^^^ string.quoted.double.css */ -/* ^ punctuation.definition.string.begin.css */ -/* ^ punctuation.definition.string.end.css */ -} - -@font-face { - font-family: m700, aria; -/* ^^^^ string.unquoted.css */ -/* ^^^^ string.unquoted.css */ -} - -.test-var-function { - top: var(--name); -/* ^^^ support.function.var.css */ -/* ^^ punctuation.definition.custom-property.css */ -/* ^^^^ support.type.custom-property.name.css */ -} - -.test-custom-tags > div > span + cust·m-tÀg > div-cøstom-tag ~ form-Çust😀m-tag.classname:last-child:hover {} -/* ^ -entity.name.tag.custom.css */ -/* ^^^ entity.name.tag.css */ -/* ^^^^ entity.name.tag.css */ -/* ^ -entity.name.tag.custom.css */ -/* ^^^^^^^^^^ entity.name.tag.custom.css */ -/* ^^^^^^^^^^^^^^ entity.name.tag.custom.css */ -/* ^ -entity.name.tag.custom.css */ -/* ^^^^^^^^^^^^^^^ entity.name.tag.custom.css */ -/* ^^^^^^^^^ entity.other.attribute-name.class.css */ -/* ^^^^^^^^^^ -entity.name.tag.custom.css */ -/* ^^^^^ -entity.name.tag.custom.css */ - -.test-property-name-order-doesnt-prevent-full-matches { - grid-template-rows: none; -/* ^^^^^^^^^^^^^^^^^^ support.type.property-name */ -/* ^ punctuation.separator.key-value */ - grid-template-columns: none; -/* ^^^^^^^^^^^^^^^^^^^^^ support.type.property-name */ -/* ^ punctuation.separator.key-value */ - grid-template-areas: auto; -/* ^^^^^^^^^^^^^^^^^^^ support.type.property-name */ -/* ^ punctuation.separator.key-value */ - grid-template: initial; -/* ^^^^^^^^^^^^^ support.type.property-name */ -/* ^ punctuation.separator.key-value */ - grid-row-gap: 3vmin; -/* ^^^^^^^^^^^^ support.type.property-name */ -/* ^ punctuation.separator.key-value */ - grid-row: auto; -/* ^^^^^^^^ support.type.property-name */ -/* ^ punctuation.separator.key-value */ -} - -.test-meta-scopes-for-completions { - top: 5px; -/*^^^^^^^^^^^ meta.property-list */ -/* ^^^ meta.property-name */ -/* ^^^^ meta.property-value */ - top: ; -/*^^^^^^^^^^^ meta.property-list */ -/* ^^^ meta.property-name */ -/* ^ meta.property-value */ - top: -/*^^^^^^^ meta.property-list */ -/* ^^^ meta.property-name */ -}/* ^ meta.property-value */ - -.generic-font-family { font-family: my-serif, serif } -/* ^^^^^^^^ string.unquoted */ -/* ^ - string */ -/* ^^^^^ support.constant.font-name */ -.generic-font-family2 { font-family: sans-serif , fantasy , system-ui; } -/* ^^^^^^^^^^ support.constant.font-name */ -/* ^^^^^^^ support.constant.font-name */ -/* ^^^^^^^^^ support.constant.font-name */ -.generic-font-family3 { - font-family: cursive -/* ^^^^^^^ support.constant.font-name */ -} -.generic-font-family4 { - font-family: droid serif; -/* ^^^^^^^^^^^ string.unquoted */ -} - -a { - height:calc(10px/*); - font-family:"a*/); -/* ^^^^^^^^^^^^^^^ comment.block.css */ -/* ^ punctuation.definition.group.end.css */ -} - -.variable-beginnings { - --1x: url(data:image/png;base64,PNG); -/* ^^^^ support.type.custom-property */ -/* ^^ punctuation.definition.custom-property */ -/* ^^ support.type.custom-property.name */ -/* ^ punctuation.separator.key-value */ - background-image: var(--1x); -/* ^^^^ support.type.custom-property */ -/* ^^ punctuation.definition.custom-property */ -/* ^^ support.type.custom-property.name */ - --\ff: 5px; -/* ^^^^^ support.type.custom-property */ -/* ^^ punctuation.definition.custom-property */ -/* ^^^ support.type.custom-property.name */ -/* ^ punctuation.separator.key-value */ -} - -img{ - clip-path: polygon( - 0% 0%, - 100% 0%, - 100% calc(100% - 31px), -/* ^^^^ support.function.calc */ -/* ^^^ constant.numeric */ -/* ^ punctuation.separator.sequence */ - calc(100% - 196px) calc(100% - 31px), - calc(100% - 196px) 100%, - 0% 100% - ) !important; -/*^ punctuation.definition.group.end */ -/* ^^^^^^^^^^ keyword.other.important */ -} diff --git a/assets/syntaxes/Packages/Diff/.python-version b/assets/syntaxes/Packages/Diff/.python-version deleted file mode 100644 index 3767b4b17..000000000 --- a/assets/syntaxes/Packages/Diff/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.14 \ No newline at end of file diff --git a/assets/syntaxes/Packages/HTML/.python-version b/assets/syntaxes/Packages/HTML/.python-version deleted file mode 100644 index 3767b4b17..000000000 --- a/assets/syntaxes/Packages/HTML/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.14 \ No newline at end of file diff --git a/assets/syntaxes/Packages/ShellScript/.python-version b/assets/syntaxes/Packages/ShellScript/.python-version deleted file mode 100644 index 3767b4b17..000000000 --- a/assets/syntaxes/Packages/ShellScript/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.14 \ No newline at end of file diff --git a/src/db/types/dependencies.rs b/crates/docs_rs_cargo_metadata/src/db.rs similarity index 100% rename from src/db/types/dependencies.rs rename to crates/docs_rs_cargo_metadata/src/db.rs diff --git a/crates/docs_rs_cargo_metadata/src/lib.rs b/crates/docs_rs_cargo_metadata/src/lib.rs index f57a14c13..e3c173118 100644 --- a/crates/docs_rs_cargo_metadata/src/lib.rs +++ b/crates/docs_rs_cargo_metadata/src/lib.rs @@ -1,3 +1,5 @@ +mod db; + use anyhow::{Context, Result, bail}; use docs_rs_database::types::version::Version; // use rustwide::{Toolchain, Workspace, cmd::Command}; diff --git a/crates/docs_rs_database/Cargo.toml b/crates/docs_rs_database/Cargo.toml index 811a35b0d..1f9e33d94 100644 --- a/crates/docs_rs_database/Cargo.toml +++ b/crates/docs_rs_database/Cargo.toml @@ -2,6 +2,7 @@ name = "docs_rs_database" version = "0.1.0" edition = "2024" +build = "build.rs" [dependencies] anyhow = { workspace = true } @@ -11,6 +12,7 @@ docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } futures-util = { workspace = true } mime = { workspace = true } +mime_guess = "2" opentelemetry = { workspace = true } semver = { workspace = true } serde = { workspace = true } @@ -21,7 +23,6 @@ strum = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } -mime_guess = "2" [features] testing = [] diff --git a/crates/docs_rs_database/build.rs b/crates/docs_rs_database/build.rs new file mode 100644 index 000000000..25c393629 --- /dev/null +++ b/crates/docs_rs_database/build.rs @@ -0,0 +1,4 @@ +fn main() { + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=migrations"); +} diff --git a/migrations/20231021111635_initial.down.sql b/crates/docs_rs_database/migrations/20231021111635_initial.down.sql similarity index 100% rename from migrations/20231021111635_initial.down.sql rename to crates/docs_rs_database/migrations/20231021111635_initial.down.sql diff --git a/migrations/20231021111635_initial.up.sql b/crates/docs_rs_database/migrations/20231021111635_initial.up.sql similarity index 100% rename from migrations/20231021111635_initial.up.sql rename to crates/docs_rs_database/migrations/20231021111635_initial.up.sql diff --git a/migrations/20240221104457_drop_releases_build_status.down.sql b/crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql similarity index 100% rename from migrations/20240221104457_drop_releases_build_status.down.sql rename to crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql diff --git a/migrations/20240221104457_drop_releases_build_status.up.sql b/crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql similarity index 100% rename from migrations/20240221104457_drop_releases_build_status.up.sql rename to crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql diff --git a/migrations/20240221113734_drop_releases_rustc_version.down.sql b/crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql similarity index 100% rename from migrations/20240221113734_drop_releases_rustc_version.down.sql rename to crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql diff --git a/migrations/20240221113734_drop_releases_rustc_version.up.sql b/crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql similarity index 100% rename from migrations/20240221113734_drop_releases_rustc_version.up.sql rename to crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql diff --git a/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql b/crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql similarity index 100% rename from migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql rename to crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql diff --git a/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql b/crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql similarity index 100% rename from migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql rename to crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql diff --git a/migrations/20240221124844_multi_stage_build_status.down.sql b/crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql similarity index 100% rename from migrations/20240221124844_multi_stage_build_status.down.sql rename to crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql diff --git a/migrations/20240221124844_multi_stage_build_status.up.sql b/crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql similarity index 100% rename from migrations/20240221124844_multi_stage_build_status.up.sql rename to crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql diff --git a/migrations/20240227040753_add_owner_kind.down.sql b/crates/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql similarity index 100% rename from migrations/20240227040753_add_owner_kind.down.sql rename to crates/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql diff --git a/migrations/20240227040753_add_owner_kind.up.sql b/crates/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql similarity index 100% rename from migrations/20240227040753_add_owner_kind.up.sql rename to crates/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql diff --git a/migrations/20240309082057_release_status_view.sql.down.sql b/crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql similarity index 100% rename from migrations/20240309082057_release_status_view.sql.down.sql rename to crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql diff --git a/migrations/20240309082057_release_status_view.sql.up.sql b/crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql similarity index 100% rename from migrations/20240309082057_release_status_view.sql.up.sql rename to crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql diff --git a/migrations/20240311202914_release_status_materialized.down.sql b/crates/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql similarity index 100% rename from migrations/20240311202914_release_status_materialized.down.sql rename to crates/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql diff --git a/migrations/20240311202914_release_status_materialized.up.sql b/crates/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql similarity index 100% rename from migrations/20240311202914_release_status_materialized.up.sql rename to crates/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql diff --git a/migrations/20240313103708_make_release_fields_optional.down.sql b/crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql similarity index 100% rename from migrations/20240313103708_make_release_fields_optional.down.sql rename to crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql diff --git a/migrations/20240313103708_make_release_fields_optional.up.sql b/crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql similarity index 100% rename from migrations/20240313103708_make_release_fields_optional.up.sql rename to crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql diff --git a/migrations/20240313182623_make_build_fields_optional.down.sql b/crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql similarity index 100% rename from migrations/20240313182623_make_build_fields_optional.down.sql rename to crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql diff --git a/migrations/20240313182623_make_build_fields_optional.up.sql b/crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql similarity index 100% rename from migrations/20240313182623_make_build_fields_optional.up.sql rename to crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql diff --git a/migrations/20240313184911_build_errors.down.sql b/crates/docs_rs_database/migrations/20240313184911_build_errors.down.sql similarity index 100% rename from migrations/20240313184911_build_errors.down.sql rename to crates/docs_rs_database/migrations/20240313184911_build_errors.down.sql diff --git a/migrations/20240313184911_build_errors.up.sql b/crates/docs_rs_database/migrations/20240313184911_build_errors.up.sql similarity index 100% rename from migrations/20240313184911_build_errors.up.sql rename to crates/docs_rs_database/migrations/20240313184911_build_errors.up.sql diff --git a/migrations/20240519141105_crate-version-name-field-length.down.sql b/crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql similarity index 100% rename from migrations/20240519141105_crate-version-name-field-length.down.sql rename to crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql diff --git a/migrations/20240519141105_crate-version-name-field-length.up.sql b/crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql similarity index 100% rename from migrations/20240519141105_crate-version-name-field-length.up.sql rename to crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql diff --git a/migrations/20240624085737_build-status-idx.down.sql b/crates/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql similarity index 100% rename from migrations/20240624085737_build-status-idx.down.sql rename to crates/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql diff --git a/migrations/20240624085737_build-status-idx.up.sql b/crates/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql similarity index 100% rename from migrations/20240624085737_build-status-idx.up.sql rename to crates/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql diff --git a/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql b/crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql similarity index 100% rename from migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql rename to crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql diff --git a/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql b/crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql similarity index 100% rename from migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql rename to crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql diff --git a/migrations/20241018031600_documentation_size.down.sql b/crates/docs_rs_database/migrations/20241018031600_documentation_size.down.sql similarity index 100% rename from migrations/20241018031600_documentation_size.down.sql rename to crates/docs_rs_database/migrations/20241018031600_documentation_size.down.sql diff --git a/migrations/20241018031600_documentation_size.up.sql b/crates/docs_rs_database/migrations/20241018031600_documentation_size.up.sql similarity index 100% rename from migrations/20241018031600_documentation_size.up.sql rename to crates/docs_rs_database/migrations/20241018031600_documentation_size.up.sql diff --git a/migrations/20241018052241_builds-rustc-nightly-date.down.sql b/crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql similarity index 100% rename from migrations/20241018052241_builds-rustc-nightly-date.down.sql rename to crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql diff --git a/migrations/20241018052241_builds-rustc-nightly-date.up.sql b/crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql similarity index 100% rename from migrations/20241018052241_builds-rustc-nightly-date.up.sql rename to crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql diff --git a/migrations/20241021050229_builds-started-finished.down.sql b/crates/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql similarity index 100% rename from migrations/20241021050229_builds-started-finished.down.sql rename to crates/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql diff --git a/migrations/20241021050229_builds-started-finished.up.sql b/crates/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql similarity index 100% rename from migrations/20241021050229_builds-started-finished.up.sql rename to crates/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql diff --git a/migrations/20241106085600_releases-rustdoc-status-idx.down.sql b/crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql similarity index 100% rename from migrations/20241106085600_releases-rustdoc-status-idx.down.sql rename to crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql diff --git a/migrations/20241106085600_releases-rustdoc-status-idx.up.sql b/crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql similarity index 100% rename from migrations/20241106085600_releases-rustdoc-status-idx.up.sql rename to crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql diff --git a/migrations/20241219091521_owner-avatar-longer.down.sql b/crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql similarity index 100% rename from migrations/20241219091521_owner-avatar-longer.down.sql rename to crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql diff --git a/migrations/20241219091521_owner-avatar-longer.up.sql b/crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql similarity index 100% rename from migrations/20241219091521_owner-avatar-longer.up.sql rename to crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql diff --git a/migrations/20251202020754_remove-file-public.down.sql b/crates/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql similarity index 100% rename from migrations/20251202020754_remove-file-public.down.sql rename to crates/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql diff --git a/migrations/20251202020754_remove-file-public.up.sql b/crates/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql similarity index 100% rename from migrations/20251202020754_remove-file-public.up.sql rename to crates/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql diff --git a/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql b/crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql similarity index 100% rename from migrations/20251202040858_remove-cdn-invalidation-queue.down.sql rename to crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql diff --git a/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql b/crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql similarity index 100% rename from migrations/20251202040858_remove-cdn-invalidation-queue.up.sql rename to crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql diff --git a/crates/docs_rs_database/src/types/mod.rs b/crates/docs_rs_database/src/types/mod.rs index 71e48173b..4f317cff9 100644 --- a/crates/docs_rs_database/src/types/mod.rs +++ b/crates/docs_rs_database/src/types/mod.rs @@ -1,5 +1,5 @@ use derive_more::{Display, FromStr}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub mod krate_name; pub mod version; @@ -15,3 +15,63 @@ pub struct ReleaseId(pub i32); #[derive(Debug, Clone, Copy, Display, PartialEq, Eq, Hash, Serialize, sqlx::Type)] #[sqlx(transparent)] pub struct BuildId(pub i32); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, sqlx::Type)] +#[sqlx(type_name = "feature")] +pub struct Feature { + pub(crate) name: String, + pub(crate) subfeatures: Vec, +} + +impl Feature { + pub fn new(name: String, subfeatures: Vec) -> Self { + Feature { name, subfeatures } + } + + pub fn is_private(&self) -> bool { + self.name.starts_with('_') + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] +#[sqlx(type_name = "build_status", rename_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum BuildStatus { + Success, + Failure, + InProgress, +} + +impl BuildStatus { + pub fn is_success(&self) -> bool { + matches!(self, BuildStatus::Success) + } +} + +impl PartialEq<&str> for BuildStatus { + fn eq(&self, other: &&str) -> bool { + match self { + Self::Success => *other == "success", + Self::Failure => *other == "failure", + Self::InProgress => *other == "in_progress", + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_case::test_case; + + #[test_case(BuildStatus::Success, "success")] + #[test_case(BuildStatus::Failure, "failure")] + #[test_case(BuildStatus::InProgress, "in_progress")] + fn test_build_status_serialization(status: BuildStatus, expected: &str) { + let serialized = serde_json::to_string(&status).unwrap(); + assert_eq!(serialized, format!("\"{expected}\"")); + assert_eq!( + serde_json::from_str::(&serialized).unwrap(), + status + ); + } +} diff --git a/crates/docs_rs_storage/Cargo.toml b/crates/docs_rs_storage/Cargo.toml index f3b938513..1694e4919 100644 --- a/crates/docs_rs_storage/Cargo.toml +++ b/crates/docs_rs_storage/Cargo.toml @@ -38,3 +38,7 @@ serde_json = { workspace = true } [dev-dependencies] test-case = { workspace = true } + +[[bench]] +name = "compression" +harness = false diff --git a/benches/compression.rs b/crates/docs_rs_storage/benches/compression.rs similarity index 100% rename from benches/compression.rs rename to crates/docs_rs_storage/benches/compression.rs diff --git a/benches/struct.CaptureMatches.html b/crates/docs_rs_storage/benches/struct.CaptureMatches.html similarity index 100% rename from benches/struct.CaptureMatches.html rename to crates/docs_rs_storage/benches/struct.CaptureMatches.html diff --git a/crates/docs_rs_web/Cargo.toml b/crates/docs_rs_web/Cargo.toml new file mode 100644 index 000000000..494a60fef --- /dev/null +++ b/crates/docs_rs_web/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "docs_rs_web" +version = "0.1.0" +edition = "2024" +build = "build.rs" + + +[dependencies] +anyhow = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_utils = { path = "../docs_rs_utils" } +docs_rs_build_queue = { path = "../docs_rs_build_queue" } +docs_rs_storage = { path = "../docs_rs_storage" } +axum = { version = "0.8.1", features = ["macros"] } +askama = { workspace = true } +chrono = { workspace = true } +axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middleware"] } +comrak = { version = "0.48.0", default-features = false } +serde = { workspace = true } +serde_json = { workspace = true } +tracing = { workspace = true } +futures-util = { workspace = true } +http = { workspace = true } +syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } +phf = "0.13.1" + +[build-dependencies] +anyhow = { workspace = true } +syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } +walkdir = { workspace = true } +grass = { version = "0.13.1", default-features = false } +md5 = "0.8.0" +phf_codegen = "0.13" + +[package.metadata.cargo-machete] +ignored = ["phf"] + diff --git a/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax similarity index 100% rename from assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax diff --git a/assets/syntaxes/Extras/TOML/.gitignore b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore similarity index 100% rename from assets/syntaxes/Extras/TOML/.gitignore rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore diff --git a/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences similarity index 100% rename from assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences diff --git a/assets/syntaxes/Extras/TOML/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Extras/TOML/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences diff --git a/assets/syntaxes/Extras/TOML/LICENSE b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE similarity index 100% rename from assets/syntaxes/Extras/TOML/LICENSE rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE diff --git a/assets/syntaxes/Extras/TOML/README.md b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/README.md similarity index 100% rename from assets/syntaxes/Extras/TOML/README.md rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/README.md diff --git a/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Extras/TOML/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences diff --git a/assets/syntaxes/Extras/TOML/TOML.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax similarity index 100% rename from assets/syntaxes/Extras/TOML/TOML.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax diff --git a/assets/syntaxes/Extras/TOML/syntax_test_toml.toml b/crates/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml similarity index 100% rename from assets/syntaxes/Extras/TOML/syntax_test_toml.toml rename to crates/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml diff --git a/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md b/crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md similarity index 100% rename from assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md rename to crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md diff --git a/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md b/crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md similarity index 100% rename from assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md rename to crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md diff --git a/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md b/crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md similarity index 100% rename from assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md rename to crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md diff --git a/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md b/crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md similarity index 100% rename from assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md rename to crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md diff --git a/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md b/crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md similarity index 100% rename from assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md rename to crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md diff --git a/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md b/crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md similarity index 100% rename from assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md rename to crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md diff --git a/assets/syntaxes/Packages/.travis.yml b/crates/docs_rs_web/assets/syntaxes/Packages/.travis.yml similarity index 100% rename from assets/syntaxes/Packages/.travis.yml rename to crates/docs_rs_web/assets/syntaxes/Packages/.travis.yml diff --git a/assets/syntaxes/Packages/ASP/ASP.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/ASP/ASP.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax diff --git a/assets/syntaxes/Packages/ASP/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ASP/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax diff --git a/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ASP/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/ASP/syntax_test_asp.asp b/crates/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp similarity index 100% rename from assets/syntaxes/Packages/ASP/syntax_test_asp.asp rename to crates/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp diff --git a/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build similarity index 100% rename from assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build diff --git a/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax diff --git a/assets/syntaxes/Packages/ActionScript/syntax_test_as.as b/crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as similarity index 100% rename from assets/syntaxes/Packages/ActionScript/syntax_test_as.as rename to crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as diff --git a/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax diff --git a/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/AppleScript/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Batch File/Batch File.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings diff --git a/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax diff --git a/assets/syntaxes/Packages/Batch File/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Batch File/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat b/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat similarity index 100% rename from assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat rename to crates/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat diff --git a/assets/syntaxes/Packages/C#/Build.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/C#/Build.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax diff --git a/assets/syntaxes/Packages/C#/C#.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/C#/C#.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax diff --git a/assets/syntaxes/Packages/C#/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Indentation.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Indentation.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences diff --git a/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences diff --git a/assets/syntaxes/Packages/C#/doc_params.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C#/doc_params.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet diff --git a/assets/syntaxes/Packages/C#/doc_see.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C#/doc_see.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet diff --git a/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C#/doc_summary.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs diff --git a/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs b/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs similarity index 100% rename from assets/syntaxes/Packages/C#/tests/syntax_test_query.cs rename to crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs diff --git a/assets/syntaxes/Packages/C++/C Single File.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build similarity index 100% rename from assets/syntaxes/Packages/C++/C Single File.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build diff --git a/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions diff --git a/assets/syntaxes/Packages/C++/C++ Single File.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build similarity index 100% rename from assets/syntaxes/Packages/C++/C++ Single File.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build diff --git a/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions diff --git a/assets/syntaxes/Packages/C++/C++.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/C++/C++.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings diff --git a/assets/syntaxes/Packages/C++/C++.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/C++/C++.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax diff --git a/assets/syntaxes/Packages/C++/C.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/C++/C.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax diff --git a/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Comments (C++).tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences diff --git a/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/C++/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet diff --git a/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol Index.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences diff --git a/assets/syntaxes/Packages/C++/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/C++/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/C++/syntax_test_accessor.c b/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c similarity index 100% rename from assets/syntaxes/Packages/C++/syntax_test_accessor.c rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c diff --git a/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp b/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp similarity index 100% rename from assets/syntaxes/Packages/C++/syntax_test_accessor.cpp rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp diff --git a/assets/syntaxes/Packages/C++/syntax_test_c.c b/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c similarity index 100% rename from assets/syntaxes/Packages/C++/syntax_test_c.c rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c diff --git a/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp b/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp similarity index 100% rename from assets/syntaxes/Packages/C++/syntax_test_cpp.cpp rename to crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp diff --git a/assets/syntaxes/Packages/CSS/CSS.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/CSS/CSS.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax diff --git a/assets/syntaxes/Packages/CSS/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/CSS/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/CSS/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/CSS/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences diff --git a/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences diff --git a/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/CSS/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/CSS/css_completions.py b/crates/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py similarity index 100% rename from assets/syntaxes/Packages/CSS/css_completions.py rename to crates/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py diff --git a/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Clojure/Clojure.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings diff --git a/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax diff --git a/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences diff --git a/assets/syntaxes/Packages/Clojure/Comment.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Clojure/Comment.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences diff --git a/assets/syntaxes/Packages/Clojure/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/Clojure/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj similarity index 100% rename from assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj diff --git a/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj b/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj similarity index 100% rename from assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj rename to crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj diff --git a/assets/syntaxes/Packages/D/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/D/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/D/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/D/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/D/D dub.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build similarity index 100% rename from assets/syntaxes/Packages/D/D dub.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build diff --git a/assets/syntaxes/Packages/D/D.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build similarity index 100% rename from assets/syntaxes/Packages/D/D.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build diff --git a/assets/syntaxes/Packages/D/D.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/D/D.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax diff --git a/assets/syntaxes/Packages/D/DMD Output.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/D/DMD Output.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax diff --git a/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/D/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/error.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/import.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/info.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/log.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/main.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/return.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/version.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/D/Snippets/while.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet diff --git a/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences diff --git a/assets/syntaxes/Packages/D/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/D/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/D/tests/syntax_test_d.d b/crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d similarity index 100% rename from assets/syntaxes/Packages/D/tests/syntax_test_d.d rename to crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d diff --git a/assets/syntaxes/Packages/D/tests/syntax_test_old.d b/crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d similarity index 100% rename from assets/syntaxes/Packages/D/tests/syntax_test_old.d rename to crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d diff --git a/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d b/crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d similarity index 100% rename from assets/syntaxes/Packages/D/tests/syntax_test_shebang.d rename to crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d diff --git a/assets/syntaxes/Packages/Diff/Context.sublime-menu b/crates/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu similarity index 100% rename from assets/syntaxes/Packages/Diff/Context.sublime-menu rename to crates/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu diff --git a/assets/syntaxes/Packages/Diff/Diff.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Diff/Diff.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax diff --git a/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu b/crates/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu similarity index 100% rename from assets/syntaxes/Packages/Diff/Side Bar.sublime-menu rename to crates/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu diff --git a/assets/syntaxes/Packages/Diff/diff.py b/crates/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py similarity index 100% rename from assets/syntaxes/Packages/Diff/diff.py rename to crates/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py diff --git a/assets/syntaxes/Packages/Diff/syntax_test_diff.diff b/crates/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff similarity index 100% rename from assets/syntaxes/Packages/Diff/syntax_test_diff.diff rename to crates/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff diff --git a/assets/syntaxes/Packages/Erlang/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Erlang.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Erlang/Erlang.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build diff --git a/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Erlang/Erlang.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings diff --git a/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax diff --git a/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions diff --git a/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax diff --git a/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences diff --git a/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl similarity index 100% rename from assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl diff --git a/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws b/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws similarity index 100% rename from assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws rename to crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws diff --git a/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions diff --git a/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings diff --git a/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings diff --git a/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax diff --git a/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_commit rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_config b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_config rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_link b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_link rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_log b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_log rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_merge rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase diff --git a/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag b/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag similarity index 100% rename from assets/syntaxes/Packages/Git Formats/syntax_test_git_tag rename to crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag diff --git a/assets/syntaxes/Packages/Go/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/Go/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/Go/Go.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Go/Go.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions diff --git a/assets/syntaxes/Packages/Go/Go.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Go/Go.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings diff --git a/assets/syntaxes/Packages/Go/Go.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Go/Go.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax diff --git a/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet diff --git a/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet diff --git a/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet diff --git a/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet diff --git a/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences diff --git a/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences diff --git a/assets/syntaxes/Packages/Go/syntax_test_go.go b/crates/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go similarity index 100% rename from assets/syntaxes/Packages/Go/syntax_test_go.go rename to crates/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go diff --git a/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions diff --git a/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions diff --git a/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions diff --git a/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions diff --git a/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax diff --git a/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build diff --git a/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot b/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot similarity index 100% rename from assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot rename to crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot diff --git a/assets/syntaxes/Packages/Groovy/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Groovy/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax diff --git a/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences diff --git a/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences diff --git a/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences diff --git a/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences diff --git a/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy similarity index 100% rename from assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy diff --git a/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy b/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy similarity index 100% rename from assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy rename to crates/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy diff --git a/assets/syntaxes/Packages/HTML/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/HTML/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/HTML/HTML.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/HTML/HTML.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax diff --git a/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet diff --git a/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet diff --git a/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences diff --git a/assets/syntaxes/Packages/HTML/encode_html_entities.py b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py similarity index 100% rename from assets/syntaxes/Packages/HTML/encode_html_entities.py rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py diff --git a/assets/syntaxes/Packages/HTML/html_completions.py b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py similarity index 100% rename from assets/syntaxes/Packages/HTML/html_completions.py rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py diff --git a/assets/syntaxes/Packages/HTML/syntax_test_html.html b/crates/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html similarity index 100% rename from assets/syntaxes/Packages/HTML/syntax_test_html.html rename to crates/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html diff --git a/assets/syntaxes/Packages/Haskell/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Haskell/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Haskell/Haskell.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Haskell/Haskell.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build diff --git a/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax diff --git a/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences diff --git a/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax diff --git a/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet diff --git a/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet diff --git a/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet diff --git a/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet diff --git a/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet diff --git a/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs b/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs similarity index 100% rename from assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs rename to crates/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs diff --git a/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences diff --git a/assets/syntaxes/Packages/JSON/JSON.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/JSON/JSON.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax diff --git a/assets/syntaxes/Packages/JSON/syntax_test_json.json b/crates/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json similarity index 100% rename from assets/syntaxes/Packages/JSON/syntax_test_json.json rename to crates/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json diff --git a/assets/syntaxes/Packages/Java/Ant.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Java/Ant.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build diff --git a/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax diff --git a/assets/syntaxes/Packages/Java/Java.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/Java/Java.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions diff --git a/assets/syntaxes/Packages/Java/Java.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Java/Java.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax diff --git a/assets/syntaxes/Packages/Java/JavaC.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Java/JavaC.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build diff --git a/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax diff --git a/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax diff --git a/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet diff --git a/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences diff --git a/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences diff --git a/assets/syntaxes/Packages/Java/syntax_test_java.java b/crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java similarity index 100% rename from assets/syntaxes/Packages/Java/syntax_test_java.java rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java diff --git a/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties b/crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties similarity index 100% rename from assets/syntaxes/Packages/Java/syntax_test_java_properties.properties rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties diff --git a/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp b/crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp similarity index 100% rename from assets/syntaxes/Packages/Java/syntax_test_jsp.jsp rename to crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp diff --git a/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences diff --git a/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences diff --git a/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax diff --git a/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet diff --git a/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences diff --git a/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js diff --git a/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js b/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js similarity index 100% rename from assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js rename to crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js diff --git a/assets/syntaxes/Packages/LICENSE b/crates/docs_rs_web/assets/syntaxes/Packages/LICENSE similarity index 100% rename from assets/syntaxes/Packages/LICENSE rename to crates/docs_rs_web/assets/syntaxes/Packages/LICENSE diff --git a/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax diff --git a/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax diff --git a/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings diff --git a/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet diff --git a/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences diff --git a/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences diff --git a/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences diff --git a/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax diff --git a/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex b/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex similarity index 100% rename from assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex rename to crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex diff --git a/assets/syntaxes/Packages/Lisp/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Lisp/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax diff --git a/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet diff --git a/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp b/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp similarity index 100% rename from assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp rename to crates/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp diff --git a/assets/syntaxes/Packages/Lua/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Lua/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Lua/Indent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Lua/Indent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences diff --git a/assets/syntaxes/Packages/Lua/Lua.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build similarity index 96% rename from assets/syntaxes/Packages/Lua/Lua.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build index 227422da9..51717eb03 100644 --- a/assets/syntaxes/Packages/Lua/Lua.sublime-build +++ b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build @@ -1,5 +1,5 @@ -{ - "cmd": ["lua", "$file"], - "file_regex": "^(?:lua:)?[\t ](...*?):([0-9]*):?([0-9]*)", - "selector": "source.lua" +{ + "cmd": ["lua", "$file"], + "file_regex": "^(?:lua:)?[\t ](...*?):([0-9]*):?([0-9]*)", + "selector": "source.lua" } \ No newline at end of file diff --git a/assets/syntaxes/Packages/Lua/Lua.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Lua/Lua.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax diff --git a/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet diff --git a/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Lua/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua similarity index 100% rename from assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua diff --git a/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua b/crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua similarity index 100% rename from assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua rename to crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua diff --git a/assets/syntaxes/Packages/Makefile/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Makefile/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax diff --git a/assets/syntaxes/Packages/Makefile/Make.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Makefile/Make.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build diff --git a/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/Makefile/Makefile.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings diff --git a/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax diff --git a/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak b/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak similarity index 100% rename from assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak rename to crates/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak diff --git a/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences diff --git a/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax diff --git a/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax diff --git a/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences diff --git a/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences diff --git a/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md similarity index 100% rename from assets/syntaxes/Packages/Markdown/syntax_test_markdown.md rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md diff --git a/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md b/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md similarity index 100% rename from assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md rename to crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md diff --git a/assets/syntaxes/Packages/Matlab/Indent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Matlab/Indent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences diff --git a/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax diff --git a/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet diff --git a/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Matlab/Symbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences diff --git a/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m b/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m similarity index 100% rename from assets/syntaxes/Packages/Matlab/syntax_test_matlab.m rename to crates/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m diff --git a/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax diff --git a/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax diff --git a/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax diff --git a/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences diff --git a/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax diff --git a/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml b/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml similarity index 100% rename from assets/syntaxes/Packages/OCaml/syntax_test_ml.ml rename to crates/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml diff --git a/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/Objective-C/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax diff --git a/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax diff --git a/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences diff --git a/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences diff --git a/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m similarity index 100% rename from assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m diff --git a/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm similarity index 100% rename from assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm diff --git a/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm similarity index 100% rename from assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm diff --git a/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m b/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m similarity index 100% rename from assets/syntaxes/Packages/Objective-C/syntax_test_objc.m rename to crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m diff --git a/assets/syntaxes/Packages/PHP/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/PHP/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences diff --git a/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences diff --git a/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax diff --git a/assets/syntaxes/Packages/PHP/PHP.sublime-completions b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions similarity index 100% rename from assets/syntaxes/Packages/PHP/PHP.sublime-completions rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions diff --git a/assets/syntaxes/Packages/PHP/PHP.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/PHP/PHP.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax diff --git a/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax diff --git a/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet diff --git a/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/PHP/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/PHP/syntax_test_php.php b/crates/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php similarity index 100% rename from assets/syntaxes/Packages/PHP/syntax_test_php.php rename to crates/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php diff --git a/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax diff --git a/assets/syntaxes/Packages/Pascal/syntax_test.pas b/crates/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas similarity index 100% rename from assets/syntaxes/Packages/Pascal/syntax_test.pas rename to crates/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas diff --git a/assets/syntaxes/Packages/Perl/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Perl/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Perl/Perl.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Perl/Perl.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build diff --git a/assets/syntaxes/Packages/Perl/Perl.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Perl/Perl.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet diff --git a/assets/syntaxes/Packages/Perl/syntax_test_perl.pl b/crates/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl similarity index 100% rename from assets/syntaxes/Packages/Perl/syntax_test_perl.pl rename to crates/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl diff --git a/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Python/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Python/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/Python/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/Python/Python.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Python/Python.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build diff --git a/assets/syntaxes/Packages/Python/Python.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Python/Python.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax diff --git a/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax diff --git a/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet diff --git a/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Python/Symbol Index.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences diff --git a/assets/syntaxes/Packages/Python/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Python/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/Python/syntax_test_python.py b/crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py similarity index 100% rename from assets/syntaxes/Packages/Python/syntax_test_python.py rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py diff --git a/assets/syntaxes/Packages/Python/syntax_test_python_strings.py b/crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py similarity index 100% rename from assets/syntaxes/Packages/Python/syntax_test_python_strings.py rename to crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py diff --git a/assets/syntaxes/Packages/R/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/R/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/R/R Console.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/R/R Console.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax diff --git a/assets/syntaxes/Packages/R/R.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build similarity index 100% rename from assets/syntaxes/Packages/R/R.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build diff --git a/assets/syntaxes/Packages/R/R.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/R/R.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings diff --git a/assets/syntaxes/Packages/R/R.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/R/R.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax diff --git a/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax diff --git a/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet diff --git a/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences diff --git a/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences similarity index 100% rename from assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences diff --git a/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences diff --git a/assets/syntaxes/Packages/R/syntax_test_r.R b/crates/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R similarity index 100% rename from assets/syntaxes/Packages/R/syntax_test_r.R rename to crates/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R diff --git a/assets/syntaxes/Packages/README.md b/crates/docs_rs_web/assets/syntaxes/Packages/README.md similarity index 100% rename from assets/syntaxes/Packages/README.md rename to crates/docs_rs_web/assets/syntaxes/Packages/README.md diff --git a/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax diff --git a/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax diff --git a/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax diff --git a/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax diff --git a/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax diff --git a/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet diff --git a/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences diff --git a/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences diff --git a/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb similarity index 100% rename from assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb diff --git a/assets/syntaxes/Packages/Rails/syntax_test_rails.rb b/crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb similarity index 100% rename from assets/syntaxes/Packages/Rails/syntax_test_rails.rb rename to crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb diff --git a/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax diff --git a/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re b/crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re similarity index 100% rename from assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re rename to crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re diff --git a/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax diff --git a/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst b/crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst similarity index 100% rename from assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst rename to crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst diff --git a/assets/syntaxes/Packages/Ruby/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Ruby/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/Ruby/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/Ruby/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/Ruby/Ruby.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Ruby/Ruby.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build diff --git a/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax diff --git a/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet diff --git a/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences diff --git a/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences diff --git a/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb b/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb similarity index 100% rename from assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb rename to crates/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb diff --git a/assets/syntaxes/Packages/Rust/Cargo.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Rust/Cargo.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build diff --git a/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rust/Cargo.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax diff --git a/assets/syntaxes/Packages/Rust/Default.sublime-keymap b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap similarity index 100% rename from assets/syntaxes/Packages/Rust/Default.sublime-keymap rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap diff --git a/assets/syntaxes/Packages/Rust/LICENSE.txt b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt similarity index 100% rename from assets/syntaxes/Packages/Rust/LICENSE.txt rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt diff --git a/assets/syntaxes/Packages/Rust/Rust.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build similarity index 100% rename from assets/syntaxes/Packages/Rust/Rust.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build diff --git a/assets/syntaxes/Packages/Rust/Rust.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Rust/Rust.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax diff --git a/assets/syntaxes/Packages/Rust/RustComment.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Rust/RustComment.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences diff --git a/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Rust/RustIndent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences diff --git a/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences diff --git a/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet diff --git a/assets/syntaxes/Packages/Rust/syntax_test_rust.rs b/crates/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs similarity index 100% rename from assets/syntaxes/Packages/Rust/syntax_test_rust.rs rename to crates/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs diff --git a/assets/syntaxes/Packages/SQL/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/SQL/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/SQL/SQL.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/SQL/SQL.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax diff --git a/assets/syntaxes/Packages/SQL/syntax_test_sql.sql b/crates/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql similarity index 100% rename from assets/syntaxes/Packages/SQL/syntax_test_sql.sql rename to crates/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql diff --git a/assets/syntaxes/Packages/Scala/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Indent-case.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Indent.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Indent.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Scala.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Scala/Scala.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax diff --git a/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet diff --git a/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/Symbols.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/Scala/Symbols.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences diff --git a/assets/syntaxes/Packages/Scala/info.plist b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist similarity index 100% rename from assets/syntaxes/Packages/Scala/info.plist rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist diff --git a/assets/syntaxes/Packages/Scala/syntax_test_scala.scala b/crates/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala similarity index 100% rename from assets/syntaxes/Packages/Scala/syntax_test_scala.scala rename to crates/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala diff --git a/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax diff --git a/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/Makefile b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Makefile rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile diff --git a/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax diff --git a/assets/syntaxes/Packages/ShellScript/ShellScript.py b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py similarity index 100% rename from assets/syntaxes/Packages/ShellScript/ShellScript.py rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py diff --git a/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build similarity index 100% rename from assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet diff --git a/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences diff --git a/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax diff --git a/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml similarity index 100% rename from assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml diff --git a/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh similarity index 100% rename from assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh diff --git a/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh similarity index 100% rename from assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh diff --git a/assets/syntaxes/Packages/ShellScript/tools/update-commands.py b/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py similarity index 100% rename from assets/syntaxes/Packages/ShellScript/tools/update-commands.py rename to crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py diff --git a/assets/syntaxes/Packages/TCL/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/TCL/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax diff --git a/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet diff --git a/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet diff --git a/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet diff --git a/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet diff --git a/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet diff --git a/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet diff --git a/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences diff --git a/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/TCL/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/TCL/Tcl.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax diff --git a/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl b/crates/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl similarity index 100% rename from assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl rename to crates/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl diff --git a/assets/syntaxes/Packages/Text/Plain text.tmLanguage b/crates/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage similarity index 100% rename from assets/syntaxes/Packages/Text/Plain text.tmLanguage rename to crates/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage diff --git a/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet diff --git a/assets/syntaxes/Packages/Textile/Textile.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/Textile/Textile.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax diff --git a/assets/syntaxes/Packages/Textile/syntax_test_textile.textile b/crates/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile similarity index 100% rename from assets/syntaxes/Packages/Textile/syntax_test_textile.textile rename to crates/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile diff --git a/assets/syntaxes/Packages/XML/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/XML/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences diff --git a/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet diff --git a/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet diff --git a/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet diff --git a/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet diff --git a/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet diff --git a/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet b/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet similarity index 100% rename from assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet diff --git a/assets/syntaxes/Packages/XML/XML.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/XML/XML.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings diff --git a/assets/syntaxes/Packages/XML/XML.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/XML/XML.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax diff --git a/assets/syntaxes/Packages/XML/syntax_test_xml.xml b/crates/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml similarity index 100% rename from assets/syntaxes/Packages/XML/syntax_test_xml.xml rename to crates/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml diff --git a/assets/syntaxes/Packages/YAML/Comments.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/YAML/Comments.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences diff --git a/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences diff --git a/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences similarity index 100% rename from assets/syntaxes/Packages/YAML/Symbol List.tmPreferences rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences diff --git a/assets/syntaxes/Packages/YAML/YAML.sublime-settings b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings similarity index 100% rename from assets/syntaxes/Packages/YAML/YAML.sublime-settings rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings diff --git a/assets/syntaxes/Packages/YAML/YAML.sublime-syntax b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax similarity index 100% rename from assets/syntaxes/Packages/YAML/YAML.sublime-syntax rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax diff --git a/assets/syntaxes/Packages/YAML/preview.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/preview.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml diff --git a/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml b/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml similarity index 100% rename from assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml rename to crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml diff --git a/build.rs b/crates/docs_rs_web/build.rs similarity index 100% rename from build.rs rename to crates/docs_rs_web/build.rs diff --git a/src/web/build_details.rs b/crates/docs_rs_web/src/build_details.rs similarity index 97% rename from src/web/build_details.rs rename to crates/docs_rs_web/src/build_details.rs index 2a6a84317..38a7380e3 100644 --- a/src/web/build_details.rs +++ b/crates/docs_rs_web/src/build_details.rs @@ -1,23 +1,18 @@ use crate::{ - Config, - db::types::BuildStatus, - impl_axum_webpage, - web::{ - MetaData, - cache::CachePolicy, - error::{AxumNope, AxumResult}, - extractors::{DbConnection, Path, rustdoc::RustdocParams}, - file::File, - filters, match_version, - page::templates::{RenderBrands, RenderRegular, RenderSolid}, - }, + Config, MetaData, + cache::CachePolicy, + error::{AxumNope, AxumResult}, + extractors::{DbConnection, Path, rustdoc::RustdocParams}, + file::File, + filters, match_version, + page::templates::{RenderBrands, RenderRegular, RenderSolid}, }; use anyhow::Context as _; use askama::Template; use axum::{extract::Extension, response::IntoResponse}; use chrono::{DateTime, Utc}; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; -use docs_rs_database::types::{BuildId, version::Version}; +use docs_rs_database::types::{BuildId, BuildStatus, version::Version}; use docs_rs_storage::AsyncStorage; use docs_rs_utils::BUILD_VERSION; use futures_util::TryStreamExt; diff --git a/src/web/builds.rs b/crates/docs_rs_web/src/builds.rs similarity index 100% rename from src/web/builds.rs rename to crates/docs_rs_web/src/builds.rs diff --git a/src/web/cache.rs b/crates/docs_rs_web/src/cache.rs similarity index 100% rename from src/web/cache.rs rename to crates/docs_rs_web/src/cache.rs diff --git a/src/web/crate_details.rs b/crates/docs_rs_web/src/crate_details.rs similarity index 100% rename from src/web/crate_details.rs rename to crates/docs_rs_web/src/crate_details.rs diff --git a/src/web/csp.rs b/crates/docs_rs_web/src/csp.rs similarity index 100% rename from src/web/csp.rs rename to crates/docs_rs_web/src/csp.rs diff --git a/src/web/error.rs b/crates/docs_rs_web/src/error.rs similarity index 100% rename from src/web/error.rs rename to crates/docs_rs_web/src/error.rs diff --git a/src/web/extractors/context.rs b/crates/docs_rs_web/src/extractors/context.rs similarity index 100% rename from src/web/extractors/context.rs rename to crates/docs_rs_web/src/extractors/context.rs diff --git a/src/web/extractors/mod.rs b/crates/docs_rs_web/src/extractors/mod.rs similarity index 100% rename from src/web/extractors/mod.rs rename to crates/docs_rs_web/src/extractors/mod.rs diff --git a/src/web/extractors/path.rs b/crates/docs_rs_web/src/extractors/path.rs similarity index 100% rename from src/web/extractors/path.rs rename to crates/docs_rs_web/src/extractors/path.rs diff --git a/src/web/extractors/rustdoc.rs b/crates/docs_rs_web/src/extractors/rustdoc.rs similarity index 100% rename from src/web/extractors/rustdoc.rs rename to crates/docs_rs_web/src/extractors/rustdoc.rs diff --git a/src/web/features.rs b/crates/docs_rs_web/src/features.rs similarity index 100% rename from src/web/features.rs rename to crates/docs_rs_web/src/features.rs diff --git a/src/web/file.rs b/crates/docs_rs_web/src/file.rs similarity index 100% rename from src/web/file.rs rename to crates/docs_rs_web/src/file.rs diff --git a/src/web/highlight.rs b/crates/docs_rs_web/src/highlight.rs similarity index 100% rename from src/web/highlight.rs rename to crates/docs_rs_web/src/highlight.rs diff --git a/src/web/mod.rs b/crates/docs_rs_web/src/lib.rs similarity index 99% rename from src/web/mod.rs rename to crates/docs_rs_web/src/lib.rs index 532b8e12f..a44e5ecbf 100644 --- a/src/web/mod.rs +++ b/crates/docs_rs_web/src/lib.rs @@ -2,14 +2,14 @@ pub mod page; -use crate::{ - db::types::BuildStatus, - utils::get_correct_docsrs_style_file, - web::{ - metrics::WebMetrics, - page::templates::{RenderBrands, RenderSolid, filters}, - }, -}; +use docs_rs_database::types::BuildStatus; +// use crate::{ +// utils::get_correct_docsrs_style_file, +// web::{ +// metrics::WebMetrics, +// page::templates::{RenderBrands, RenderSolid, filters}, +// }, +// }; use anyhow::{Context as _, Result, anyhow, bail}; use askama::Template; use axum_extra::middleware::option_layer; diff --git a/src/web/licenses.rs b/crates/docs_rs_web/src/licenses.rs similarity index 100% rename from src/web/licenses.rs rename to crates/docs_rs_web/src/licenses.rs diff --git a/src/web/markdown.rs b/crates/docs_rs_web/src/markdown.rs similarity index 100% rename from src/web/markdown.rs rename to crates/docs_rs_web/src/markdown.rs diff --git a/src/web/metrics.rs b/crates/docs_rs_web/src/metrics.rs similarity index 94% rename from src/web/metrics.rs rename to crates/docs_rs_web/src/metrics.rs index d1dc67020..fdbd3953d 100644 --- a/src/web/metrics.rs +++ b/crates/docs_rs_web/src/metrics.rs @@ -1,4 +1,3 @@ -use crate::metrics::RESPONSE_TIME_HISTOGRAM_BUCKETS; use axum::{ extract::{MatchedPath, Request as AxumRequest}, http::StatusCode, @@ -12,6 +11,18 @@ use opentelemetry::{ }; use std::{borrow::Cow, sync::Arc, time::Instant}; +/// response time histogram buckets from the opentelemetry semantiv conventions +/// https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestduration +/// +/// These are the default prometheus bucket sizes, +/// https://docs.rs/prometheus/0.14.0/src/prometheus/histogram.rs.html#25-27 +/// tailored to broadly measure the response time (in seconds) of a network service. +/// +/// Otel default buckets are not suited for that. +pub const RESPONSE_TIME_HISTOGRAM_BUCKETS: &[f64] = &[ + 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0, +]; + #[derive(Debug)] pub(crate) struct WebMetrics { pub(crate) html_rewrite_ooms: Counter, diff --git a/src/web/page/mod.rs b/crates/docs_rs_web/src/page/mod.rs similarity index 100% rename from src/web/page/mod.rs rename to crates/docs_rs_web/src/page/mod.rs diff --git a/src/web/page/templates.rs b/crates/docs_rs_web/src/page/templates.rs similarity index 99% rename from src/web/page/templates.rs rename to crates/docs_rs_web/src/page/templates.rs index 77593a8f7..6b1e98f07 100644 --- a/src/web/page/templates.rs +++ b/crates/docs_rs_web/src/page/templates.rs @@ -1,5 +1,4 @@ -use crate::error::Result; -use crate::web::rustdoc::RustdocPage; +use crate::rustdoc::RustdocPage; use anyhow::Context; use askama::Template; use std::sync::Arc; diff --git a/src/web/page/web_page.rs b/crates/docs_rs_web/src/page/web_page.rs similarity index 98% rename from src/web/page/web_page.rs rename to crates/docs_rs_web/src/page/web_page.rs index 71c6bbbeb..ef60fc6f4 100644 --- a/src/web/page/web_page.rs +++ b/crates/docs_rs_web/src/page/web_page.rs @@ -1,4 +1,4 @@ -use crate::web::{TemplateData, csp::Csp, error::AxumNope}; +use crate::{TemplateData, csp::Csp, error::AxumNope}; use axum::{ body::Body, extract::Request as AxumRequest, @@ -13,7 +13,6 @@ pub(crate) trait AddCspNonce: IntoResponse { fn render_with_csp_nonce(&mut self, csp_nonce: String) -> askama::Result; } -#[macro_export] macro_rules! impl_axum_webpage { ( $page:ty diff --git a/src/web/releases.rs b/crates/docs_rs_web/src/releases.rs similarity index 100% rename from src/web/releases.rs rename to crates/docs_rs_web/src/releases.rs diff --git a/src/web/routes.rs b/crates/docs_rs_web/src/routes.rs similarity index 100% rename from src/web/routes.rs rename to crates/docs_rs_web/src/routes.rs diff --git a/src/web/rustdoc.rs b/crates/docs_rs_web/src/rustdoc.rs similarity index 100% rename from src/web/rustdoc.rs rename to crates/docs_rs_web/src/rustdoc.rs diff --git a/src/web/sitemap.rs b/crates/docs_rs_web/src/sitemap.rs similarity index 100% rename from src/web/sitemap.rs rename to crates/docs_rs_web/src/sitemap.rs diff --git a/src/web/source.rs b/crates/docs_rs_web/src/source.rs similarity index 100% rename from src/web/source.rs rename to crates/docs_rs_web/src/source.rs diff --git a/src/web/statics.rs b/crates/docs_rs_web/src/statics.rs similarity index 100% rename from src/web/statics.rs rename to crates/docs_rs_web/src/statics.rs diff --git a/src/web/status.rs b/crates/docs_rs_web/src/status.rs similarity index 100% rename from src/web/status.rs rename to crates/docs_rs_web/src/status.rs diff --git a/static/FiraSans-LICENSE.txt b/crates/docs_rs_web/static/FiraSans-LICENSE.txt similarity index 100% rename from static/FiraSans-LICENSE.txt rename to crates/docs_rs_web/static/FiraSans-LICENSE.txt diff --git a/static/FiraSans-Medium.woff b/crates/docs_rs_web/static/FiraSans-Medium.woff similarity index 100% rename from static/FiraSans-Medium.woff rename to crates/docs_rs_web/static/FiraSans-Medium.woff diff --git a/static/FiraSans-Medium.woff2 b/crates/docs_rs_web/static/FiraSans-Medium.woff2 similarity index 100% rename from static/FiraSans-Medium.woff2 rename to crates/docs_rs_web/static/FiraSans-Medium.woff2 diff --git a/static/FiraSans-Regular.woff b/crates/docs_rs_web/static/FiraSans-Regular.woff similarity index 100% rename from static/FiraSans-Regular.woff rename to crates/docs_rs_web/static/FiraSans-Regular.woff diff --git a/static/FiraSans-Regular.woff2 b/crates/docs_rs_web/static/FiraSans-Regular.woff2 similarity index 100% rename from static/FiraSans-Regular.woff2 rename to crates/docs_rs_web/static/FiraSans-Regular.woff2 diff --git a/static/SourceCodePro-It.ttf.woff b/crates/docs_rs_web/static/SourceCodePro-It.ttf.woff similarity index 100% rename from static/SourceCodePro-It.ttf.woff rename to crates/docs_rs_web/static/SourceCodePro-It.ttf.woff diff --git a/static/SourceCodePro-It.ttf.woff2 b/crates/docs_rs_web/static/SourceCodePro-It.ttf.woff2 similarity index 100% rename from static/SourceCodePro-It.ttf.woff2 rename to crates/docs_rs_web/static/SourceCodePro-It.ttf.woff2 diff --git a/static/SourceCodePro-LICENSE.md b/crates/docs_rs_web/static/SourceCodePro-LICENSE.md similarity index 100% rename from static/SourceCodePro-LICENSE.md rename to crates/docs_rs_web/static/SourceCodePro-LICENSE.md diff --git a/static/SourceCodePro-Regular.ttf.woff b/crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff similarity index 100% rename from static/SourceCodePro-Regular.ttf.woff rename to crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff diff --git a/static/SourceCodePro-Regular.ttf.woff2 b/crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 similarity index 100% rename from static/SourceCodePro-Regular.ttf.woff2 rename to crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 diff --git a/static/SourceCodePro-Semibold.ttf.woff b/crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff similarity index 100% rename from static/SourceCodePro-Semibold.ttf.woff rename to crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff diff --git a/static/SourceCodePro-Semibold.ttf.woff2 b/crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 similarity index 100% rename from static/SourceCodePro-Semibold.ttf.woff2 rename to crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 diff --git a/static/SourceSerif4-Bold.ttf.woff b/crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff similarity index 100% rename from static/SourceSerif4-Bold.ttf.woff rename to crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff diff --git a/static/SourceSerif4-Bold.ttf.woff2 b/crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 similarity index 100% rename from static/SourceSerif4-Bold.ttf.woff2 rename to crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 diff --git a/static/SourceSerif4-It.ttf.woff b/crates/docs_rs_web/static/SourceSerif4-It.ttf.woff similarity index 100% rename from static/SourceSerif4-It.ttf.woff rename to crates/docs_rs_web/static/SourceSerif4-It.ttf.woff diff --git a/static/SourceSerif4-It.ttf.woff2 b/crates/docs_rs_web/static/SourceSerif4-It.ttf.woff2 similarity index 100% rename from static/SourceSerif4-It.ttf.woff2 rename to crates/docs_rs_web/static/SourceSerif4-It.ttf.woff2 diff --git a/static/SourceSerif4-LICENSE.md b/crates/docs_rs_web/static/SourceSerif4-LICENSE.md similarity index 100% rename from static/SourceSerif4-LICENSE.md rename to crates/docs_rs_web/static/SourceSerif4-LICENSE.md diff --git a/static/SourceSerif4-Regular.ttf.woff b/crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff similarity index 100% rename from static/SourceSerif4-Regular.ttf.woff rename to crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff diff --git a/static/SourceSerif4-Regular.ttf.woff2 b/crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 similarity index 100% rename from static/SourceSerif4-Regular.ttf.woff2 rename to crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 diff --git a/static/clipboard.svg b/crates/docs_rs_web/static/clipboard.svg similarity index 100% rename from static/clipboard.svg rename to crates/docs_rs_web/static/clipboard.svg diff --git a/static/fa-brands-400.ttf b/crates/docs_rs_web/static/fa-brands-400.ttf similarity index 100% rename from static/fa-brands-400.ttf rename to crates/docs_rs_web/static/fa-brands-400.ttf diff --git a/static/fa-brands-400.woff2 b/crates/docs_rs_web/static/fa-brands-400.woff2 similarity index 100% rename from static/fa-brands-400.woff2 rename to crates/docs_rs_web/static/fa-brands-400.woff2 diff --git a/static/fa-regular-400.ttf b/crates/docs_rs_web/static/fa-regular-400.ttf similarity index 100% rename from static/fa-regular-400.ttf rename to crates/docs_rs_web/static/fa-regular-400.ttf diff --git a/static/fa-regular-400.woff2 b/crates/docs_rs_web/static/fa-regular-400.woff2 similarity index 100% rename from static/fa-regular-400.woff2 rename to crates/docs_rs_web/static/fa-regular-400.woff2 diff --git a/static/fa-solid-900.ttf b/crates/docs_rs_web/static/fa-solid-900.ttf similarity index 100% rename from static/fa-solid-900.ttf rename to crates/docs_rs_web/static/fa-solid-900.ttf diff --git a/static/fa-solid-900.woff2 b/crates/docs_rs_web/static/fa-solid-900.woff2 similarity index 100% rename from static/fa-solid-900.woff2 rename to crates/docs_rs_web/static/fa-solid-900.woff2 diff --git a/static/fa-v4compatibility.ttf b/crates/docs_rs_web/static/fa-v4compatibility.ttf similarity index 100% rename from static/fa-v4compatibility.ttf rename to crates/docs_rs_web/static/fa-v4compatibility.ttf diff --git a/static/fa-v4compatibility.woff2 b/crates/docs_rs_web/static/fa-v4compatibility.woff2 similarity index 100% rename from static/fa-v4compatibility.woff2 rename to crates/docs_rs_web/static/fa-v4compatibility.woff2 diff --git a/static/favicon.ico b/crates/docs_rs_web/static/favicon.ico similarity index 100% rename from static/favicon.ico rename to crates/docs_rs_web/static/favicon.ico diff --git a/static/index.js b/crates/docs_rs_web/static/index.js similarity index 100% rename from static/index.js rename to crates/docs_rs_web/static/index.js diff --git a/static/keyboard.js b/crates/docs_rs_web/static/keyboard.js similarity index 100% rename from static/keyboard.js rename to crates/docs_rs_web/static/keyboard.js diff --git a/static/menu.js b/crates/docs_rs_web/static/menu.js similarity index 100% rename from static/menu.js rename to crates/docs_rs_web/static/menu.js diff --git a/static/opensearch.xml b/crates/docs_rs_web/static/opensearch.xml similarity index 100% rename from static/opensearch.xml rename to crates/docs_rs_web/static/opensearch.xml diff --git a/static/robots.txt b/crates/docs_rs_web/static/robots.txt similarity index 100% rename from static/robots.txt rename to crates/docs_rs_web/static/robots.txt diff --git a/static/source.js b/crates/docs_rs_web/static/source.js similarity index 100% rename from static/source.js rename to crates/docs_rs_web/static/source.js diff --git a/static/trigger-rebuild.png b/crates/docs_rs_web/static/trigger-rebuild.png similarity index 100% rename from static/trigger-rebuild.png rename to crates/docs_rs_web/static/trigger-rebuild.png diff --git a/templates/about-base.html b/crates/docs_rs_web/templates/about-base.html similarity index 100% rename from templates/about-base.html rename to crates/docs_rs_web/templates/about-base.html diff --git a/templates/base.html b/crates/docs_rs_web/templates/base.html similarity index 100% rename from templates/base.html rename to crates/docs_rs_web/templates/base.html diff --git a/templates/core/Cargo.toml.example b/crates/docs_rs_web/templates/core/Cargo.toml.example similarity index 100% rename from templates/core/Cargo.toml.example rename to crates/docs_rs_web/templates/core/Cargo.toml.example diff --git a/templates/core/about/badges.html b/crates/docs_rs_web/templates/core/about/badges.html similarity index 100% rename from templates/core/about/badges.html rename to crates/docs_rs_web/templates/core/about/badges.html diff --git a/templates/core/about/builds.html b/crates/docs_rs_web/templates/core/about/builds.html similarity index 100% rename from templates/core/about/builds.html rename to crates/docs_rs_web/templates/core/about/builds.html diff --git a/templates/core/about/download.html b/crates/docs_rs_web/templates/core/about/download.html similarity index 100% rename from templates/core/about/download.html rename to crates/docs_rs_web/templates/core/about/download.html diff --git a/templates/core/about/index.html b/crates/docs_rs_web/templates/core/about/index.html similarity index 100% rename from templates/core/about/index.html rename to crates/docs_rs_web/templates/core/about/index.html diff --git a/templates/core/about/metadata.html b/crates/docs_rs_web/templates/core/about/metadata.html similarity index 100% rename from templates/core/about/metadata.html rename to crates/docs_rs_web/templates/core/about/metadata.html diff --git a/templates/core/about/redirections.html b/crates/docs_rs_web/templates/core/about/redirections.html similarity index 100% rename from templates/core/about/redirections.html rename to crates/docs_rs_web/templates/core/about/redirections.html diff --git a/templates/core/about/rustdoc-json.html b/crates/docs_rs_web/templates/core/about/rustdoc-json.html similarity index 100% rename from templates/core/about/rustdoc-json.html rename to crates/docs_rs_web/templates/core/about/rustdoc-json.html diff --git a/templates/core/home.html b/crates/docs_rs_web/templates/core/home.html similarity index 100% rename from templates/core/home.html rename to crates/docs_rs_web/templates/core/home.html diff --git a/templates/core/sitemap/_item.xml b/crates/docs_rs_web/templates/core/sitemap/_item.xml similarity index 100% rename from templates/core/sitemap/_item.xml rename to crates/docs_rs_web/templates/core/sitemap/_item.xml diff --git a/templates/core/sitemap/index.xml b/crates/docs_rs_web/templates/core/sitemap/index.xml similarity index 100% rename from templates/core/sitemap/index.xml rename to crates/docs_rs_web/templates/core/sitemap/index.xml diff --git a/templates/crate/build_details.html b/crates/docs_rs_web/templates/crate/build_details.html similarity index 100% rename from templates/crate/build_details.html rename to crates/docs_rs_web/templates/crate/build_details.html diff --git a/templates/crate/builds.html b/crates/docs_rs_web/templates/crate/builds.html similarity index 100% rename from templates/crate/builds.html rename to crates/docs_rs_web/templates/crate/builds.html diff --git a/templates/crate/details.html b/crates/docs_rs_web/templates/crate/details.html similarity index 100% rename from templates/crate/details.html rename to crates/docs_rs_web/templates/crate/details.html diff --git a/templates/crate/features.html b/crates/docs_rs_web/templates/crate/features.html similarity index 100% rename from templates/crate/features.html rename to crates/docs_rs_web/templates/crate/features.html diff --git a/templates/crate/source.html b/crates/docs_rs_web/templates/crate/source.html similarity index 100% rename from templates/crate/source.html rename to crates/docs_rs_web/templates/crate/source.html diff --git a/templates/error.html b/crates/docs_rs_web/templates/error.html similarity index 100% rename from templates/error.html rename to crates/docs_rs_web/templates/error.html diff --git a/templates/header/global_alert.html b/crates/docs_rs_web/templates/header/global_alert.html similarity index 100% rename from templates/header/global_alert.html rename to crates/docs_rs_web/templates/header/global_alert.html diff --git a/templates/header/package_navigation.html b/crates/docs_rs_web/templates/header/package_navigation.html similarity index 100% rename from templates/header/package_navigation.html rename to crates/docs_rs_web/templates/header/package_navigation.html diff --git a/templates/header/topbar.html b/crates/docs_rs_web/templates/header/topbar.html similarity index 100% rename from templates/header/topbar.html rename to crates/docs_rs_web/templates/header/topbar.html diff --git a/templates/header/topbar_begin.html b/crates/docs_rs_web/templates/header/topbar_begin.html similarity index 100% rename from templates/header/topbar_begin.html rename to crates/docs_rs_web/templates/header/topbar_begin.html diff --git a/templates/header/topbar_end.html b/crates/docs_rs_web/templates/header/topbar_end.html similarity index 100% rename from templates/header/topbar_end.html rename to crates/docs_rs_web/templates/header/topbar_end.html diff --git a/templates/macros.html b/crates/docs_rs_web/templates/macros.html similarity index 100% rename from templates/macros.html rename to crates/docs_rs_web/templates/macros.html diff --git a/templates/releases/activity.html b/crates/docs_rs_web/templates/releases/activity.html similarity index 100% rename from templates/releases/activity.html rename to crates/docs_rs_web/templates/releases/activity.html diff --git a/templates/releases/build_queue.html b/crates/docs_rs_web/templates/releases/build_queue.html similarity index 100% rename from templates/releases/build_queue.html rename to crates/docs_rs_web/templates/releases/build_queue.html diff --git a/templates/releases/feed.xml b/crates/docs_rs_web/templates/releases/feed.xml similarity index 100% rename from templates/releases/feed.xml rename to crates/docs_rs_web/templates/releases/feed.xml diff --git a/templates/releases/header.html b/crates/docs_rs_web/templates/releases/header.html similarity index 100% rename from templates/releases/header.html rename to crates/docs_rs_web/templates/releases/header.html diff --git a/templates/releases/releases.html b/crates/docs_rs_web/templates/releases/releases.html similarity index 100% rename from templates/releases/releases.html rename to crates/docs_rs_web/templates/releases/releases.html diff --git a/templates/releases/search_results.html b/crates/docs_rs_web/templates/releases/search_results.html similarity index 100% rename from templates/releases/search_results.html rename to crates/docs_rs_web/templates/releases/search_results.html diff --git a/templates/rustdoc/body.html b/crates/docs_rs_web/templates/rustdoc/body.html similarity index 100% rename from templates/rustdoc/body.html rename to crates/docs_rs_web/templates/rustdoc/body.html diff --git a/templates/rustdoc/head.html b/crates/docs_rs_web/templates/rustdoc/head.html similarity index 100% rename from templates/rustdoc/head.html rename to crates/docs_rs_web/templates/rustdoc/head.html diff --git a/templates/rustdoc/platforms.html b/crates/docs_rs_web/templates/rustdoc/platforms.html similarity index 100% rename from templates/rustdoc/platforms.html rename to crates/docs_rs_web/templates/rustdoc/platforms.html diff --git a/templates/rustdoc/releases.html b/crates/docs_rs_web/templates/rustdoc/releases.html similarity index 100% rename from templates/rustdoc/releases.html rename to crates/docs_rs_web/templates/rustdoc/releases.html diff --git a/templates/rustdoc/topbar.html b/crates/docs_rs_web/templates/rustdoc/topbar.html similarity index 100% rename from templates/rustdoc/topbar.html rename to crates/docs_rs_web/templates/rustdoc/topbar.html diff --git a/templates/rustdoc/vendored.html b/crates/docs_rs_web/templates/rustdoc/vendored.html similarity index 100% rename from templates/rustdoc/vendored.html rename to crates/docs_rs_web/templates/rustdoc/vendored.html diff --git a/templates/storage-change-detection.html b/crates/docs_rs_web/templates/storage-change-detection.html similarity index 100% rename from templates/storage-change-detection.html rename to crates/docs_rs_web/templates/storage-change-detection.html diff --git a/templates/style/_navbar.scss b/crates/docs_rs_web/templates/style/_navbar.scss similarity index 100% rename from templates/style/_navbar.scss rename to crates/docs_rs_web/templates/style/_navbar.scss diff --git a/templates/style/_rustdoc-common.scss b/crates/docs_rs_web/templates/style/_rustdoc-common.scss similarity index 100% rename from templates/style/_rustdoc-common.scss rename to crates/docs_rs_web/templates/style/_rustdoc-common.scss diff --git a/templates/style/_syntax-themes.scss b/crates/docs_rs_web/templates/style/_syntax-themes.scss similarity index 100% rename from templates/style/_syntax-themes.scss rename to crates/docs_rs_web/templates/style/_syntax-themes.scss diff --git a/templates/style/_syntax.scss b/crates/docs_rs_web/templates/style/_syntax.scss similarity index 100% rename from templates/style/_syntax.scss rename to crates/docs_rs_web/templates/style/_syntax.scss diff --git a/templates/style/_themes.scss b/crates/docs_rs_web/templates/style/_themes.scss similarity index 100% rename from templates/style/_themes.scss rename to crates/docs_rs_web/templates/style/_themes.scss diff --git a/templates/style/_utils.scss b/crates/docs_rs_web/templates/style/_utils.scss similarity index 100% rename from templates/style/_utils.scss rename to crates/docs_rs_web/templates/style/_utils.scss diff --git a/templates/style/_vars.scss b/crates/docs_rs_web/templates/style/_vars.scss similarity index 100% rename from templates/style/_vars.scss rename to crates/docs_rs_web/templates/style/_vars.scss diff --git a/templates/style/rustdoc-2021-12-05.scss b/crates/docs_rs_web/templates/style/rustdoc-2021-12-05.scss similarity index 100% rename from templates/style/rustdoc-2021-12-05.scss rename to crates/docs_rs_web/templates/style/rustdoc-2021-12-05.scss diff --git a/templates/style/rustdoc-2025-08-20.scss b/crates/docs_rs_web/templates/style/rustdoc-2025-08-20.scss similarity index 100% rename from templates/style/rustdoc-2025-08-20.scss rename to crates/docs_rs_web/templates/style/rustdoc-2025-08-20.scss diff --git a/templates/style/rustdoc.scss b/crates/docs_rs_web/templates/style/rustdoc.scss similarity index 100% rename from templates/style/rustdoc.scss rename to crates/docs_rs_web/templates/style/rustdoc.scss diff --git a/templates/style/style.scss b/crates/docs_rs_web/templates/style/style.scss similarity index 100% rename from templates/style/style.scss rename to crates/docs_rs_web/templates/style/style.scss diff --git a/templates/theme.js b/crates/docs_rs_web/templates/theme.js similarity index 100% rename from templates/theme.js rename to crates/docs_rs_web/templates/theme.js diff --git a/vendor/chartjs/LICENSE b/crates/docs_rs_web/vendor/chartjs/LICENSE similarity index 99% rename from vendor/chartjs/LICENSE rename to crates/docs_rs_web/vendor/chartjs/LICENSE index 837673f68..29c941dcc 100644 --- a/vendor/chartjs/LICENSE +++ b/crates/docs_rs_web/vendor/chartjs/LICENSE @@ -1,9 +1,9 @@ -The MIT License (MIT) - -Copyright (c) 2018 Chart.js Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +The MIT License (MIT) + +Copyright (c) 2018 Chart.js Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/chartjs/chart.min.js b/crates/docs_rs_web/vendor/chartjs/chart.min.js similarity index 100% rename from vendor/chartjs/chart.min.js rename to crates/docs_rs_web/vendor/chartjs/chart.min.js diff --git a/vendor/pure-css/LICENSE b/crates/docs_rs_web/vendor/pure-css/LICENSE similarity index 100% rename from vendor/pure-css/LICENSE rename to crates/docs_rs_web/vendor/pure-css/LICENSE diff --git a/src/db/types/mod.rs b/src/db/types/mod.rs deleted file mode 100644 index 7ba865c31..000000000 --- a/src/db/types/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -use serde::{Deserialize, Serialize}; - -pub mod dependencies; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize, sqlx::Type)] -#[sqlx(type_name = "feature")] -pub struct Feature { - pub(crate) name: String, - pub(crate) subfeatures: Vec, -} - -impl Feature { - pub fn new(name: String, subfeatures: Vec) -> Self { - Feature { name, subfeatures } - } - - pub fn is_private(&self) -> bool { - self.name.starts_with('_') - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, sqlx::Type)] -#[sqlx(type_name = "build_status", rename_all = "snake_case")] -#[serde(rename_all = "snake_case")] -pub(crate) enum BuildStatus { - Success, - Failure, - InProgress, -} - -impl BuildStatus { - pub(crate) fn is_success(&self) -> bool { - matches!(self, BuildStatus::Success) - } -} - -impl PartialEq<&str> for BuildStatus { - fn eq(&self, other: &&str) -> bool { - match self { - Self::Success => *other == "success", - Self::Failure => *other == "failure", - Self::InProgress => *other == "in_progress", - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; - - #[test_case(BuildStatus::Success, "success")] - #[test_case(BuildStatus::Failure, "failure")] - #[test_case(BuildStatus::InProgress, "in_progress")] - fn test_build_status_serialization(status: BuildStatus, expected: &str) { - let serialized = serde_json::to_string(&status).unwrap(); - assert_eq!(serialized, format!("\"{expected}\"")); - assert_eq!( - serde_json::from_str::(&serialized).unwrap(), - status - ); - } -} diff --git a/src/metrics/mod.rs b/src/metrics/mod.rs index fb09390d7..667365871 100644 --- a/src/metrics/mod.rs +++ b/src/metrics/mod.rs @@ -56,15 +56,3 @@ pub const BUILD_TIME_HISTOGRAM_BUCKETS: &[f64] = &[ 3000.0, // 50 3600.0, // 60 ]; - -/// response time histogram buckets from the opentelemetry semantiv conventions -/// https://opentelemetry.io/docs/specs/semconv/http/http-metrics/#metric-httpserverrequestduration -/// -/// These are the default prometheus bucket sizes, -/// https://docs.rs/prometheus/0.14.0/src/prometheus/histogram.rs.html#25-27 -/// tailored to broadly measure the response time (in seconds) of a network service. -/// -/// Otel default buckets are not suited for that. -pub const RESPONSE_TIME_HISTOGRAM_BUCKETS: &[f64] = &[ - 0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1.0, 2.5, 5.0, 7.5, 10.0, -]; diff --git a/src/utils/daemon.rs b/src/utils/daemon.rs deleted file mode 100644 index f17db1a07..000000000 --- a/src/utils/daemon.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Simple daemon -//! -//! This daemon will start web server, track new packages and build them - -use crate::{Context, RustwideBuilder, utils::queue_builder, web::start_web_server}; -use anyhow::{Error, anyhow}; -use docs_rs_utils::{start_async_cron, start_async_cron_in_runtime}; -use docs_rs_watcher::{rebuilds::queue_rebuilds, service_metrics::OtelServiceMetrics}; -use std::sync::Arc; -use std::thread; -use std::time::Duration; -use tracing::{info, trace}; - -fn start_registry_watcher(context: &Context) -> Result<(), Error> { - let build_queue = context.async_build_queue.clone(); - let config = context.config.clone(); - - context.runtime.spawn(async move { - // space this out to prevent it from clashing against the queue-builder thread on launch - tokio::time::sleep(Duration::from_secs(30)).await; - - docs_rs_watcher::watch_registry(&build_queue, &config.watcher).await - }); - - Ok(()) -} - -pub fn start_daemon(context: Context, enable_registry_watcher: bool) -> Result<(), Error> { - let context = Arc::new(context); - - // Start the web server before doing anything more expensive - // Please check with an administrator before changing this (see #1172 for context). - info!("Starting web server"); - let webserver_thread = thread::spawn({ - let context = context.clone(); - move || start_web_server(None, &context) - }); - - if enable_registry_watcher { - // check new crates every minute - start_registry_watcher(&context)?; - } - - // build new crates every minute - let rustwide_builder = RustwideBuilder::init(&context)?; - thread::Builder::new() - .name("build queue reader".to_string()) - .spawn({ - let context = context.clone(); - move || queue_builder(&context, rustwide_builder).unwrap() - }) - .unwrap(); - - start_background_repository_stats_updater(&context)?; - start_background_queue_rebuild(&context)?; - - // when people run the daemon, we assume the daemon is the one single process where - // we can collect the service metrics. - start_background_service_metric_collector(&context)?; - - // NOTE: if a error occurred earlier in `start_daemon`, the server will _not_ be joined - - // instead it will get killed when the process exits. - webserver_thread - .join() - .map_err(|err| anyhow!("web server panicked: {:?}", err))? -} - -pub fn start_background_queue_rebuild(context: &Context) -> Result<(), Error> { - let runtime = context.runtime.clone(); - let pool = context.pool.clone(); - let config = context.config.clone(); - let build_queue = context.async_build_queue.clone(); - - if config.watcher.max_queued_rebuilds.is_none() { - info!("rebuild config incomplete, skipping rebuild queueing"); - return Ok(()); - } - - start_async_cron_in_runtime( - &runtime, - "background queue rebuilder", - Duration::from_secs(60 * 60), - move || { - let pool = pool.clone(); - let build_queue = build_queue.clone(); - let config = config.clone(); - async move { - let mut conn = pool.get_async().await?; - queue_rebuilds(&mut conn, &config.watcher, &build_queue).await?; - Ok(()) - } - }, - ); - Ok(()) -} - -pub fn start_background_repository_stats_updater(context: &Context) -> Result<(), Error> { - // This call will still skip github repositories updates and continue if no token is provided - // (gitlab doesn't require to have a token). The only time this can return an error is when - // creating a pool or if config fails, which shouldn't happen here because this is run right at - // startup. - let updater = context.repository_stats_updater.clone(); - let runtime = context.runtime.clone(); - start_async_cron_in_runtime( - &runtime, - "repository stats updater", - Duration::from_secs(60 * 60), - move || { - let updater = updater.clone(); - async move { - updater.update_all_crates().await?; - Ok(()) - } - }, - ); - Ok(()) -} - -pub fn start_background_service_metric_collector(context: &Context) -> Result<(), Error> { - let runtime = context.runtime.clone(); - let build_queue = context.async_build_queue.clone(); - let service_metrics = Arc::new(OtelServiceMetrics::new(&context.meter_provider)); - - start_async_cron_in_runtime( - &runtime, - "background service metric collector", - // old prometheus scrape interval seems to have been ~5s, but IMO that's far too frequent - // for these service metrics. - Duration::from_secs(30), - move || { - let build_queue = build_queue.clone(); - let service_metrics = service_metrics.clone(); - async move { - trace!("collecting service metrics"); - service_metrics.gather(&build_queue).await - } - }, - ); - Ok(()) -} diff --git a/static/ayu-highlight.css b/static/ayu-highlight.css deleted file mode 100644 index 38eb90626..000000000 --- a/static/ayu-highlight.css +++ /dev/null @@ -1,79 +0,0 @@ -/* -Based off of the Ayu theme -Original by Dempfi (https://github.com/dempfi/ayu) -*/ - -.hljs { - display: block; - overflow-x: auto; - background: #191f26; - color: #e6e1cf; - padding: 0.5em; -} - -.hljs-comment, -.hljs-quote { - color: #5c6773; - font-style: italic; -} - -.hljs-variable, -.hljs-template-variable, -.hljs-attribute, -.hljs-attr, -.hljs-regexp, -.hljs-link, -.hljs-selector-id, -.hljs-selector-class { - color: #ff7733; -} - -.hljs-number, -.hljs-meta, -.hljs-builtin-name, -.hljs-literal, -.hljs-type, -.hljs-params { - color: #ffee99; -} - -.hljs-string, -.hljs-bullet { - color: #b8cc52; -} - -.hljs-title, -.hljs-built_in, -.hljs-section { - color: #ffb454; -} - -.hljs-keyword, -.hljs-selector-tag, -.hljs-symbol { - color: #ff7733; -} - -.hljs-name { - color: #36a3d9; -} - -.hljs-tag { - color: #00568d; -} - -.hljs-emphasis { - font-style: italic; -} - -.hljs-strong { - font-weight: bold; -} - -.hljs-addition { - color: #91b362; -} - -.hljs-deletion { - color: #d96c75; -} diff --git a/static/font-awesome.css b/static/font-awesome.css deleted file mode 100644 index d4fb19278..000000000 --- a/static/font-awesome.css +++ /dev/null @@ -1,31 +0,0 @@ -/*! - * Font Awesome Free 6.6.0 by @fontawesome - https://fontawesome.com - * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) - * Copyright 2024 Fonticons, Inc. - */ - -/* -This file is modified in two ways: - -1. The paths to the font size (using `/-/static/`). -2. Adding the `svg-clipboard` class to make it available on all docs.rs pages. -*/ -.fa{font-family:var(--fa-style-family,"Font Awesome 6 Free");font-weight:var(--fa-style,900)}.fa,.fa-brands,.fa-classic,.fa-regular,.fa-sharp-solid,.fa-solid,.fab,.far,.fas{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;display:var(--fa-display,inline-block);font-style:normal;font-variant:normal;line-height:1;text-rendering:auto}.fa-classic,.fa-regular,.fa-solid,.far,.fas{font-family:"Font Awesome 6 Free"}.fa-brands,.fab{font-family:"Font Awesome 6 Brands"}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-2xs{font-size:.625em;line-height:.1em;vertical-align:.225em}.fa-xs{font-size:.75em;line-height:.08333em;vertical-align:.125em}.fa-sm{font-size:.875em;line-height:.07143em;vertical-align:.05357em}.fa-lg{font-size:1.25em;line-height:.05em;vertical-align:-.075em}.fa-xl{font-size:1.5em;line-height:.04167em;vertical-align:-.125em}.fa-2xl{font-size:2em;line-height:.03125em;vertical-align:-.1875em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:var(--fa-li-margin,2.5em);padding-left:0}.fa-ul>li{position:relative}.fa-li{left:calc(var(--fa-li-width, 2em)*-1);position:absolute;text-align:center;width:var(--fa-li-width,2em);line-height:inherit}.fa-border{border-radius:var(--fa-border-radius,.1em);border:var(--fa-border-width,.08em) var(--fa-border-style,solid) var(--fa-border-color,#eee);padding:var(--fa-border-padding,.2em .25em .15em)}.fa-pull-left{float:left;margin-right:var(--fa-pull-margin,.3em)}.fa-pull-right{float:right;margin-left:var(--fa-pull-margin,.3em)}.fa-beat{animation-name:fa-beat;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-bounce{animation-name:fa-bounce;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.28,.84,.42,1))}.fa-fade{animation-name:fa-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-beat-fade,.fa-fade{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s)}.fa-beat-fade{animation-name:fa-beat-fade;animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,cubic-bezier(.4,0,.6,1))}.fa-flip{animation-name:fa-flip;animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,ease-in-out)}.fa-shake{animation-name:fa-shake;animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-shake,.fa-spin{animation-delay:var(--fa-animation-delay,0s);animation-direction:var(--fa-animation-direction,normal)}.fa-spin{animation-name:fa-spin;animation-duration:var(--fa-animation-duration,2s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,linear)}.fa-spin-reverse{--fa-animation-direction:reverse}.fa-pulse,.fa-spin-pulse{animation-name:fa-spin;animation-direction:var(--fa-animation-direction,normal);animation-duration:var(--fa-animation-duration,1s);animation-iteration-count:var(--fa-animation-iteration-count,infinite);animation-timing-function:var(--fa-animation-timing,steps(8))}@media (prefers-reduced-motion:reduce){.fa-beat,.fa-beat-fade,.fa-bounce,.fa-fade,.fa-flip,.fa-pulse,.fa-shake,.fa-spin,.fa-spin-pulse{animation-delay:-1ms;animation-duration:1ms;animation-iteration-count:1;transition-delay:0s;transition-duration:0s}}@keyframes fa-beat{0%,90%{transform:scale(1)}45%{transform:scale(var(--fa-beat-scale,1.25))}}@keyframes fa-bounce{0%{transform:scale(1) translateY(0)}10%{transform:scale(var(--fa-bounce-start-scale-x,1.1),var(--fa-bounce-start-scale-y,.9)) translateY(0)}30%{transform:scale(var(--fa-bounce-jump-scale-x,.9),var(--fa-bounce-jump-scale-y,1.1)) translateY(var(--fa-bounce-height,-.5em))}50%{transform:scale(var(--fa-bounce-land-scale-x,1.05),var(--fa-bounce-land-scale-y,.95)) translateY(0)}57%{transform:scale(1) translateY(var(--fa-bounce-rebound,-.125em))}64%{transform:scale(1) translateY(0)}to{transform:scale(1) translateY(0)}}@keyframes fa-fade{50%{opacity:var(--fa-fade-opacity,.4)}}@keyframes fa-beat-fade{0%,to{opacity:var(--fa-beat-fade-opacity,.4);transform:scale(1)}50%{opacity:1;transform:scale(var(--fa-beat-fade-scale,1.125))}}@keyframes fa-flip{50%{transform:rotate3d(var(--fa-flip-x,0),var(--fa-flip-y,1),var(--fa-flip-z,0),var(--fa-flip-angle,-180deg))}}@keyframes fa-shake{0%{transform:rotate(-15deg)}4%{transform:rotate(15deg)}8%,24%{transform:rotate(-18deg)}12%,28%{transform:rotate(18deg)}16%{transform:rotate(-22deg)}20%{transform:rotate(22deg)}32%{transform:rotate(-12deg)}36%{transform:rotate(12deg)}40%,to{transform:rotate(0deg)}}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{transform:rotate(90deg)}.fa-rotate-180{transform:rotate(180deg)}.fa-rotate-270{transform:rotate(270deg)}.fa-flip-horizontal{transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-both,.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}.fa-rotate-by{transform:rotate(var(--fa-rotate-angle,0))}.fa-stack{display:inline-block;height:2em;line-height:2em;position:relative;vertical-align:middle;width:2.5em}.fa-stack-1x,.fa-stack-2x{left:0;position:absolute;text-align:center;width:100%;z-index:var(--fa-stack-z-index,auto)}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:var(--fa-inverse,#fff)} - -.fa-0:before{content:"\30"}.fa-1:before{content:"\31"}.fa-2:before{content:"\32"}.fa-3:before{content:"\33"}.fa-4:before{content:"\34"}.fa-5:before{content:"\35"}.fa-6:before{content:"\36"}.fa-7:before{content:"\37"}.fa-8:before{content:"\38"}.fa-9:before{content:"\39"}.fa-fill-drip:before{content:"\f576"}.fa-arrows-to-circle:before{content:"\e4bd"}.fa-chevron-circle-right:before,.fa-circle-chevron-right:before{content:"\f138"}.fa-at:before{content:"\40"}.fa-trash-alt:before,.fa-trash-can:before{content:"\f2ed"}.fa-text-height:before{content:"\f034"}.fa-user-times:before,.fa-user-xmark:before{content:"\f235"}.fa-stethoscope:before{content:"\f0f1"}.fa-comment-alt:before,.fa-message:before{content:"\f27a"}.fa-info:before{content:"\f129"}.fa-compress-alt:before,.fa-down-left-and-up-right-to-center:before{content:"\f422"}.fa-explosion:before{content:"\e4e9"}.fa-file-alt:before,.fa-file-lines:before,.fa-file-text:before{content:"\f15c"}.fa-wave-square:before{content:"\f83e"}.fa-ring:before{content:"\f70b"}.fa-building-un:before{content:"\e4d9"}.fa-dice-three:before{content:"\f527"}.fa-calendar-alt:before,.fa-calendar-days:before{content:"\f073"}.fa-anchor-circle-check:before{content:"\e4aa"}.fa-building-circle-arrow-right:before{content:"\e4d1"}.fa-volleyball-ball:before,.fa-volleyball:before{content:"\f45f"}.fa-arrows-up-to-line:before{content:"\e4c2"}.fa-sort-desc:before,.fa-sort-down:before{content:"\f0dd"}.fa-circle-minus:before,.fa-minus-circle:before{content:"\f056"}.fa-door-open:before{content:"\f52b"}.fa-right-from-bracket:before,.fa-sign-out-alt:before{content:"\f2f5"}.fa-atom:before{content:"\f5d2"}.fa-soap:before{content:"\e06e"}.fa-heart-music-camera-bolt:before,.fa-icons:before{content:"\f86d"}.fa-microphone-alt-slash:before,.fa-microphone-lines-slash:before{content:"\f539"}.fa-bridge-circle-check:before{content:"\e4c9"}.fa-pump-medical:before{content:"\e06a"}.fa-fingerprint:before{content:"\f577"}.fa-hand-point-right:before{content:"\f0a4"}.fa-magnifying-glass-location:before,.fa-search-location:before{content:"\f689"}.fa-forward-step:before,.fa-step-forward:before{content:"\f051"}.fa-face-smile-beam:before,.fa-smile-beam:before{content:"\f5b8"}.fa-flag-checkered:before{content:"\f11e"}.fa-football-ball:before,.fa-football:before{content:"\f44e"}.fa-school-circle-exclamation:before{content:"\e56c"}.fa-crop:before{content:"\f125"}.fa-angle-double-down:before,.fa-angles-down:before{content:"\f103"}.fa-users-rectangle:before{content:"\e594"}.fa-people-roof:before{content:"\e537"}.fa-people-line:before{content:"\e534"}.fa-beer-mug-empty:before,.fa-beer:before{content:"\f0fc"}.fa-diagram-predecessor:before{content:"\e477"}.fa-arrow-up-long:before,.fa-long-arrow-up:before{content:"\f176"}.fa-burn:before,.fa-fire-flame-simple:before{content:"\f46a"}.fa-male:before,.fa-person:before{content:"\f183"}.fa-laptop:before{content:"\f109"}.fa-file-csv:before{content:"\f6dd"}.fa-menorah:before{content:"\f676"}.fa-truck-plane:before{content:"\e58f"}.fa-record-vinyl:before{content:"\f8d9"}.fa-face-grin-stars:before,.fa-grin-stars:before{content:"\f587"}.fa-bong:before{content:"\f55c"}.fa-pastafarianism:before,.fa-spaghetti-monster-flying:before{content:"\f67b"}.fa-arrow-down-up-across-line:before{content:"\e4af"}.fa-spoon:before,.fa-utensil-spoon:before{content:"\f2e5"}.fa-jar-wheat:before{content:"\e517"}.fa-envelopes-bulk:before,.fa-mail-bulk:before{content:"\f674"}.fa-file-circle-exclamation:before{content:"\e4eb"}.fa-circle-h:before,.fa-hospital-symbol:before{content:"\f47e"}.fa-pager:before{content:"\f815"}.fa-address-book:before,.fa-contact-book:before{content:"\f2b9"}.fa-strikethrough:before{content:"\f0cc"}.fa-k:before{content:"\4b"}.fa-landmark-flag:before{content:"\e51c"}.fa-pencil-alt:before,.fa-pencil:before{content:"\f303"}.fa-backward:before{content:"\f04a"}.fa-caret-right:before{content:"\f0da"}.fa-comments:before{content:"\f086"}.fa-file-clipboard:before,.fa-paste:before{content:"\f0ea"}.fa-code-pull-request:before{content:"\e13c"}.fa-clipboard-list:before{content:"\f46d"}.fa-truck-loading:before,.fa-truck-ramp-box:before{content:"\f4de"}.fa-user-check:before{content:"\f4fc"}.fa-vial-virus:before{content:"\e597"}.fa-sheet-plastic:before{content:"\e571"}.fa-blog:before{content:"\f781"}.fa-user-ninja:before{content:"\f504"}.fa-person-arrow-up-from-line:before{content:"\e539"}.fa-scroll-torah:before,.fa-torah:before{content:"\f6a0"}.fa-broom-ball:before,.fa-quidditch-broom-ball:before,.fa-quidditch:before{content:"\f458"}.fa-toggle-off:before{content:"\f204"}.fa-archive:before,.fa-box-archive:before{content:"\f187"}.fa-person-drowning:before{content:"\e545"}.fa-arrow-down-9-1:before,.fa-sort-numeric-desc:before,.fa-sort-numeric-down-alt:before{content:"\f886"}.fa-face-grin-tongue-squint:before,.fa-grin-tongue-squint:before{content:"\f58a"}.fa-spray-can:before{content:"\f5bd"}.fa-truck-monster:before{content:"\f63b"}.fa-w:before{content:"\57"}.fa-earth-africa:before,.fa-globe-africa:before{content:"\f57c"}.fa-rainbow:before{content:"\f75b"}.fa-circle-notch:before{content:"\f1ce"}.fa-tablet-alt:before,.fa-tablet-screen-button:before{content:"\f3fa"}.fa-paw:before{content:"\f1b0"}.fa-cloud:before{content:"\f0c2"}.fa-trowel-bricks:before{content:"\e58a"}.fa-face-flushed:before,.fa-flushed:before{content:"\f579"}.fa-hospital-user:before{content:"\f80d"}.fa-tent-arrow-left-right:before{content:"\e57f"}.fa-gavel:before,.fa-legal:before{content:"\f0e3"}.fa-binoculars:before{content:"\f1e5"}.fa-microphone-slash:before{content:"\f131"}.fa-box-tissue:before{content:"\e05b"}.fa-motorcycle:before{content:"\f21c"}.fa-bell-concierge:before,.fa-concierge-bell:before{content:"\f562"}.fa-pen-ruler:before,.fa-pencil-ruler:before{content:"\f5ae"}.fa-people-arrows-left-right:before,.fa-people-arrows:before{content:"\e068"}.fa-mars-and-venus-burst:before{content:"\e523"}.fa-caret-square-right:before,.fa-square-caret-right:before{content:"\f152"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-sun-plant-wilt:before{content:"\e57a"}.fa-toilets-portable:before{content:"\e584"}.fa-hockey-puck:before{content:"\f453"}.fa-table:before{content:"\f0ce"}.fa-magnifying-glass-arrow-right:before{content:"\e521"}.fa-digital-tachograph:before,.fa-tachograph-digital:before{content:"\f566"}.fa-users-slash:before{content:"\e073"}.fa-clover:before{content:"\e139"}.fa-mail-reply:before,.fa-reply:before{content:"\f3e5"}.fa-star-and-crescent:before{content:"\f699"}.fa-house-fire:before{content:"\e50c"}.fa-minus-square:before,.fa-square-minus:before{content:"\f146"}.fa-helicopter:before{content:"\f533"}.fa-compass:before{content:"\f14e"}.fa-caret-square-down:before,.fa-square-caret-down:before{content:"\f150"}.fa-file-circle-question:before{content:"\e4ef"}.fa-laptop-code:before{content:"\f5fc"}.fa-swatchbook:before{content:"\f5c3"}.fa-prescription-bottle:before{content:"\f485"}.fa-bars:before,.fa-navicon:before{content:"\f0c9"}.fa-people-group:before{content:"\e533"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-heart-broken:before,.fa-heart-crack:before{content:"\f7a9"}.fa-external-link-square-alt:before,.fa-square-up-right:before{content:"\f360"}.fa-face-kiss-beam:before,.fa-kiss-beam:before{content:"\f597"}.fa-film:before{content:"\f008"}.fa-ruler-horizontal:before{content:"\f547"}.fa-people-robbery:before{content:"\e536"}.fa-lightbulb:before{content:"\f0eb"}.fa-caret-left:before{content:"\f0d9"}.fa-circle-exclamation:before,.fa-exclamation-circle:before{content:"\f06a"}.fa-school-circle-xmark:before{content:"\e56d"}.fa-arrow-right-from-bracket:before,.fa-sign-out:before{content:"\f08b"}.fa-chevron-circle-down:before,.fa-circle-chevron-down:before{content:"\f13a"}.fa-unlock-alt:before,.fa-unlock-keyhole:before{content:"\f13e"}.fa-cloud-showers-heavy:before{content:"\f740"}.fa-headphones-alt:before,.fa-headphones-simple:before{content:"\f58f"}.fa-sitemap:before{content:"\f0e8"}.fa-circle-dollar-to-slot:before,.fa-donate:before{content:"\f4b9"}.fa-memory:before{content:"\f538"}.fa-road-spikes:before{content:"\e568"}.fa-fire-burner:before{content:"\e4f1"}.fa-flag:before{content:"\f024"}.fa-hanukiah:before{content:"\f6e6"}.fa-feather:before{content:"\f52d"}.fa-volume-down:before,.fa-volume-low:before{content:"\f027"}.fa-comment-slash:before{content:"\f4b3"}.fa-cloud-sun-rain:before{content:"\f743"}.fa-compress:before{content:"\f066"}.fa-wheat-alt:before,.fa-wheat-awn:before{content:"\e2cd"}.fa-ankh:before{content:"\f644"}.fa-hands-holding-child:before{content:"\e4fa"}.fa-asterisk:before{content:"\2a"}.fa-check-square:before,.fa-square-check:before{content:"\f14a"}.fa-peseta-sign:before{content:"\e221"}.fa-header:before,.fa-heading:before{content:"\f1dc"}.fa-ghost:before{content:"\f6e2"}.fa-list-squares:before,.fa-list:before{content:"\f03a"}.fa-phone-square-alt:before,.fa-square-phone-flip:before{content:"\f87b"}.fa-cart-plus:before{content:"\f217"}.fa-gamepad:before{content:"\f11b"}.fa-circle-dot:before,.fa-dot-circle:before{content:"\f192"}.fa-dizzy:before,.fa-face-dizzy:before{content:"\f567"}.fa-egg:before{content:"\f7fb"}.fa-house-medical-circle-xmark:before{content:"\e513"}.fa-campground:before{content:"\f6bb"}.fa-folder-plus:before{content:"\f65e"}.fa-futbol-ball:before,.fa-futbol:before,.fa-soccer-ball:before{content:"\f1e3"}.fa-paint-brush:before,.fa-paintbrush:before{content:"\f1fc"}.fa-lock:before{content:"\f023"}.fa-gas-pump:before{content:"\f52f"}.fa-hot-tub-person:before,.fa-hot-tub:before{content:"\f593"}.fa-map-location:before,.fa-map-marked:before{content:"\f59f"}.fa-house-flood-water:before{content:"\e50e"}.fa-tree:before{content:"\f1bb"}.fa-bridge-lock:before{content:"\e4cc"}.fa-sack-dollar:before{content:"\f81d"}.fa-edit:before,.fa-pen-to-square:before{content:"\f044"}.fa-car-side:before{content:"\f5e4"}.fa-share-alt:before,.fa-share-nodes:before{content:"\f1e0"}.fa-heart-circle-minus:before{content:"\e4ff"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-microscope:before{content:"\f610"}.fa-sink:before{content:"\e06d"}.fa-bag-shopping:before,.fa-shopping-bag:before{content:"\f290"}.fa-arrow-down-z-a:before,.fa-sort-alpha-desc:before,.fa-sort-alpha-down-alt:before{content:"\f881"}.fa-mitten:before{content:"\f7b5"}.fa-person-rays:before{content:"\e54d"}.fa-users:before{content:"\f0c0"}.fa-eye-slash:before{content:"\f070"}.fa-flask-vial:before{content:"\e4f3"}.fa-hand-paper:before,.fa-hand:before{content:"\f256"}.fa-om:before{content:"\f679"}.fa-worm:before{content:"\e599"}.fa-house-circle-xmark:before{content:"\e50b"}.fa-plug:before{content:"\f1e6"}.fa-chevron-up:before{content:"\f077"}.fa-hand-spock:before{content:"\f259"}.fa-stopwatch:before{content:"\f2f2"}.fa-face-kiss:before,.fa-kiss:before{content:"\f596"}.fa-bridge-circle-xmark:before{content:"\e4cb"}.fa-face-grin-tongue:before,.fa-grin-tongue:before{content:"\f589"}.fa-chess-bishop:before{content:"\f43a"}.fa-face-grin-wink:before,.fa-grin-wink:before{content:"\f58c"}.fa-deaf:before,.fa-deafness:before,.fa-ear-deaf:before,.fa-hard-of-hearing:before{content:"\f2a4"}.fa-road-circle-check:before{content:"\e564"}.fa-dice-five:before{content:"\f523"}.fa-rss-square:before,.fa-square-rss:before{content:"\f143"}.fa-land-mine-on:before{content:"\e51b"}.fa-i-cursor:before{content:"\f246"}.fa-stamp:before{content:"\f5bf"}.fa-stairs:before{content:"\e289"}.fa-i:before{content:"\49"}.fa-hryvnia-sign:before,.fa-hryvnia:before{content:"\f6f2"}.fa-pills:before{content:"\f484"}.fa-face-grin-wide:before,.fa-grin-alt:before{content:"\f581"}.fa-tooth:before{content:"\f5c9"}.fa-v:before{content:"\56"}.fa-bangladeshi-taka-sign:before{content:"\e2e6"}.fa-bicycle:before{content:"\f206"}.fa-rod-asclepius:before,.fa-rod-snake:before,.fa-staff-aesculapius:before,.fa-staff-snake:before{content:"\e579"}.fa-head-side-cough-slash:before{content:"\e062"}.fa-ambulance:before,.fa-truck-medical:before{content:"\f0f9"}.fa-wheat-awn-circle-exclamation:before{content:"\e598"}.fa-snowman:before{content:"\f7d0"}.fa-mortar-pestle:before{content:"\f5a7"}.fa-road-barrier:before{content:"\e562"}.fa-school:before{content:"\f549"}.fa-igloo:before{content:"\f7ae"}.fa-joint:before{content:"\f595"}.fa-angle-right:before{content:"\f105"}.fa-horse:before{content:"\f6f0"}.fa-q:before{content:"\51"}.fa-g:before{content:"\47"}.fa-notes-medical:before{content:"\f481"}.fa-temperature-2:before,.fa-temperature-half:before,.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-dong-sign:before{content:"\e169"}.fa-capsules:before{content:"\f46b"}.fa-poo-bolt:before,.fa-poo-storm:before{content:"\f75a"}.fa-face-frown-open:before,.fa-frown-open:before{content:"\f57a"}.fa-hand-point-up:before{content:"\f0a6"}.fa-money-bill:before{content:"\f0d6"}.fa-bookmark:before{content:"\f02e"}.fa-align-justify:before{content:"\f039"}.fa-umbrella-beach:before{content:"\f5ca"}.fa-helmet-un:before{content:"\e503"}.fa-bullseye:before{content:"\f140"}.fa-bacon:before{content:"\f7e5"}.fa-hand-point-down:before{content:"\f0a7"}.fa-arrow-up-from-bracket:before{content:"\e09a"}.fa-folder-blank:before,.fa-folder:before{content:"\f07b"}.fa-file-medical-alt:before,.fa-file-waveform:before{content:"\f478"}.fa-radiation:before{content:"\f7b9"}.fa-chart-simple:before{content:"\e473"}.fa-mars-stroke:before{content:"\f229"}.fa-vial:before{content:"\f492"}.fa-dashboard:before,.fa-gauge-med:before,.fa-gauge:before,.fa-tachometer-alt-average:before{content:"\f624"}.fa-magic-wand-sparkles:before,.fa-wand-magic-sparkles:before{content:"\e2ca"}.fa-e:before{content:"\45"}.fa-pen-alt:before,.fa-pen-clip:before{content:"\f305"}.fa-bridge-circle-exclamation:before{content:"\e4ca"}.fa-user:before{content:"\f007"}.fa-school-circle-check:before{content:"\e56b"}.fa-dumpster:before{content:"\f793"}.fa-shuttle-van:before,.fa-van-shuttle:before{content:"\f5b6"}.fa-building-user:before{content:"\e4da"}.fa-caret-square-left:before,.fa-square-caret-left:before{content:"\f191"}.fa-highlighter:before{content:"\f591"}.fa-key:before{content:"\f084"}.fa-bullhorn:before{content:"\f0a1"}.fa-globe:before{content:"\f0ac"}.fa-synagogue:before{content:"\f69b"}.fa-person-half-dress:before{content:"\e548"}.fa-road-bridge:before{content:"\e563"}.fa-location-arrow:before{content:"\f124"}.fa-c:before{content:"\43"}.fa-tablet-button:before{content:"\f10a"}.fa-building-lock:before{content:"\e4d6"}.fa-pizza-slice:before{content:"\f818"}.fa-money-bill-wave:before{content:"\f53a"}.fa-area-chart:before,.fa-chart-area:before{content:"\f1fe"}.fa-house-flag:before{content:"\e50d"}.fa-person-circle-minus:before{content:"\e540"}.fa-ban:before,.fa-cancel:before{content:"\f05e"}.fa-camera-rotate:before{content:"\e0d8"}.fa-air-freshener:before,.fa-spray-can-sparkles:before{content:"\f5d0"}.fa-star:before{content:"\f005"}.fa-repeat:before{content:"\f363"}.fa-cross:before{content:"\f654"}.fa-box:before{content:"\f466"}.fa-venus-mars:before{content:"\f228"}.fa-arrow-pointer:before,.fa-mouse-pointer:before{content:"\f245"}.fa-expand-arrows-alt:before,.fa-maximize:before{content:"\f31e"}.fa-charging-station:before{content:"\f5e7"}.fa-shapes:before,.fa-triangle-circle-square:before{content:"\f61f"}.fa-random:before,.fa-shuffle:before{content:"\f074"}.fa-person-running:before,.fa-running:before{content:"\f70c"}.fa-mobile-retro:before{content:"\e527"}.fa-grip-lines-vertical:before{content:"\f7a5"}.fa-spider:before{content:"\f717"}.fa-hands-bound:before{content:"\e4f9"}.fa-file-invoice-dollar:before{content:"\f571"}.fa-plane-circle-exclamation:before{content:"\e556"}.fa-x-ray:before{content:"\f497"}.fa-spell-check:before{content:"\f891"}.fa-slash:before{content:"\f715"}.fa-computer-mouse:before,.fa-mouse:before{content:"\f8cc"}.fa-arrow-right-to-bracket:before,.fa-sign-in:before{content:"\f090"}.fa-shop-slash:before,.fa-store-alt-slash:before{content:"\e070"}.fa-server:before{content:"\f233"}.fa-virus-covid-slash:before{content:"\e4a9"}.fa-shop-lock:before{content:"\e4a5"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-blender-phone:before{content:"\f6b6"}.fa-building-wheat:before{content:"\e4db"}.fa-person-breastfeeding:before{content:"\e53a"}.fa-right-to-bracket:before,.fa-sign-in-alt:before{content:"\f2f6"}.fa-venus:before{content:"\f221"}.fa-passport:before{content:"\f5ab"}.fa-thumb-tack-slash:before,.fa-thumbtack-slash:before{content:"\e68f"}.fa-heart-pulse:before,.fa-heartbeat:before{content:"\f21e"}.fa-people-carry-box:before,.fa-people-carry:before{content:"\f4ce"}.fa-temperature-high:before{content:"\f769"}.fa-microchip:before{content:"\f2db"}.fa-crown:before{content:"\f521"}.fa-weight-hanging:before{content:"\f5cd"}.fa-xmarks-lines:before{content:"\e59a"}.fa-file-prescription:before{content:"\f572"}.fa-weight-scale:before,.fa-weight:before{content:"\f496"}.fa-user-friends:before,.fa-user-group:before{content:"\f500"}.fa-arrow-up-a-z:before,.fa-sort-alpha-up:before{content:"\f15e"}.fa-chess-knight:before{content:"\f441"}.fa-face-laugh-squint:before,.fa-laugh-squint:before{content:"\f59b"}.fa-wheelchair:before{content:"\f193"}.fa-arrow-circle-up:before,.fa-circle-arrow-up:before{content:"\f0aa"}.fa-toggle-on:before{content:"\f205"}.fa-person-walking:before,.fa-walking:before{content:"\f554"}.fa-l:before{content:"\4c"}.fa-fire:before{content:"\f06d"}.fa-bed-pulse:before,.fa-procedures:before{content:"\f487"}.fa-shuttle-space:before,.fa-space-shuttle:before{content:"\f197"}.fa-face-laugh:before,.fa-laugh:before{content:"\f599"}.fa-folder-open:before{content:"\f07c"}.fa-heart-circle-plus:before{content:"\e500"}.fa-code-fork:before{content:"\e13b"}.fa-city:before{content:"\f64f"}.fa-microphone-alt:before,.fa-microphone-lines:before{content:"\f3c9"}.fa-pepper-hot:before{content:"\f816"}.fa-unlock:before{content:"\f09c"}.fa-colon-sign:before{content:"\e140"}.fa-headset:before{content:"\f590"}.fa-store-slash:before{content:"\e071"}.fa-road-circle-xmark:before{content:"\e566"}.fa-user-minus:before{content:"\f503"}.fa-mars-stroke-up:before,.fa-mars-stroke-v:before{content:"\f22a"}.fa-champagne-glasses:before,.fa-glass-cheers:before{content:"\f79f"}.fa-clipboard:before{content:"\f328"}.fa-house-circle-exclamation:before{content:"\e50a"}.fa-file-arrow-up:before,.fa-file-upload:before{content:"\f574"}.fa-wifi-3:before,.fa-wifi-strong:before,.fa-wifi:before{content:"\f1eb"}.fa-bath:before,.fa-bathtub:before{content:"\f2cd"}.fa-underline:before{content:"\f0cd"}.fa-user-edit:before,.fa-user-pen:before{content:"\f4ff"}.fa-signature:before{content:"\f5b7"}.fa-stroopwafel:before{content:"\f551"}.fa-bold:before{content:"\f032"}.fa-anchor-lock:before{content:"\e4ad"}.fa-building-ngo:before{content:"\e4d7"}.fa-manat-sign:before{content:"\e1d5"}.fa-not-equal:before{content:"\f53e"}.fa-border-style:before,.fa-border-top-left:before{content:"\f853"}.fa-map-location-dot:before,.fa-map-marked-alt:before{content:"\f5a0"}.fa-jedi:before{content:"\f669"}.fa-poll:before,.fa-square-poll-vertical:before{content:"\f681"}.fa-mug-hot:before{content:"\f7b6"}.fa-battery-car:before,.fa-car-battery:before{content:"\f5df"}.fa-gift:before{content:"\f06b"}.fa-dice-two:before{content:"\f528"}.fa-chess-queen:before{content:"\f445"}.fa-glasses:before{content:"\f530"}.fa-chess-board:before{content:"\f43c"}.fa-building-circle-check:before{content:"\e4d2"}.fa-person-chalkboard:before{content:"\e53d"}.fa-mars-stroke-h:before,.fa-mars-stroke-right:before{content:"\f22b"}.fa-hand-back-fist:before,.fa-hand-rock:before{content:"\f255"}.fa-caret-square-up:before,.fa-square-caret-up:before{content:"\f151"}.fa-cloud-showers-water:before{content:"\e4e4"}.fa-bar-chart:before,.fa-chart-bar:before{content:"\f080"}.fa-hands-bubbles:before,.fa-hands-wash:before{content:"\e05e"}.fa-less-than-equal:before{content:"\f537"}.fa-train:before{content:"\f238"}.fa-eye-low-vision:before,.fa-low-vision:before{content:"\f2a8"}.fa-crow:before{content:"\f520"}.fa-sailboat:before{content:"\e445"}.fa-window-restore:before{content:"\f2d2"}.fa-plus-square:before,.fa-square-plus:before{content:"\f0fe"}.fa-torii-gate:before{content:"\f6a1"}.fa-frog:before{content:"\f52e"}.fa-bucket:before{content:"\e4cf"}.fa-image:before{content:"\f03e"}.fa-microphone:before{content:"\f130"}.fa-cow:before{content:"\f6c8"}.fa-caret-up:before{content:"\f0d8"}.fa-screwdriver:before{content:"\f54a"}.fa-folder-closed:before{content:"\e185"}.fa-house-tsunami:before{content:"\e515"}.fa-square-nfi:before{content:"\e576"}.fa-arrow-up-from-ground-water:before{content:"\e4b5"}.fa-glass-martini-alt:before,.fa-martini-glass:before{content:"\f57b"}.fa-rotate-back:before,.fa-rotate-backward:before,.fa-rotate-left:before,.fa-undo-alt:before{content:"\f2ea"}.fa-columns:before,.fa-table-columns:before{content:"\f0db"}.fa-lemon:before{content:"\f094"}.fa-head-side-mask:before{content:"\e063"}.fa-handshake:before{content:"\f2b5"}.fa-gem:before{content:"\f3a5"}.fa-dolly-box:before,.fa-dolly:before{content:"\f472"}.fa-smoking:before{content:"\f48d"}.fa-compress-arrows-alt:before,.fa-minimize:before{content:"\f78c"}.fa-monument:before{content:"\f5a6"}.fa-snowplow:before{content:"\f7d2"}.fa-angle-double-right:before,.fa-angles-right:before{content:"\f101"}.fa-cannabis:before{content:"\f55f"}.fa-circle-play:before,.fa-play-circle:before{content:"\f144"}.fa-tablets:before{content:"\f490"}.fa-ethernet:before{content:"\f796"}.fa-eur:before,.fa-euro-sign:before,.fa-euro:before{content:"\f153"}.fa-chair:before{content:"\f6c0"}.fa-check-circle:before,.fa-circle-check:before{content:"\f058"}.fa-circle-stop:before,.fa-stop-circle:before{content:"\f28d"}.fa-compass-drafting:before,.fa-drafting-compass:before{content:"\f568"}.fa-plate-wheat:before{content:"\e55a"}.fa-icicles:before{content:"\f7ad"}.fa-person-shelter:before{content:"\e54f"}.fa-neuter:before{content:"\f22c"}.fa-id-badge:before{content:"\f2c1"}.fa-marker:before{content:"\f5a1"}.fa-face-laugh-beam:before,.fa-laugh-beam:before{content:"\f59a"}.fa-helicopter-symbol:before{content:"\e502"}.fa-universal-access:before{content:"\f29a"}.fa-chevron-circle-up:before,.fa-circle-chevron-up:before{content:"\f139"}.fa-lari-sign:before{content:"\e1c8"}.fa-volcano:before{content:"\f770"}.fa-person-walking-dashed-line-arrow-right:before{content:"\e553"}.fa-gbp:before,.fa-pound-sign:before,.fa-sterling-sign:before{content:"\f154"}.fa-viruses:before{content:"\e076"}.fa-square-person-confined:before{content:"\e577"}.fa-user-tie:before{content:"\f508"}.fa-arrow-down-long:before,.fa-long-arrow-down:before{content:"\f175"}.fa-tent-arrow-down-to-line:before{content:"\e57e"}.fa-certificate:before{content:"\f0a3"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-suitcase:before{content:"\f0f2"}.fa-person-skating:before,.fa-skating:before{content:"\f7c5"}.fa-filter-circle-dollar:before,.fa-funnel-dollar:before{content:"\f662"}.fa-camera-retro:before{content:"\f083"}.fa-arrow-circle-down:before,.fa-circle-arrow-down:before{content:"\f0ab"}.fa-arrow-right-to-file:before,.fa-file-import:before{content:"\f56f"}.fa-external-link-square:before,.fa-square-arrow-up-right:before{content:"\f14c"}.fa-box-open:before{content:"\f49e"}.fa-scroll:before{content:"\f70e"}.fa-spa:before{content:"\f5bb"}.fa-location-pin-lock:before{content:"\e51f"}.fa-pause:before{content:"\f04c"}.fa-hill-avalanche:before{content:"\e507"}.fa-temperature-0:before,.fa-temperature-empty:before,.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-bomb:before{content:"\f1e2"}.fa-registered:before{content:"\f25d"}.fa-address-card:before,.fa-contact-card:before,.fa-vcard:before{content:"\f2bb"}.fa-balance-scale-right:before,.fa-scale-unbalanced-flip:before{content:"\f516"}.fa-subscript:before{content:"\f12c"}.fa-diamond-turn-right:before,.fa-directions:before{content:"\f5eb"}.fa-burst:before{content:"\e4dc"}.fa-house-laptop:before,.fa-laptop-house:before{content:"\e066"}.fa-face-tired:before,.fa-tired:before{content:"\f5c8"}.fa-money-bills:before{content:"\e1f3"}.fa-smog:before{content:"\f75f"}.fa-crutch:before{content:"\f7f7"}.fa-cloud-arrow-up:before,.fa-cloud-upload-alt:before,.fa-cloud-upload:before{content:"\f0ee"}.fa-palette:before{content:"\f53f"}.fa-arrows-turn-right:before{content:"\e4c0"}.fa-vest:before{content:"\e085"}.fa-ferry:before{content:"\e4ea"}.fa-arrows-down-to-people:before{content:"\e4b9"}.fa-seedling:before,.fa-sprout:before{content:"\f4d8"}.fa-arrows-alt-h:before,.fa-left-right:before{content:"\f337"}.fa-boxes-packing:before{content:"\e4c7"}.fa-arrow-circle-left:before,.fa-circle-arrow-left:before{content:"\f0a8"}.fa-group-arrows-rotate:before{content:"\e4f6"}.fa-bowl-food:before{content:"\e4c6"}.fa-candy-cane:before{content:"\f786"}.fa-arrow-down-wide-short:before,.fa-sort-amount-asc:before,.fa-sort-amount-down:before{content:"\f160"}.fa-cloud-bolt:before,.fa-thunderstorm:before{content:"\f76c"}.fa-remove-format:before,.fa-text-slash:before{content:"\f87d"}.fa-face-smile-wink:before,.fa-smile-wink:before{content:"\f4da"}.fa-file-word:before{content:"\f1c2"}.fa-file-powerpoint:before{content:"\f1c4"}.fa-arrows-h:before,.fa-arrows-left-right:before{content:"\f07e"}.fa-house-lock:before{content:"\e510"}.fa-cloud-arrow-down:before,.fa-cloud-download-alt:before,.fa-cloud-download:before{content:"\f0ed"}.fa-children:before{content:"\e4e1"}.fa-blackboard:before,.fa-chalkboard:before{content:"\f51b"}.fa-user-alt-slash:before,.fa-user-large-slash:before{content:"\f4fa"}.fa-envelope-open:before{content:"\f2b6"}.fa-handshake-alt-slash:before,.fa-handshake-simple-slash:before{content:"\e05f"}.fa-mattress-pillow:before{content:"\e525"}.fa-guarani-sign:before{content:"\e19a"}.fa-arrows-rotate:before,.fa-refresh:before,.fa-sync:before{content:"\f021"}.fa-fire-extinguisher:before{content:"\f134"}.fa-cruzeiro-sign:before{content:"\e152"}.fa-greater-than-equal:before{content:"\f532"}.fa-shield-alt:before,.fa-shield-halved:before{content:"\f3ed"}.fa-atlas:before,.fa-book-atlas:before{content:"\f558"}.fa-virus:before{content:"\e074"}.fa-envelope-circle-check:before{content:"\e4e8"}.fa-layer-group:before{content:"\f5fd"}.fa-arrows-to-dot:before{content:"\e4be"}.fa-archway:before{content:"\f557"}.fa-heart-circle-check:before{content:"\e4fd"}.fa-house-chimney-crack:before,.fa-house-damage:before{content:"\f6f1"}.fa-file-archive:before,.fa-file-zipper:before{content:"\f1c6"}.fa-square:before{content:"\f0c8"}.fa-glass-martini:before,.fa-martini-glass-empty:before{content:"\f000"}.fa-couch:before{content:"\f4b8"}.fa-cedi-sign:before{content:"\e0df"}.fa-italic:before{content:"\f033"}.fa-table-cells-column-lock:before{content:"\e678"}.fa-church:before{content:"\f51d"}.fa-comments-dollar:before{content:"\f653"}.fa-democrat:before{content:"\f747"}.fa-z:before{content:"\5a"}.fa-person-skiing:before,.fa-skiing:before{content:"\f7c9"}.fa-road-lock:before{content:"\e567"}.fa-a:before{content:"\41"}.fa-temperature-arrow-down:before,.fa-temperature-down:before{content:"\e03f"}.fa-feather-alt:before,.fa-feather-pointed:before{content:"\f56b"}.fa-p:before{content:"\50"}.fa-snowflake:before{content:"\f2dc"}.fa-newspaper:before{content:"\f1ea"}.fa-ad:before,.fa-rectangle-ad:before{content:"\f641"}.fa-arrow-circle-right:before,.fa-circle-arrow-right:before{content:"\f0a9"}.fa-filter-circle-xmark:before{content:"\e17b"}.fa-locust:before{content:"\e520"}.fa-sort:before,.fa-unsorted:before{content:"\f0dc"}.fa-list-1-2:before,.fa-list-numeric:before,.fa-list-ol:before{content:"\f0cb"}.fa-person-dress-burst:before{content:"\e544"}.fa-money-check-alt:before,.fa-money-check-dollar:before{content:"\f53d"}.fa-vector-square:before{content:"\f5cb"}.fa-bread-slice:before{content:"\f7ec"}.fa-language:before{content:"\f1ab"}.fa-face-kiss-wink-heart:before,.fa-kiss-wink-heart:before{content:"\f598"}.fa-filter:before{content:"\f0b0"}.fa-question:before{content:"\3f"}.fa-file-signature:before{content:"\f573"}.fa-arrows-alt:before,.fa-up-down-left-right:before{content:"\f0b2"}.fa-house-chimney-user:before{content:"\e065"}.fa-hand-holding-heart:before{content:"\f4be"}.fa-puzzle-piece:before{content:"\f12e"}.fa-money-check:before{content:"\f53c"}.fa-star-half-alt:before,.fa-star-half-stroke:before{content:"\f5c0"}.fa-code:before{content:"\f121"}.fa-glass-whiskey:before,.fa-whiskey-glass:before{content:"\f7a0"}.fa-building-circle-exclamation:before{content:"\e4d3"}.fa-magnifying-glass-chart:before{content:"\e522"}.fa-arrow-up-right-from-square:before,.fa-external-link:before{content:"\f08e"}.fa-cubes-stacked:before{content:"\e4e6"}.fa-krw:before,.fa-won-sign:before,.fa-won:before{content:"\f159"}.fa-virus-covid:before{content:"\e4a8"}.fa-austral-sign:before{content:"\e0a9"}.fa-f:before{content:"\46"}.fa-leaf:before{content:"\f06c"}.fa-road:before{content:"\f018"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-person-circle-plus:before{content:"\e541"}.fa-chart-pie:before,.fa-pie-chart:before{content:"\f200"}.fa-bolt-lightning:before{content:"\e0b7"}.fa-sack-xmark:before{content:"\e56a"}.fa-file-excel:before{content:"\f1c3"}.fa-file-contract:before{content:"\f56c"}.fa-fish-fins:before{content:"\e4f2"}.fa-building-flag:before{content:"\e4d5"}.fa-face-grin-beam:before,.fa-grin-beam:before{content:"\f582"}.fa-object-ungroup:before{content:"\f248"}.fa-poop:before{content:"\f619"}.fa-location-pin:before,.fa-map-marker:before{content:"\f041"}.fa-kaaba:before{content:"\f66b"}.fa-toilet-paper:before{content:"\f71e"}.fa-hard-hat:before,.fa-hat-hard:before,.fa-helmet-safety:before{content:"\f807"}.fa-eject:before{content:"\f052"}.fa-arrow-alt-circle-right:before,.fa-circle-right:before{content:"\f35a"}.fa-plane-circle-check:before{content:"\e555"}.fa-face-rolling-eyes:before,.fa-meh-rolling-eyes:before{content:"\f5a5"}.fa-object-group:before{content:"\f247"}.fa-chart-line:before,.fa-line-chart:before{content:"\f201"}.fa-mask-ventilator:before{content:"\e524"}.fa-arrow-right:before{content:"\f061"}.fa-map-signs:before,.fa-signs-post:before{content:"\f277"}.fa-cash-register:before{content:"\f788"}.fa-person-circle-question:before{content:"\e542"}.fa-h:before{content:"\48"}.fa-tarp:before{content:"\e57b"}.fa-screwdriver-wrench:before,.fa-tools:before{content:"\f7d9"}.fa-arrows-to-eye:before{content:"\e4bf"}.fa-plug-circle-bolt:before{content:"\e55b"}.fa-heart:before{content:"\f004"}.fa-mars-and-venus:before{content:"\f224"}.fa-home-user:before,.fa-house-user:before{content:"\e1b0"}.fa-dumpster-fire:before{content:"\f794"}.fa-house-crack:before{content:"\e3b1"}.fa-cocktail:before,.fa-martini-glass-citrus:before{content:"\f561"}.fa-face-surprise:before,.fa-surprise:before{content:"\f5c2"}.fa-bottle-water:before{content:"\e4c5"}.fa-circle-pause:before,.fa-pause-circle:before{content:"\f28b"}.fa-toilet-paper-slash:before{content:"\e072"}.fa-apple-alt:before,.fa-apple-whole:before{content:"\f5d1"}.fa-kitchen-set:before{content:"\e51a"}.fa-r:before{content:"\52"}.fa-temperature-1:before,.fa-temperature-quarter:before,.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-cube:before{content:"\f1b2"}.fa-bitcoin-sign:before{content:"\e0b4"}.fa-shield-dog:before{content:"\e573"}.fa-solar-panel:before{content:"\f5ba"}.fa-lock-open:before{content:"\f3c1"}.fa-elevator:before{content:"\e16d"}.fa-money-bill-transfer:before{content:"\e528"}.fa-money-bill-trend-up:before{content:"\e529"}.fa-house-flood-water-circle-arrow-right:before{content:"\e50f"}.fa-poll-h:before,.fa-square-poll-horizontal:before{content:"\f682"}.fa-circle:before{content:"\f111"}.fa-backward-fast:before,.fa-fast-backward:before{content:"\f049"}.fa-recycle:before{content:"\f1b8"}.fa-user-astronaut:before{content:"\f4fb"}.fa-plane-slash:before{content:"\e069"}.fa-trademark:before{content:"\f25c"}.fa-basketball-ball:before,.fa-basketball:before{content:"\f434"}.fa-satellite-dish:before{content:"\f7c0"}.fa-arrow-alt-circle-up:before,.fa-circle-up:before{content:"\f35b"}.fa-mobile-alt:before,.fa-mobile-screen-button:before{content:"\f3cd"}.fa-volume-high:before,.fa-volume-up:before{content:"\f028"}.fa-users-rays:before{content:"\e593"}.fa-wallet:before{content:"\f555"}.fa-clipboard-check:before{content:"\f46c"}.fa-file-audio:before{content:"\f1c7"}.fa-burger:before,.fa-hamburger:before{content:"\f805"}.fa-wrench:before{content:"\f0ad"}.fa-bugs:before{content:"\e4d0"}.fa-rupee-sign:before,.fa-rupee:before{content:"\f156"}.fa-file-image:before{content:"\f1c5"}.fa-circle-question:before,.fa-question-circle:before{content:"\f059"}.fa-plane-departure:before{content:"\f5b0"}.fa-handshake-slash:before{content:"\e060"}.fa-book-bookmark:before{content:"\e0bb"}.fa-code-branch:before{content:"\f126"}.fa-hat-cowboy:before{content:"\f8c0"}.fa-bridge:before{content:"\e4c8"}.fa-phone-alt:before,.fa-phone-flip:before{content:"\f879"}.fa-truck-front:before{content:"\e2b7"}.fa-cat:before{content:"\f6be"}.fa-anchor-circle-exclamation:before{content:"\e4ab"}.fa-truck-field:before{content:"\e58d"}.fa-route:before{content:"\f4d7"}.fa-clipboard-question:before{content:"\e4e3"}.fa-panorama:before{content:"\e209"}.fa-comment-medical:before{content:"\f7f5"}.fa-teeth-open:before{content:"\f62f"}.fa-file-circle-minus:before{content:"\e4ed"}.fa-tags:before{content:"\f02c"}.fa-wine-glass:before{content:"\f4e3"}.fa-fast-forward:before,.fa-forward-fast:before{content:"\f050"}.fa-face-meh-blank:before,.fa-meh-blank:before{content:"\f5a4"}.fa-parking:before,.fa-square-parking:before{content:"\f540"}.fa-house-signal:before{content:"\e012"}.fa-bars-progress:before,.fa-tasks-alt:before{content:"\f828"}.fa-faucet-drip:before{content:"\e006"}.fa-cart-flatbed:before,.fa-dolly-flatbed:before{content:"\f474"}.fa-ban-smoking:before,.fa-smoking-ban:before{content:"\f54d"}.fa-terminal:before{content:"\f120"}.fa-mobile-button:before{content:"\f10b"}.fa-house-medical-flag:before{content:"\e514"}.fa-basket-shopping:before,.fa-shopping-basket:before{content:"\f291"}.fa-tape:before{content:"\f4db"}.fa-bus-alt:before,.fa-bus-simple:before{content:"\f55e"}.fa-eye:before{content:"\f06e"}.fa-face-sad-cry:before,.fa-sad-cry:before{content:"\f5b3"}.fa-audio-description:before{content:"\f29e"}.fa-person-military-to-person:before{content:"\e54c"}.fa-file-shield:before{content:"\e4f0"}.fa-user-slash:before{content:"\f506"}.fa-pen:before{content:"\f304"}.fa-tower-observation:before{content:"\e586"}.fa-file-code:before{content:"\f1c9"}.fa-signal-5:before,.fa-signal-perfect:before,.fa-signal:before{content:"\f012"}.fa-bus:before{content:"\f207"}.fa-heart-circle-xmark:before{content:"\e501"}.fa-home-lg:before,.fa-house-chimney:before{content:"\e3af"}.fa-window-maximize:before{content:"\f2d0"}.fa-face-frown:before,.fa-frown:before{content:"\f119"}.fa-prescription:before{content:"\f5b1"}.fa-shop:before,.fa-store-alt:before{content:"\f54f"}.fa-floppy-disk:before,.fa-save:before{content:"\f0c7"}.fa-vihara:before{content:"\f6a7"}.fa-balance-scale-left:before,.fa-scale-unbalanced:before{content:"\f515"}.fa-sort-asc:before,.fa-sort-up:before{content:"\f0de"}.fa-comment-dots:before,.fa-commenting:before{content:"\f4ad"}.fa-plant-wilt:before{content:"\e5aa"}.fa-diamond:before{content:"\f219"}.fa-face-grin-squint:before,.fa-grin-squint:before{content:"\f585"}.fa-hand-holding-dollar:before,.fa-hand-holding-usd:before{content:"\f4c0"}.fa-bacterium:before{content:"\e05a"}.fa-hand-pointer:before{content:"\f25a"}.fa-drum-steelpan:before{content:"\f56a"}.fa-hand-scissors:before{content:"\f257"}.fa-hands-praying:before,.fa-praying-hands:before{content:"\f684"}.fa-arrow-right-rotate:before,.fa-arrow-rotate-forward:before,.fa-arrow-rotate-right:before,.fa-redo:before{content:"\f01e"}.fa-biohazard:before{content:"\f780"}.fa-location-crosshairs:before,.fa-location:before{content:"\f601"}.fa-mars-double:before{content:"\f227"}.fa-child-dress:before{content:"\e59c"}.fa-users-between-lines:before{content:"\e591"}.fa-lungs-virus:before{content:"\e067"}.fa-face-grin-tears:before,.fa-grin-tears:before{content:"\f588"}.fa-phone:before{content:"\f095"}.fa-calendar-times:before,.fa-calendar-xmark:before{content:"\f273"}.fa-child-reaching:before{content:"\e59d"}.fa-head-side-virus:before{content:"\e064"}.fa-user-cog:before,.fa-user-gear:before{content:"\f4fe"}.fa-arrow-up-1-9:before,.fa-sort-numeric-up:before{content:"\f163"}.fa-door-closed:before{content:"\f52a"}.fa-shield-virus:before{content:"\e06c"}.fa-dice-six:before{content:"\f526"}.fa-mosquito-net:before{content:"\e52c"}.fa-bridge-water:before{content:"\e4ce"}.fa-person-booth:before{content:"\f756"}.fa-text-width:before{content:"\f035"}.fa-hat-wizard:before{content:"\f6e8"}.fa-pen-fancy:before{content:"\f5ac"}.fa-digging:before,.fa-person-digging:before{content:"\f85e"}.fa-trash:before{content:"\f1f8"}.fa-gauge-simple-med:before,.fa-gauge-simple:before,.fa-tachometer-average:before{content:"\f629"}.fa-book-medical:before{content:"\f7e6"}.fa-poo:before{content:"\f2fe"}.fa-quote-right-alt:before,.fa-quote-right:before{content:"\f10e"}.fa-shirt:before,.fa-t-shirt:before,.fa-tshirt:before{content:"\f553"}.fa-cubes:before{content:"\f1b3"}.fa-divide:before{content:"\f529"}.fa-tenge-sign:before,.fa-tenge:before{content:"\f7d7"}.fa-headphones:before{content:"\f025"}.fa-hands-holding:before{content:"\f4c2"}.fa-hands-clapping:before{content:"\e1a8"}.fa-republican:before{content:"\f75e"}.fa-arrow-left:before{content:"\f060"}.fa-person-circle-xmark:before{content:"\e543"}.fa-ruler:before{content:"\f545"}.fa-align-left:before{content:"\f036"}.fa-dice-d6:before{content:"\f6d1"}.fa-restroom:before{content:"\f7bd"}.fa-j:before{content:"\4a"}.fa-users-viewfinder:before{content:"\e595"}.fa-file-video:before{content:"\f1c8"}.fa-external-link-alt:before,.fa-up-right-from-square:before{content:"\f35d"}.fa-table-cells:before,.fa-th:before{content:"\f00a"}.fa-file-pdf:before{content:"\f1c1"}.fa-bible:before,.fa-book-bible:before{content:"\f647"}.fa-o:before{content:"\4f"}.fa-medkit:before,.fa-suitcase-medical:before{content:"\f0fa"}.fa-user-secret:before{content:"\f21b"}.fa-otter:before{content:"\f700"}.fa-female:before,.fa-person-dress:before{content:"\f182"}.fa-comment-dollar:before{content:"\f651"}.fa-briefcase-clock:before,.fa-business-time:before{content:"\f64a"}.fa-table-cells-large:before,.fa-th-large:before{content:"\f009"}.fa-book-tanakh:before,.fa-tanakh:before{content:"\f827"}.fa-phone-volume:before,.fa-volume-control-phone:before{content:"\f2a0"}.fa-hat-cowboy-side:before{content:"\f8c1"}.fa-clipboard-user:before{content:"\f7f3"}.fa-child:before{content:"\f1ae"}.fa-lira-sign:before{content:"\f195"}.fa-satellite:before{content:"\f7bf"}.fa-plane-lock:before{content:"\e558"}.fa-tag:before{content:"\f02b"}.fa-comment:before{content:"\f075"}.fa-birthday-cake:before,.fa-cake-candles:before,.fa-cake:before{content:"\f1fd"}.fa-envelope:before{content:"\f0e0"}.fa-angle-double-up:before,.fa-angles-up:before{content:"\f102"}.fa-paperclip:before{content:"\f0c6"}.fa-arrow-right-to-city:before{content:"\e4b3"}.fa-ribbon:before{content:"\f4d6"}.fa-lungs:before{content:"\f604"}.fa-arrow-up-9-1:before,.fa-sort-numeric-up-alt:before{content:"\f887"}.fa-litecoin-sign:before{content:"\e1d3"}.fa-border-none:before{content:"\f850"}.fa-circle-nodes:before{content:"\e4e2"}.fa-parachute-box:before{content:"\f4cd"}.fa-indent:before{content:"\f03c"}.fa-truck-field-un:before{content:"\e58e"}.fa-hourglass-empty:before,.fa-hourglass:before{content:"\f254"}.fa-mountain:before{content:"\f6fc"}.fa-user-doctor:before,.fa-user-md:before{content:"\f0f0"}.fa-circle-info:before,.fa-info-circle:before{content:"\f05a"}.fa-cloud-meatball:before{content:"\f73b"}.fa-camera-alt:before,.fa-camera:before{content:"\f030"}.fa-square-virus:before{content:"\e578"}.fa-meteor:before{content:"\f753"}.fa-car-on:before{content:"\e4dd"}.fa-sleigh:before{content:"\f7cc"}.fa-arrow-down-1-9:before,.fa-sort-numeric-asc:before,.fa-sort-numeric-down:before{content:"\f162"}.fa-hand-holding-droplet:before,.fa-hand-holding-water:before{content:"\f4c1"}.fa-water:before{content:"\f773"}.fa-calendar-check:before{content:"\f274"}.fa-braille:before{content:"\f2a1"}.fa-prescription-bottle-alt:before,.fa-prescription-bottle-medical:before{content:"\f486"}.fa-landmark:before{content:"\f66f"}.fa-truck:before{content:"\f0d1"}.fa-crosshairs:before{content:"\f05b"}.fa-person-cane:before{content:"\e53c"}.fa-tent:before{content:"\e57d"}.fa-vest-patches:before{content:"\e086"}.fa-check-double:before{content:"\f560"}.fa-arrow-down-a-z:before,.fa-sort-alpha-asc:before,.fa-sort-alpha-down:before{content:"\f15d"}.fa-money-bill-wheat:before{content:"\e52a"}.fa-cookie:before{content:"\f563"}.fa-arrow-left-rotate:before,.fa-arrow-rotate-back:before,.fa-arrow-rotate-backward:before,.fa-arrow-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-hard-drive:before,.fa-hdd:before{content:"\f0a0"}.fa-face-grin-squint-tears:before,.fa-grin-squint-tears:before{content:"\f586"}.fa-dumbbell:before{content:"\f44b"}.fa-list-alt:before,.fa-rectangle-list:before{content:"\f022"}.fa-tarp-droplet:before{content:"\e57c"}.fa-house-medical-circle-check:before{content:"\e511"}.fa-person-skiing-nordic:before,.fa-skiing-nordic:before{content:"\f7ca"}.fa-calendar-plus:before{content:"\f271"}.fa-plane-arrival:before{content:"\f5af"}.fa-arrow-alt-circle-left:before,.fa-circle-left:before{content:"\f359"}.fa-subway:before,.fa-train-subway:before{content:"\f239"}.fa-chart-gantt:before{content:"\e0e4"}.fa-indian-rupee-sign:before,.fa-indian-rupee:before,.fa-inr:before{content:"\e1bc"}.fa-crop-alt:before,.fa-crop-simple:before{content:"\f565"}.fa-money-bill-1:before,.fa-money-bill-alt:before{content:"\f3d1"}.fa-left-long:before,.fa-long-arrow-alt-left:before{content:"\f30a"}.fa-dna:before{content:"\f471"}.fa-virus-slash:before{content:"\e075"}.fa-minus:before,.fa-subtract:before{content:"\f068"}.fa-chess:before{content:"\f439"}.fa-arrow-left-long:before,.fa-long-arrow-left:before{content:"\f177"}.fa-plug-circle-check:before{content:"\e55c"}.fa-street-view:before{content:"\f21d"}.fa-franc-sign:before{content:"\e18f"}.fa-volume-off:before{content:"\f026"}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before,.fa-hands-american-sign-language-interpreting:before,.fa-hands-asl-interpreting:before{content:"\f2a3"}.fa-cog:before,.fa-gear:before{content:"\f013"}.fa-droplet-slash:before,.fa-tint-slash:before{content:"\f5c7"}.fa-mosque:before{content:"\f678"}.fa-mosquito:before{content:"\e52b"}.fa-star-of-david:before{content:"\f69a"}.fa-person-military-rifle:before{content:"\e54b"}.fa-cart-shopping:before,.fa-shopping-cart:before{content:"\f07a"}.fa-vials:before{content:"\f493"}.fa-plug-circle-plus:before{content:"\e55f"}.fa-place-of-worship:before{content:"\f67f"}.fa-grip-vertical:before{content:"\f58e"}.fa-arrow-turn-up:before,.fa-level-up:before{content:"\f148"}.fa-u:before{content:"\55"}.fa-square-root-alt:before,.fa-square-root-variable:before{content:"\f698"}.fa-clock-four:before,.fa-clock:before{content:"\f017"}.fa-backward-step:before,.fa-step-backward:before{content:"\f048"}.fa-pallet:before{content:"\f482"}.fa-faucet:before{content:"\e005"}.fa-baseball-bat-ball:before{content:"\f432"}.fa-s:before{content:"\53"}.fa-timeline:before{content:"\e29c"}.fa-keyboard:before{content:"\f11c"}.fa-caret-down:before{content:"\f0d7"}.fa-clinic-medical:before,.fa-house-chimney-medical:before{content:"\f7f2"}.fa-temperature-3:before,.fa-temperature-three-quarters:before,.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-mobile-android-alt:before,.fa-mobile-screen:before{content:"\f3cf"}.fa-plane-up:before{content:"\e22d"}.fa-piggy-bank:before{content:"\f4d3"}.fa-battery-3:before,.fa-battery-half:before{content:"\f242"}.fa-mountain-city:before{content:"\e52e"}.fa-coins:before{content:"\f51e"}.fa-khanda:before{content:"\f66d"}.fa-sliders-h:before,.fa-sliders:before{content:"\f1de"}.fa-folder-tree:before{content:"\f802"}.fa-network-wired:before{content:"\f6ff"}.fa-map-pin:before{content:"\f276"}.fa-hamsa:before{content:"\f665"}.fa-cent-sign:before{content:"\e3f5"}.fa-flask:before{content:"\f0c3"}.fa-person-pregnant:before{content:"\e31e"}.fa-wand-sparkles:before{content:"\f72b"}.fa-ellipsis-v:before,.fa-ellipsis-vertical:before{content:"\f142"}.fa-ticket:before{content:"\f145"}.fa-power-off:before{content:"\f011"}.fa-long-arrow-alt-right:before,.fa-right-long:before{content:"\f30b"}.fa-flag-usa:before{content:"\f74d"}.fa-laptop-file:before{content:"\e51d"}.fa-teletype:before,.fa-tty:before{content:"\f1e4"}.fa-diagram-next:before{content:"\e476"}.fa-person-rifle:before{content:"\e54e"}.fa-house-medical-circle-exclamation:before{content:"\e512"}.fa-closed-captioning:before{content:"\f20a"}.fa-hiking:before,.fa-person-hiking:before{content:"\f6ec"}.fa-venus-double:before{content:"\f226"}.fa-images:before{content:"\f302"}.fa-calculator:before{content:"\f1ec"}.fa-people-pulling:before{content:"\e535"}.fa-n:before{content:"\4e"}.fa-cable-car:before,.fa-tram:before{content:"\f7da"}.fa-cloud-rain:before{content:"\f73d"}.fa-building-circle-xmark:before{content:"\e4d4"}.fa-ship:before{content:"\f21a"}.fa-arrows-down-to-line:before{content:"\e4b8"}.fa-download:before{content:"\f019"}.fa-face-grin:before,.fa-grin:before{content:"\f580"}.fa-backspace:before,.fa-delete-left:before{content:"\f55a"}.fa-eye-dropper-empty:before,.fa-eye-dropper:before,.fa-eyedropper:before{content:"\f1fb"}.fa-file-circle-check:before{content:"\e5a0"}.fa-forward:before{content:"\f04e"}.fa-mobile-android:before,.fa-mobile-phone:before,.fa-mobile:before{content:"\f3ce"}.fa-face-meh:before,.fa-meh:before{content:"\f11a"}.fa-align-center:before{content:"\f037"}.fa-book-dead:before,.fa-book-skull:before{content:"\f6b7"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-heart-circle-exclamation:before{content:"\e4fe"}.fa-home-alt:before,.fa-home-lg-alt:before,.fa-home:before,.fa-house:before{content:"\f015"}.fa-calendar-week:before{content:"\f784"}.fa-laptop-medical:before{content:"\f812"}.fa-b:before{content:"\42"}.fa-file-medical:before{content:"\f477"}.fa-dice-one:before{content:"\f525"}.fa-kiwi-bird:before{content:"\f535"}.fa-arrow-right-arrow-left:before,.fa-exchange:before{content:"\f0ec"}.fa-redo-alt:before,.fa-rotate-forward:before,.fa-rotate-right:before{content:"\f2f9"}.fa-cutlery:before,.fa-utensils:before{content:"\f2e7"}.fa-arrow-up-wide-short:before,.fa-sort-amount-up:before{content:"\f161"}.fa-mill-sign:before{content:"\e1ed"}.fa-bowl-rice:before{content:"\e2eb"}.fa-skull:before{content:"\f54c"}.fa-broadcast-tower:before,.fa-tower-broadcast:before{content:"\f519"}.fa-truck-pickup:before{content:"\f63c"}.fa-long-arrow-alt-up:before,.fa-up-long:before{content:"\f30c"}.fa-stop:before{content:"\f04d"}.fa-code-merge:before{content:"\f387"}.fa-upload:before{content:"\f093"}.fa-hurricane:before{content:"\f751"}.fa-mound:before{content:"\e52d"}.fa-toilet-portable:before{content:"\e583"}.fa-compact-disc:before{content:"\f51f"}.fa-file-arrow-down:before,.fa-file-download:before{content:"\f56d"}.fa-caravan:before{content:"\f8ff"}.fa-shield-cat:before{content:"\e572"}.fa-bolt:before,.fa-zap:before{content:"\f0e7"}.fa-glass-water:before{content:"\e4f4"}.fa-oil-well:before{content:"\e532"}.fa-vault:before{content:"\e2c5"}.fa-mars:before{content:"\f222"}.fa-toilet:before{content:"\f7d8"}.fa-plane-circle-xmark:before{content:"\e557"}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen-sign:before,.fa-yen:before{content:"\f157"}.fa-rouble:before,.fa-rub:before,.fa-ruble-sign:before,.fa-ruble:before{content:"\f158"}.fa-sun:before{content:"\f185"}.fa-guitar:before{content:"\f7a6"}.fa-face-laugh-wink:before,.fa-laugh-wink:before{content:"\f59c"}.fa-horse-head:before{content:"\f7ab"}.fa-bore-hole:before{content:"\e4c3"}.fa-industry:before{content:"\f275"}.fa-arrow-alt-circle-down:before,.fa-circle-down:before{content:"\f358"}.fa-arrows-turn-to-dots:before{content:"\e4c1"}.fa-florin-sign:before{content:"\e184"}.fa-arrow-down-short-wide:before,.fa-sort-amount-desc:before,.fa-sort-amount-down-alt:before{content:"\f884"}.fa-less-than:before{content:"\3c"}.fa-angle-down:before{content:"\f107"}.fa-car-tunnel:before{content:"\e4de"}.fa-head-side-cough:before{content:"\e061"}.fa-grip-lines:before{content:"\f7a4"}.fa-thumbs-down:before{content:"\f165"}.fa-user-lock:before{content:"\f502"}.fa-arrow-right-long:before,.fa-long-arrow-right:before{content:"\f178"}.fa-anchor-circle-xmark:before{content:"\e4ac"}.fa-ellipsis-h:before,.fa-ellipsis:before{content:"\f141"}.fa-chess-pawn:before{content:"\f443"}.fa-first-aid:before,.fa-kit-medical:before{content:"\f479"}.fa-person-through-window:before{content:"\e5a9"}.fa-toolbox:before{content:"\f552"}.fa-hands-holding-circle:before{content:"\e4fb"}.fa-bug:before{content:"\f188"}.fa-credit-card-alt:before,.fa-credit-card:before{content:"\f09d"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-hand-holding-hand:before{content:"\e4f7"}.fa-book-open-reader:before,.fa-book-reader:before{content:"\f5da"}.fa-mountain-sun:before{content:"\e52f"}.fa-arrows-left-right-to-line:before{content:"\e4ba"}.fa-dice-d20:before{content:"\f6cf"}.fa-truck-droplet:before{content:"\e58c"}.fa-file-circle-xmark:before{content:"\e5a1"}.fa-temperature-arrow-up:before,.fa-temperature-up:before{content:"\e040"}.fa-medal:before{content:"\f5a2"}.fa-bed:before{content:"\f236"}.fa-h-square:before,.fa-square-h:before{content:"\f0fd"}.fa-podcast:before{content:"\f2ce"}.fa-temperature-4:before,.fa-temperature-full:before,.fa-thermometer-4:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-bell:before{content:"\f0f3"}.fa-superscript:before{content:"\f12b"}.fa-plug-circle-xmark:before{content:"\e560"}.fa-star-of-life:before{content:"\f621"}.fa-phone-slash:before{content:"\f3dd"}.fa-paint-roller:before{content:"\f5aa"}.fa-hands-helping:before,.fa-handshake-angle:before{content:"\f4c4"}.fa-location-dot:before,.fa-map-marker-alt:before{content:"\f3c5"}.fa-file:before{content:"\f15b"}.fa-greater-than:before{content:"\3e"}.fa-person-swimming:before,.fa-swimmer:before{content:"\f5c4"}.fa-arrow-down:before{content:"\f063"}.fa-droplet:before,.fa-tint:before{content:"\f043"}.fa-eraser:before{content:"\f12d"}.fa-earth-america:before,.fa-earth-americas:before,.fa-earth:before,.fa-globe-americas:before{content:"\f57d"}.fa-person-burst:before{content:"\e53b"}.fa-dove:before{content:"\f4ba"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-socks:before{content:"\f696"}.fa-inbox:before{content:"\f01c"}.fa-section:before{content:"\e447"}.fa-gauge-high:before,.fa-tachometer-alt-fast:before,.fa-tachometer-alt:before{content:"\f625"}.fa-envelope-open-text:before{content:"\f658"}.fa-hospital-alt:before,.fa-hospital-wide:before,.fa-hospital:before{content:"\f0f8"}.fa-wine-bottle:before{content:"\f72f"}.fa-chess-rook:before{content:"\f447"}.fa-bars-staggered:before,.fa-reorder:before,.fa-stream:before{content:"\f550"}.fa-dharmachakra:before{content:"\f655"}.fa-hotdog:before{content:"\f80f"}.fa-blind:before,.fa-person-walking-with-cane:before{content:"\f29d"}.fa-drum:before{content:"\f569"}.fa-ice-cream:before{content:"\f810"}.fa-heart-circle-bolt:before{content:"\e4fc"}.fa-fax:before{content:"\f1ac"}.fa-paragraph:before{content:"\f1dd"}.fa-check-to-slot:before,.fa-vote-yea:before{content:"\f772"}.fa-star-half:before{content:"\f089"}.fa-boxes-alt:before,.fa-boxes-stacked:before,.fa-boxes:before{content:"\f468"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-assistive-listening-systems:before,.fa-ear-listen:before{content:"\f2a2"}.fa-tree-city:before{content:"\e587"}.fa-play:before{content:"\f04b"}.fa-font:before{content:"\f031"}.fa-table-cells-row-lock:before{content:"\e67a"}.fa-rupiah-sign:before{content:"\e23d"}.fa-magnifying-glass:before,.fa-search:before{content:"\f002"}.fa-ping-pong-paddle-ball:before,.fa-table-tennis-paddle-ball:before,.fa-table-tennis:before{content:"\f45d"}.fa-diagnoses:before,.fa-person-dots-from-line:before{content:"\f470"}.fa-trash-can-arrow-up:before,.fa-trash-restore-alt:before{content:"\f82a"}.fa-naira-sign:before{content:"\e1f6"}.fa-cart-arrow-down:before{content:"\f218"}.fa-walkie-talkie:before{content:"\f8ef"}.fa-file-edit:before,.fa-file-pen:before{content:"\f31c"}.fa-receipt:before{content:"\f543"}.fa-pen-square:before,.fa-pencil-square:before,.fa-square-pen:before{content:"\f14b"}.fa-suitcase-rolling:before{content:"\f5c1"}.fa-person-circle-exclamation:before{content:"\e53f"}.fa-chevron-down:before{content:"\f078"}.fa-battery-5:before,.fa-battery-full:before,.fa-battery:before{content:"\f240"}.fa-skull-crossbones:before{content:"\f714"}.fa-code-compare:before{content:"\e13a"}.fa-list-dots:before,.fa-list-ul:before{content:"\f0ca"}.fa-school-lock:before{content:"\e56f"}.fa-tower-cell:before{content:"\e585"}.fa-down-long:before,.fa-long-arrow-alt-down:before{content:"\f309"}.fa-ranking-star:before{content:"\e561"}.fa-chess-king:before{content:"\f43f"}.fa-person-harassing:before{content:"\e549"}.fa-brazilian-real-sign:before{content:"\e46c"}.fa-landmark-alt:before,.fa-landmark-dome:before{content:"\f752"}.fa-arrow-up:before{content:"\f062"}.fa-television:before,.fa-tv-alt:before,.fa-tv:before{content:"\f26c"}.fa-shrimp:before{content:"\e448"}.fa-list-check:before,.fa-tasks:before{content:"\f0ae"}.fa-jug-detergent:before{content:"\e519"}.fa-circle-user:before,.fa-user-circle:before{content:"\f2bd"}.fa-user-shield:before{content:"\f505"}.fa-wind:before{content:"\f72e"}.fa-car-burst:before,.fa-car-crash:before{content:"\f5e1"}.fa-y:before{content:"\59"}.fa-person-snowboarding:before,.fa-snowboarding:before{content:"\f7ce"}.fa-shipping-fast:before,.fa-truck-fast:before{content:"\f48b"}.fa-fish:before{content:"\f578"}.fa-user-graduate:before{content:"\f501"}.fa-adjust:before,.fa-circle-half-stroke:before{content:"\f042"}.fa-clapperboard:before{content:"\e131"}.fa-circle-radiation:before,.fa-radiation-alt:before{content:"\f7ba"}.fa-baseball-ball:before,.fa-baseball:before{content:"\f433"}.fa-jet-fighter-up:before{content:"\e518"}.fa-diagram-project:before,.fa-project-diagram:before{content:"\f542"}.fa-copy:before{content:"\f0c5"}.fa-volume-mute:before,.fa-volume-times:before,.fa-volume-xmark:before{content:"\f6a9"}.fa-hand-sparkles:before{content:"\e05d"}.fa-grip-horizontal:before,.fa-grip:before{content:"\f58d"}.fa-share-from-square:before,.fa-share-square:before{content:"\f14d"}.fa-child-combatant:before,.fa-child-rifle:before{content:"\e4e0"}.fa-gun:before{content:"\e19b"}.fa-phone-square:before,.fa-square-phone:before{content:"\f098"}.fa-add:before,.fa-plus:before{content:"\2b"}.fa-expand:before{content:"\f065"}.fa-computer:before{content:"\e4e5"}.fa-close:before,.fa-multiply:before,.fa-remove:before,.fa-times:before,.fa-xmark:before{content:"\f00d"}.fa-arrows-up-down-left-right:before,.fa-arrows:before{content:"\f047"}.fa-chalkboard-teacher:before,.fa-chalkboard-user:before{content:"\f51c"}.fa-peso-sign:before{content:"\e222"}.fa-building-shield:before{content:"\e4d8"}.fa-baby:before{content:"\f77c"}.fa-users-line:before{content:"\e592"}.fa-quote-left-alt:before,.fa-quote-left:before{content:"\f10d"}.fa-tractor:before{content:"\f722"}.fa-trash-arrow-up:before,.fa-trash-restore:before{content:"\f829"}.fa-arrow-down-up-lock:before{content:"\e4b0"}.fa-lines-leaning:before{content:"\e51e"}.fa-ruler-combined:before{content:"\f546"}.fa-copyright:before{content:"\f1f9"}.fa-equals:before{content:"\3d"}.fa-blender:before{content:"\f517"}.fa-teeth:before{content:"\f62e"}.fa-ils:before,.fa-shekel-sign:before,.fa-shekel:before,.fa-sheqel-sign:before,.fa-sheqel:before{content:"\f20b"}.fa-map:before{content:"\f279"}.fa-rocket:before{content:"\f135"}.fa-photo-film:before,.fa-photo-video:before{content:"\f87c"}.fa-folder-minus:before{content:"\f65d"}.fa-store:before{content:"\f54e"}.fa-arrow-trend-up:before{content:"\e098"}.fa-plug-circle-minus:before{content:"\e55e"}.fa-sign-hanging:before,.fa-sign:before{content:"\f4d9"}.fa-bezier-curve:before{content:"\f55b"}.fa-bell-slash:before{content:"\f1f6"}.fa-tablet-android:before,.fa-tablet:before{content:"\f3fb"}.fa-school-flag:before{content:"\e56e"}.fa-fill:before{content:"\f575"}.fa-angle-up:before{content:"\f106"}.fa-drumstick-bite:before{content:"\f6d7"}.fa-holly-berry:before{content:"\f7aa"}.fa-chevron-left:before{content:"\f053"}.fa-bacteria:before{content:"\e059"}.fa-hand-lizard:before{content:"\f258"}.fa-notdef:before{content:"\e1fe"}.fa-disease:before{content:"\f7fa"}.fa-briefcase-medical:before{content:"\f469"}.fa-genderless:before{content:"\f22d"}.fa-chevron-right:before{content:"\f054"}.fa-retweet:before{content:"\f079"}.fa-car-alt:before,.fa-car-rear:before{content:"\f5de"}.fa-pump-soap:before{content:"\e06b"}.fa-video-slash:before{content:"\f4e2"}.fa-battery-2:before,.fa-battery-quarter:before{content:"\f243"}.fa-radio:before{content:"\f8d7"}.fa-baby-carriage:before,.fa-carriage-baby:before{content:"\f77d"}.fa-traffic-light:before{content:"\f637"}.fa-thermometer:before{content:"\f491"}.fa-vr-cardboard:before{content:"\f729"}.fa-hand-middle-finger:before{content:"\f806"}.fa-percent:before,.fa-percentage:before{content:"\25"}.fa-truck-moving:before{content:"\f4df"}.fa-glass-water-droplet:before{content:"\e4f5"}.fa-display:before{content:"\e163"}.fa-face-smile:before,.fa-smile:before{content:"\f118"}.fa-thumb-tack:before,.fa-thumbtack:before{content:"\f08d"}.fa-trophy:before{content:"\f091"}.fa-person-praying:before,.fa-pray:before{content:"\f683"}.fa-hammer:before{content:"\f6e3"}.fa-hand-peace:before{content:"\f25b"}.fa-rotate:before,.fa-sync-alt:before{content:"\f2f1"}.fa-spinner:before{content:"\f110"}.fa-robot:before{content:"\f544"}.fa-peace:before{content:"\f67c"}.fa-cogs:before,.fa-gears:before{content:"\f085"}.fa-warehouse:before{content:"\f494"}.fa-arrow-up-right-dots:before{content:"\e4b7"}.fa-splotch:before{content:"\f5bc"}.fa-face-grin-hearts:before,.fa-grin-hearts:before{content:"\f584"}.fa-dice-four:before{content:"\f524"}.fa-sim-card:before{content:"\f7c4"}.fa-transgender-alt:before,.fa-transgender:before{content:"\f225"}.fa-mercury:before{content:"\f223"}.fa-arrow-turn-down:before,.fa-level-down:before{content:"\f149"}.fa-person-falling-burst:before{content:"\e547"}.fa-award:before{content:"\f559"}.fa-ticket-alt:before,.fa-ticket-simple:before{content:"\f3ff"}.fa-building:before{content:"\f1ad"}.fa-angle-double-left:before,.fa-angles-left:before{content:"\f100"}.fa-qrcode:before{content:"\f029"}.fa-clock-rotate-left:before,.fa-history:before{content:"\f1da"}.fa-face-grin-beam-sweat:before,.fa-grin-beam-sweat:before{content:"\f583"}.fa-arrow-right-from-file:before,.fa-file-export:before{content:"\f56e"}.fa-shield-blank:before,.fa-shield:before{content:"\f132"}.fa-arrow-up-short-wide:before,.fa-sort-amount-up-alt:before{content:"\f885"}.fa-house-medical:before{content:"\e3b2"}.fa-golf-ball-tee:before,.fa-golf-ball:before{content:"\f450"}.fa-chevron-circle-left:before,.fa-circle-chevron-left:before{content:"\f137"}.fa-house-chimney-window:before{content:"\e00d"}.fa-pen-nib:before{content:"\f5ad"}.fa-tent-arrow-turn-left:before{content:"\e580"}.fa-tents:before{content:"\e582"}.fa-magic:before,.fa-wand-magic:before{content:"\f0d0"}.fa-dog:before{content:"\f6d3"}.fa-carrot:before{content:"\f787"}.fa-moon:before{content:"\f186"}.fa-wine-glass-alt:before,.fa-wine-glass-empty:before{content:"\f5ce"}.fa-cheese:before{content:"\f7ef"}.fa-yin-yang:before{content:"\f6ad"}.fa-music:before{content:"\f001"}.fa-code-commit:before{content:"\f386"}.fa-temperature-low:before{content:"\f76b"}.fa-biking:before,.fa-person-biking:before{content:"\f84a"}.fa-broom:before{content:"\f51a"}.fa-shield-heart:before{content:"\e574"}.fa-gopuram:before{content:"\f664"}.fa-earth-oceania:before,.fa-globe-oceania:before{content:"\e47b"}.fa-square-xmark:before,.fa-times-square:before,.fa-xmark-square:before{content:"\f2d3"}.fa-hashtag:before{content:"\23"}.fa-expand-alt:before,.fa-up-right-and-down-left-from-center:before{content:"\f424"}.fa-oil-can:before{content:"\f613"}.fa-t:before{content:"\54"}.fa-hippo:before{content:"\f6ed"}.fa-chart-column:before{content:"\e0e3"}.fa-infinity:before{content:"\f534"}.fa-vial-circle-check:before{content:"\e596"}.fa-person-arrow-down-to-line:before{content:"\e538"}.fa-voicemail:before{content:"\f897"}.fa-fan:before{content:"\f863"}.fa-person-walking-luggage:before{content:"\e554"}.fa-arrows-alt-v:before,.fa-up-down:before{content:"\f338"}.fa-cloud-moon-rain:before{content:"\f73c"}.fa-calendar:before{content:"\f133"}.fa-trailer:before{content:"\e041"}.fa-bahai:before,.fa-haykal:before{content:"\f666"}.fa-sd-card:before{content:"\f7c2"}.fa-dragon:before{content:"\f6d5"}.fa-shoe-prints:before{content:"\f54b"}.fa-circle-plus:before,.fa-plus-circle:before{content:"\f055"}.fa-face-grin-tongue-wink:before,.fa-grin-tongue-wink:before{content:"\f58b"}.fa-hand-holding:before{content:"\f4bd"}.fa-plug-circle-exclamation:before{content:"\e55d"}.fa-chain-broken:before,.fa-chain-slash:before,.fa-link-slash:before,.fa-unlink:before{content:"\f127"}.fa-clone:before{content:"\f24d"}.fa-person-walking-arrow-loop-left:before{content:"\e551"}.fa-arrow-up-z-a:before,.fa-sort-alpha-up-alt:before{content:"\f882"}.fa-fire-alt:before,.fa-fire-flame-curved:before{content:"\f7e4"}.fa-tornado:before{content:"\f76f"}.fa-file-circle-plus:before{content:"\e494"}.fa-book-quran:before,.fa-quran:before{content:"\f687"}.fa-anchor:before{content:"\f13d"}.fa-border-all:before{content:"\f84c"}.fa-angry:before,.fa-face-angry:before{content:"\f556"}.fa-cookie-bite:before{content:"\f564"}.fa-arrow-trend-down:before{content:"\e097"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-draw-polygon:before{content:"\f5ee"}.fa-balance-scale:before,.fa-scale-balanced:before{content:"\f24e"}.fa-gauge-simple-high:before,.fa-tachometer-fast:before,.fa-tachometer:before{content:"\f62a"}.fa-shower:before{content:"\f2cc"}.fa-desktop-alt:before,.fa-desktop:before{content:"\f390"}.fa-m:before{content:"\4d"}.fa-table-list:before,.fa-th-list:before{content:"\f00b"}.fa-comment-sms:before,.fa-sms:before{content:"\f7cd"}.fa-book:before{content:"\f02d"}.fa-user-plus:before{content:"\f234"}.fa-check:before{content:"\f00c"}.fa-battery-4:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-house-circle-check:before{content:"\e509"}.fa-angle-left:before{content:"\f104"}.fa-diagram-successor:before{content:"\e47a"}.fa-truck-arrow-right:before{content:"\e58b"}.fa-arrows-split-up-and-left:before{content:"\e4bc"}.fa-fist-raised:before,.fa-hand-fist:before{content:"\f6de"}.fa-cloud-moon:before{content:"\f6c3"}.fa-briefcase:before{content:"\f0b1"}.fa-person-falling:before{content:"\e546"}.fa-image-portrait:before,.fa-portrait:before{content:"\f3e0"}.fa-user-tag:before{content:"\f507"}.fa-rug:before{content:"\e569"}.fa-earth-europe:before,.fa-globe-europe:before{content:"\f7a2"}.fa-cart-flatbed-suitcase:before,.fa-luggage-cart:before{content:"\f59d"}.fa-rectangle-times:before,.fa-rectangle-xmark:before,.fa-times-rectangle:before,.fa-window-close:before{content:"\f410"}.fa-baht-sign:before{content:"\e0ac"}.fa-book-open:before{content:"\f518"}.fa-book-journal-whills:before,.fa-journal-whills:before{content:"\f66a"}.fa-handcuffs:before{content:"\e4f8"}.fa-exclamation-triangle:before,.fa-triangle-exclamation:before,.fa-warning:before{content:"\f071"}.fa-database:before{content:"\f1c0"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-bottle-droplet:before{content:"\e4c4"}.fa-mask-face:before{content:"\e1d7"}.fa-hill-rockslide:before{content:"\e508"}.fa-exchange-alt:before,.fa-right-left:before{content:"\f362"}.fa-paper-plane:before{content:"\f1d8"}.fa-road-circle-exclamation:before{content:"\e565"}.fa-dungeon:before{content:"\f6d9"}.fa-align-right:before{content:"\f038"}.fa-money-bill-1-wave:before,.fa-money-bill-wave-alt:before{content:"\f53b"}.fa-life-ring:before{content:"\f1cd"}.fa-hands:before,.fa-sign-language:before,.fa-signing:before{content:"\f2a7"}.fa-calendar-day:before{content:"\f783"}.fa-ladder-water:before,.fa-swimming-pool:before,.fa-water-ladder:before{content:"\f5c5"}.fa-arrows-up-down:before,.fa-arrows-v:before{content:"\f07d"}.fa-face-grimace:before,.fa-grimace:before{content:"\f57f"}.fa-wheelchair-alt:before,.fa-wheelchair-move:before{content:"\e2ce"}.fa-level-down-alt:before,.fa-turn-down:before{content:"\f3be"}.fa-person-walking-arrow-right:before{content:"\e552"}.fa-envelope-square:before,.fa-square-envelope:before{content:"\f199"}.fa-dice:before{content:"\f522"}.fa-bowling-ball:before{content:"\f436"}.fa-brain:before{content:"\f5dc"}.fa-band-aid:before,.fa-bandage:before{content:"\f462"}.fa-calendar-minus:before{content:"\f272"}.fa-circle-xmark:before,.fa-times-circle:before,.fa-xmark-circle:before{content:"\f057"}.fa-gifts:before{content:"\f79c"}.fa-hotel:before{content:"\f594"}.fa-earth-asia:before,.fa-globe-asia:before{content:"\f57e"}.fa-id-card-alt:before,.fa-id-card-clip:before{content:"\f47f"}.fa-magnifying-glass-plus:before,.fa-search-plus:before{content:"\f00e"}.fa-thumbs-up:before{content:"\f164"}.fa-user-clock:before{content:"\f4fd"}.fa-allergies:before,.fa-hand-dots:before{content:"\f461"}.fa-file-invoice:before{content:"\f570"}.fa-window-minimize:before{content:"\f2d1"}.fa-coffee:before,.fa-mug-saucer:before{content:"\f0f4"}.fa-brush:before{content:"\f55d"}.fa-mask:before{content:"\f6fa"}.fa-magnifying-glass-minus:before,.fa-search-minus:before{content:"\f010"}.fa-ruler-vertical:before{content:"\f548"}.fa-user-alt:before,.fa-user-large:before{content:"\f406"}.fa-train-tram:before{content:"\e5b4"}.fa-user-nurse:before{content:"\f82f"}.fa-syringe:before{content:"\f48e"}.fa-cloud-sun:before{content:"\f6c4"}.fa-stopwatch-20:before{content:"\e06f"}.fa-square-full:before{content:"\f45c"}.fa-magnet:before{content:"\f076"}.fa-jar:before{content:"\e516"}.fa-note-sticky:before,.fa-sticky-note:before{content:"\f249"}.fa-bug-slash:before{content:"\e490"}.fa-arrow-up-from-water-pump:before{content:"\e4b6"}.fa-bone:before{content:"\f5d7"}.fa-table-cells-row-unlock:before{content:"\e691"}.fa-user-injured:before{content:"\f728"}.fa-face-sad-tear:before,.fa-sad-tear:before{content:"\f5b4"}.fa-plane:before{content:"\f072"}.fa-tent-arrows-down:before{content:"\e581"}.fa-exclamation:before{content:"\21"}.fa-arrows-spin:before{content:"\e4bb"}.fa-print:before{content:"\f02f"}.fa-try:before,.fa-turkish-lira-sign:before,.fa-turkish-lira:before{content:"\e2bb"}.fa-dollar-sign:before,.fa-dollar:before,.fa-usd:before{content:"\24"}.fa-x:before{content:"\58"}.fa-magnifying-glass-dollar:before,.fa-search-dollar:before{content:"\f688"}.fa-users-cog:before,.fa-users-gear:before{content:"\f509"}.fa-person-military-pointing:before{content:"\e54a"}.fa-bank:before,.fa-building-columns:before,.fa-institution:before,.fa-museum:before,.fa-university:before{content:"\f19c"}.fa-umbrella:before{content:"\f0e9"}.fa-trowel:before{content:"\e589"}.fa-d:before{content:"\44"}.fa-stapler:before{content:"\e5af"}.fa-masks-theater:before,.fa-theater-masks:before{content:"\f630"}.fa-kip-sign:before{content:"\e1c4"}.fa-hand-point-left:before{content:"\f0a5"}.fa-handshake-alt:before,.fa-handshake-simple:before{content:"\f4c6"}.fa-fighter-jet:before,.fa-jet-fighter:before{content:"\f0fb"}.fa-share-alt-square:before,.fa-square-share-nodes:before{content:"\f1e1"}.fa-barcode:before{content:"\f02a"}.fa-plus-minus:before{content:"\e43c"}.fa-video-camera:before,.fa-video:before{content:"\f03d"}.fa-graduation-cap:before,.fa-mortar-board:before{content:"\f19d"}.fa-hand-holding-medical:before{content:"\e05c"}.fa-person-circle-check:before{content:"\e53e"}.fa-level-up-alt:before,.fa-turn-up:before{content:"\f3bf"} -.fa-sr-only,.fa-sr-only-focusable:not(:focus),.sr-only,.sr-only-focusable:not(:focus){position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}:host,:root{--fa-style-family-brands:"Font Awesome 6 Brands";--fa-font-brands:normal 400 1em/1 "Font Awesome 6 Brands"}@font-face{font-family:"Font Awesome 6 Brands";font-style:normal;font-weight:400;font-display:block;src:url(/-/static/fa-brands-400.woff2) format("woff2"),url(/-/static/fa-brands-400.ttf) format("truetype")}.fa-brands,.fab{font-weight:400}.fa-monero:before{content:"\f3d0"}.fa-hooli:before{content:"\f427"}.fa-yelp:before{content:"\f1e9"}.fa-cc-visa:before{content:"\f1f0"}.fa-lastfm:before{content:"\f202"}.fa-shopware:before{content:"\f5b5"}.fa-creative-commons-nc:before{content:"\f4e8"}.fa-aws:before{content:"\f375"}.fa-redhat:before{content:"\f7bc"}.fa-yoast:before{content:"\f2b1"}.fa-cloudflare:before{content:"\e07d"}.fa-ups:before{content:"\f7e0"}.fa-pixiv:before{content:"\e640"}.fa-wpexplorer:before{content:"\f2de"}.fa-dyalog:before{content:"\f399"}.fa-bity:before{content:"\f37a"}.fa-stackpath:before{content:"\f842"}.fa-buysellads:before{content:"\f20d"}.fa-first-order:before{content:"\f2b0"}.fa-modx:before{content:"\f285"}.fa-guilded:before{content:"\e07e"}.fa-vnv:before{content:"\f40b"}.fa-js-square:before,.fa-square-js:before{content:"\f3b9"}.fa-microsoft:before{content:"\f3ca"}.fa-qq:before{content:"\f1d6"}.fa-orcid:before{content:"\f8d2"}.fa-java:before{content:"\f4e4"}.fa-invision:before{content:"\f7b0"}.fa-creative-commons-pd-alt:before{content:"\f4ed"}.fa-centercode:before{content:"\f380"}.fa-glide-g:before{content:"\f2a6"}.fa-drupal:before{content:"\f1a9"}.fa-jxl:before{content:"\e67b"}.fa-dart-lang:before{content:"\e693"}.fa-hire-a-helper:before{content:"\f3b0"}.fa-creative-commons-by:before{content:"\f4e7"}.fa-unity:before{content:"\e049"}.fa-whmcs:before{content:"\f40d"}.fa-rocketchat:before{content:"\f3e8"}.fa-vk:before{content:"\f189"}.fa-untappd:before{content:"\f405"}.fa-mailchimp:before{content:"\f59e"}.fa-css3-alt:before{content:"\f38b"}.fa-reddit-square:before,.fa-square-reddit:before{content:"\f1a2"}.fa-vimeo-v:before{content:"\f27d"}.fa-contao:before{content:"\f26d"}.fa-square-font-awesome:before{content:"\e5ad"}.fa-deskpro:before{content:"\f38f"}.fa-brave:before{content:"\e63c"}.fa-sistrix:before{content:"\f3ee"}.fa-instagram-square:before,.fa-square-instagram:before{content:"\e055"}.fa-battle-net:before{content:"\f835"}.fa-the-red-yeti:before{content:"\f69d"}.fa-hacker-news-square:before,.fa-square-hacker-news:before{content:"\f3af"}.fa-edge:before{content:"\f282"}.fa-threads:before{content:"\e618"}.fa-napster:before{content:"\f3d2"}.fa-snapchat-square:before,.fa-square-snapchat:before{content:"\f2ad"}.fa-google-plus-g:before{content:"\f0d5"}.fa-artstation:before{content:"\f77a"}.fa-markdown:before{content:"\f60f"}.fa-sourcetree:before{content:"\f7d3"}.fa-google-plus:before{content:"\f2b3"}.fa-diaspora:before{content:"\f791"}.fa-foursquare:before{content:"\f180"}.fa-stack-overflow:before{content:"\f16c"}.fa-github-alt:before{content:"\f113"}.fa-phoenix-squadron:before{content:"\f511"}.fa-pagelines:before{content:"\f18c"}.fa-algolia:before{content:"\f36c"}.fa-red-river:before{content:"\f3e3"}.fa-creative-commons-sa:before{content:"\f4ef"}.fa-safari:before{content:"\f267"}.fa-google:before{content:"\f1a0"}.fa-font-awesome-alt:before,.fa-square-font-awesome-stroke:before{content:"\f35c"}.fa-atlassian:before{content:"\f77b"}.fa-linkedin-in:before{content:"\f0e1"}.fa-digital-ocean:before{content:"\f391"}.fa-nimblr:before{content:"\f5a8"}.fa-chromecast:before{content:"\f838"}.fa-evernote:before{content:"\f839"}.fa-hacker-news:before{content:"\f1d4"}.fa-creative-commons-sampling:before{content:"\f4f0"}.fa-adversal:before{content:"\f36a"}.fa-creative-commons:before{content:"\f25e"}.fa-watchman-monitoring:before{content:"\e087"}.fa-fonticons:before{content:"\f280"}.fa-weixin:before{content:"\f1d7"}.fa-shirtsinbulk:before{content:"\f214"}.fa-codepen:before{content:"\f1cb"}.fa-git-alt:before{content:"\f841"}.fa-lyft:before{content:"\f3c3"}.fa-rev:before{content:"\f5b2"}.fa-windows:before{content:"\f17a"}.fa-wizards-of-the-coast:before{content:"\f730"}.fa-square-viadeo:before,.fa-viadeo-square:before{content:"\f2aa"}.fa-meetup:before{content:"\f2e0"}.fa-centos:before{content:"\f789"}.fa-adn:before{content:"\f170"}.fa-cloudsmith:before{content:"\f384"}.fa-opensuse:before{content:"\e62b"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-dribbble-square:before,.fa-square-dribbble:before{content:"\f397"}.fa-codiepie:before{content:"\f284"}.fa-node:before{content:"\f419"}.fa-mix:before{content:"\f3cb"}.fa-steam:before{content:"\f1b6"}.fa-cc-apple-pay:before{content:"\f416"}.fa-scribd:before{content:"\f28a"}.fa-debian:before{content:"\e60b"}.fa-openid:before{content:"\f19b"}.fa-instalod:before{content:"\e081"}.fa-expeditedssl:before{content:"\f23e"}.fa-sellcast:before{content:"\f2da"}.fa-square-twitter:before,.fa-twitter-square:before{content:"\f081"}.fa-r-project:before{content:"\f4f7"}.fa-delicious:before{content:"\f1a5"}.fa-freebsd:before{content:"\f3a4"}.fa-vuejs:before{content:"\f41f"}.fa-accusoft:before{content:"\f369"}.fa-ioxhost:before{content:"\f208"}.fa-fonticons-fi:before{content:"\f3a2"}.fa-app-store:before{content:"\f36f"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-itunes-note:before{content:"\f3b5"}.fa-golang:before{content:"\e40f"}.fa-kickstarter:before,.fa-square-kickstarter:before{content:"\f3bb"}.fa-grav:before{content:"\f2d6"}.fa-weibo:before{content:"\f18a"}.fa-uncharted:before{content:"\e084"}.fa-firstdraft:before{content:"\f3a1"}.fa-square-youtube:before,.fa-youtube-square:before{content:"\f431"}.fa-wikipedia-w:before{content:"\f266"}.fa-rendact:before,.fa-wpressr:before{content:"\f3e4"}.fa-angellist:before{content:"\f209"}.fa-galactic-republic:before{content:"\f50c"}.fa-nfc-directional:before{content:"\e530"}.fa-skype:before{content:"\f17e"}.fa-joget:before{content:"\f3b7"}.fa-fedora:before{content:"\f798"}.fa-stripe-s:before{content:"\f42a"}.fa-meta:before{content:"\e49b"}.fa-laravel:before{content:"\f3bd"}.fa-hotjar:before{content:"\f3b1"}.fa-bluetooth-b:before{content:"\f294"}.fa-square-letterboxd:before{content:"\e62e"}.fa-sticker-mule:before{content:"\f3f7"}.fa-creative-commons-zero:before{content:"\f4f3"}.fa-hips:before{content:"\f452"}.fa-behance:before{content:"\f1b4"}.fa-reddit:before{content:"\f1a1"}.fa-discord:before{content:"\f392"}.fa-chrome:before{content:"\f268"}.fa-app-store-ios:before{content:"\f370"}.fa-cc-discover:before{content:"\f1f2"}.fa-wpbeginner:before{content:"\f297"}.fa-confluence:before{content:"\f78d"}.fa-shoelace:before{content:"\e60c"}.fa-mdb:before{content:"\f8ca"}.fa-dochub:before{content:"\f394"}.fa-accessible-icon:before{content:"\f368"}.fa-ebay:before{content:"\f4f4"}.fa-amazon:before{content:"\f270"}.fa-unsplash:before{content:"\e07c"}.fa-yarn:before{content:"\f7e3"}.fa-square-steam:before,.fa-steam-square:before{content:"\f1b7"}.fa-500px:before{content:"\f26e"}.fa-square-vimeo:before,.fa-vimeo-square:before{content:"\f194"}.fa-asymmetrik:before{content:"\f372"}.fa-font-awesome-flag:before,.fa-font-awesome-logo-full:before,.fa-font-awesome:before{content:"\f2b4"}.fa-gratipay:before{content:"\f184"}.fa-apple:before{content:"\f179"}.fa-hive:before{content:"\e07f"}.fa-gitkraken:before{content:"\f3a6"}.fa-keybase:before{content:"\f4f5"}.fa-apple-pay:before{content:"\f415"}.fa-padlet:before{content:"\e4a0"}.fa-amazon-pay:before{content:"\f42c"}.fa-github-square:before,.fa-square-github:before{content:"\f092"}.fa-stumbleupon:before{content:"\f1a4"}.fa-fedex:before{content:"\f797"}.fa-phoenix-framework:before{content:"\f3dc"}.fa-shopify:before{content:"\e057"}.fa-neos:before{content:"\f612"}.fa-square-threads:before{content:"\e619"}.fa-hackerrank:before{content:"\f5f7"}.fa-researchgate:before{content:"\f4f8"}.fa-swift:before{content:"\f8e1"}.fa-angular:before{content:"\f420"}.fa-speakap:before{content:"\f3f3"}.fa-angrycreative:before{content:"\f36e"}.fa-y-combinator:before{content:"\f23b"}.fa-empire:before{content:"\f1d1"}.fa-envira:before{content:"\f299"}.fa-google-scholar:before{content:"\e63b"}.fa-gitlab-square:before,.fa-square-gitlab:before{content:"\e5ae"}.fa-studiovinari:before{content:"\f3f8"}.fa-pied-piper:before{content:"\f2ae"}.fa-wordpress:before{content:"\f19a"}.fa-product-hunt:before{content:"\f288"}.fa-firefox:before{content:"\f269"}.fa-linode:before{content:"\f2b8"}.fa-goodreads:before{content:"\f3a8"}.fa-odnoklassniki-square:before,.fa-square-odnoklassniki:before{content:"\f264"}.fa-jsfiddle:before{content:"\f1cc"}.fa-sith:before{content:"\f512"}.fa-themeisle:before{content:"\f2b2"}.fa-page4:before{content:"\f3d7"}.fa-hashnode:before{content:"\e499"}.fa-react:before{content:"\f41b"}.fa-cc-paypal:before{content:"\f1f4"}.fa-squarespace:before{content:"\f5be"}.fa-cc-stripe:before{content:"\f1f5"}.fa-creative-commons-share:before{content:"\f4f2"}.fa-bitcoin:before{content:"\f379"}.fa-keycdn:before{content:"\f3ba"}.fa-opera:before{content:"\f26a"}.fa-itch-io:before{content:"\f83a"}.fa-umbraco:before{content:"\f8e8"}.fa-galactic-senate:before{content:"\f50d"}.fa-ubuntu:before{content:"\f7df"}.fa-draft2digital:before{content:"\f396"}.fa-stripe:before{content:"\f429"}.fa-houzz:before{content:"\f27c"}.fa-gg:before{content:"\f260"}.fa-dhl:before{content:"\f790"}.fa-pinterest-square:before,.fa-square-pinterest:before{content:"\f0d3"}.fa-xing:before{content:"\f168"}.fa-blackberry:before{content:"\f37b"}.fa-creative-commons-pd:before{content:"\f4ec"}.fa-playstation:before{content:"\f3df"}.fa-quinscape:before{content:"\f459"}.fa-less:before{content:"\f41d"}.fa-blogger-b:before{content:"\f37d"}.fa-opencart:before{content:"\f23d"}.fa-vine:before{content:"\f1ca"}.fa-signal-messenger:before{content:"\e663"}.fa-paypal:before{content:"\f1ed"}.fa-gitlab:before{content:"\f296"}.fa-typo3:before{content:"\f42b"}.fa-reddit-alien:before{content:"\f281"}.fa-yahoo:before{content:"\f19e"}.fa-dailymotion:before{content:"\e052"}.fa-affiliatetheme:before{content:"\f36b"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-bootstrap:before{content:"\f836"}.fa-odnoklassniki:before{content:"\f263"}.fa-nfc-symbol:before{content:"\e531"}.fa-mintbit:before{content:"\e62f"}.fa-ethereum:before{content:"\f42e"}.fa-speaker-deck:before{content:"\f83c"}.fa-creative-commons-nc-eu:before{content:"\f4e9"}.fa-patreon:before{content:"\f3d9"}.fa-avianex:before{content:"\f374"}.fa-ello:before{content:"\f5f1"}.fa-gofore:before{content:"\f3a7"}.fa-bimobject:before{content:"\f378"}.fa-brave-reverse:before{content:"\e63d"}.fa-facebook-f:before{content:"\f39e"}.fa-google-plus-square:before,.fa-square-google-plus:before{content:"\f0d4"}.fa-web-awesome:before{content:"\e682"}.fa-mandalorian:before{content:"\f50f"}.fa-first-order-alt:before{content:"\f50a"}.fa-osi:before{content:"\f41a"}.fa-google-wallet:before{content:"\f1ee"}.fa-d-and-d-beyond:before{content:"\f6ca"}.fa-periscope:before{content:"\f3da"}.fa-fulcrum:before{content:"\f50b"}.fa-cloudscale:before{content:"\f383"}.fa-forumbee:before{content:"\f211"}.fa-mizuni:before{content:"\f3cc"}.fa-schlix:before{content:"\f3ea"}.fa-square-xing:before,.fa-xing-square:before{content:"\f169"}.fa-bandcamp:before{content:"\f2d5"}.fa-wpforms:before{content:"\f298"}.fa-cloudversify:before{content:"\f385"}.fa-usps:before{content:"\f7e1"}.fa-megaport:before{content:"\f5a3"}.fa-magento:before{content:"\f3c4"}.fa-spotify:before{content:"\f1bc"}.fa-optin-monster:before{content:"\f23c"}.fa-fly:before{content:"\f417"}.fa-aviato:before{content:"\f421"}.fa-itunes:before{content:"\f3b4"}.fa-cuttlefish:before{content:"\f38c"}.fa-blogger:before{content:"\f37c"}.fa-flickr:before{content:"\f16e"}.fa-viber:before{content:"\f409"}.fa-soundcloud:before{content:"\f1be"}.fa-digg:before{content:"\f1a6"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-letterboxd:before{content:"\e62d"}.fa-symfony:before{content:"\f83d"}.fa-maxcdn:before{content:"\f136"}.fa-etsy:before{content:"\f2d7"}.fa-facebook-messenger:before{content:"\f39f"}.fa-audible:before{content:"\f373"}.fa-think-peaks:before{content:"\f731"}.fa-bilibili:before{content:"\e3d9"}.fa-erlang:before{content:"\f39d"}.fa-x-twitter:before{content:"\e61b"}.fa-cotton-bureau:before{content:"\f89e"}.fa-dashcube:before{content:"\f210"}.fa-42-group:before,.fa-innosoft:before{content:"\e080"}.fa-stack-exchange:before{content:"\f18d"}.fa-elementor:before{content:"\f430"}.fa-pied-piper-square:before,.fa-square-pied-piper:before{content:"\e01e"}.fa-creative-commons-nd:before{content:"\f4eb"}.fa-palfed:before{content:"\f3d8"}.fa-superpowers:before{content:"\f2dd"}.fa-resolving:before{content:"\f3e7"}.fa-xbox:before{content:"\f412"}.fa-square-web-awesome-stroke:before{content:"\e684"}.fa-searchengin:before{content:"\f3eb"}.fa-tiktok:before{content:"\e07b"}.fa-facebook-square:before,.fa-square-facebook:before{content:"\f082"}.fa-renren:before{content:"\f18b"}.fa-linux:before{content:"\f17c"}.fa-glide:before{content:"\f2a5"}.fa-linkedin:before{content:"\f08c"}.fa-hubspot:before{content:"\f3b2"}.fa-deploydog:before{content:"\f38e"}.fa-twitch:before{content:"\f1e8"}.fa-flutter:before{content:"\e694"}.fa-ravelry:before{content:"\f2d9"}.fa-mixer:before{content:"\e056"}.fa-lastfm-square:before,.fa-square-lastfm:before{content:"\f203"}.fa-vimeo:before{content:"\f40a"}.fa-mendeley:before{content:"\f7b3"}.fa-uniregistry:before{content:"\f404"}.fa-figma:before{content:"\f799"}.fa-creative-commons-remix:before{content:"\f4ee"}.fa-cc-amazon-pay:before{content:"\f42d"}.fa-dropbox:before{content:"\f16b"}.fa-instagram:before{content:"\f16d"}.fa-cmplid:before{content:"\e360"}.fa-upwork:before{content:"\e641"}.fa-facebook:before{content:"\f09a"}.fa-gripfire:before{content:"\f3ac"}.fa-jedi-order:before{content:"\f50e"}.fa-uikit:before{content:"\f403"}.fa-fort-awesome-alt:before{content:"\f3a3"}.fa-phabricator:before{content:"\f3db"}.fa-ussunnah:before{content:"\f407"}.fa-earlybirds:before{content:"\f39a"}.fa-trade-federation:before{content:"\f513"}.fa-autoprefixer:before{content:"\f41c"}.fa-whatsapp:before{content:"\f232"}.fa-square-upwork:before{content:"\e67c"}.fa-slideshare:before{content:"\f1e7"}.fa-google-play:before{content:"\f3ab"}.fa-viadeo:before{content:"\f2a9"}.fa-line:before{content:"\f3c0"}.fa-google-drive:before{content:"\f3aa"}.fa-servicestack:before{content:"\f3ec"}.fa-simplybuilt:before{content:"\f215"}.fa-bitbucket:before{content:"\f171"}.fa-imdb:before{content:"\f2d8"}.fa-deezer:before{content:"\e077"}.fa-raspberry-pi:before{content:"\f7bb"}.fa-jira:before{content:"\f7b1"}.fa-docker:before{content:"\f395"}.fa-screenpal:before{content:"\e570"}.fa-bluetooth:before{content:"\f293"}.fa-gitter:before{content:"\f426"}.fa-d-and-d:before{content:"\f38d"}.fa-microblog:before{content:"\e01a"}.fa-cc-diners-club:before{content:"\f24c"}.fa-gg-circle:before{content:"\f261"}.fa-pied-piper-hat:before{content:"\f4e5"}.fa-kickstarter-k:before{content:"\f3bc"}.fa-yandex:before{content:"\f413"}.fa-readme:before{content:"\f4d5"}.fa-html5:before{content:"\f13b"}.fa-sellsy:before{content:"\f213"}.fa-square-web-awesome:before{content:"\e683"}.fa-sass:before{content:"\f41e"}.fa-wirsindhandwerk:before,.fa-wsh:before{content:"\e2d0"}.fa-buromobelexperte:before{content:"\f37f"}.fa-salesforce:before{content:"\f83b"}.fa-octopus-deploy:before{content:"\e082"}.fa-medapps:before{content:"\f3c6"}.fa-ns8:before{content:"\f3d5"}.fa-pinterest-p:before{content:"\f231"}.fa-apper:before{content:"\f371"}.fa-fort-awesome:before{content:"\f286"}.fa-waze:before{content:"\f83f"}.fa-bluesky:before{content:"\e671"}.fa-cc-jcb:before{content:"\f24b"}.fa-snapchat-ghost:before,.fa-snapchat:before{content:"\f2ab"}.fa-fantasy-flight-games:before{content:"\f6dc"}.fa-rust:before{content:"\e07a"}.fa-wix:before{content:"\f5cf"}.fa-behance-square:before,.fa-square-behance:before{content:"\f1b5"}.fa-supple:before{content:"\f3f9"}.fa-webflow:before{content:"\e65c"}.fa-rebel:before{content:"\f1d0"}.fa-css3:before{content:"\f13c"}.fa-staylinked:before{content:"\f3f5"}.fa-kaggle:before{content:"\f5fa"}.fa-space-awesome:before{content:"\e5ac"}.fa-deviantart:before{content:"\f1bd"}.fa-cpanel:before{content:"\f388"}.fa-goodreads-g:before{content:"\f3a9"}.fa-git-square:before,.fa-square-git:before{content:"\f1d2"}.fa-square-tumblr:before,.fa-tumblr-square:before{content:"\f174"}.fa-trello:before{content:"\f181"}.fa-creative-commons-nc-jp:before{content:"\f4ea"}.fa-get-pocket:before{content:"\f265"}.fa-perbyte:before{content:"\e083"}.fa-grunt:before{content:"\f3ad"}.fa-weebly:before{content:"\f5cc"}.fa-connectdevelop:before{content:"\f20e"}.fa-leanpub:before{content:"\f212"}.fa-black-tie:before{content:"\f27e"}.fa-themeco:before{content:"\f5c6"}.fa-python:before{content:"\f3e2"}.fa-android:before{content:"\f17b"}.fa-bots:before{content:"\e340"}.fa-free-code-camp:before{content:"\f2c5"}.fa-hornbill:before{content:"\f592"}.fa-js:before{content:"\f3b8"}.fa-ideal:before{content:"\e013"}.fa-git:before{content:"\f1d3"}.fa-dev:before{content:"\f6cc"}.fa-sketch:before{content:"\f7c6"}.fa-yandex-international:before{content:"\f414"}.fa-cc-amex:before{content:"\f1f3"}.fa-uber:before{content:"\f402"}.fa-github:before{content:"\f09b"}.fa-php:before{content:"\f457"}.fa-alipay:before{content:"\f642"}.fa-youtube:before{content:"\f167"}.fa-skyatlas:before{content:"\f216"}.fa-firefox-browser:before{content:"\e007"}.fa-replyd:before{content:"\f3e6"}.fa-suse:before{content:"\f7d6"}.fa-jenkins:before{content:"\f3b6"}.fa-twitter:before{content:"\f099"}.fa-rockrms:before{content:"\f3e9"}.fa-pinterest:before{content:"\f0d2"}.fa-buffer:before{content:"\f837"}.fa-npm:before{content:"\f3d4"}.fa-yammer:before{content:"\f840"}.fa-btc:before{content:"\f15a"}.fa-dribbble:before{content:"\f17d"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-internet-explorer:before{content:"\f26b"}.fa-stubber:before{content:"\e5c7"}.fa-telegram-plane:before,.fa-telegram:before{content:"\f2c6"}.fa-old-republic:before{content:"\f510"}.fa-odysee:before{content:"\e5c6"}.fa-square-whatsapp:before,.fa-whatsapp-square:before{content:"\f40c"}.fa-node-js:before{content:"\f3d3"}.fa-edge-legacy:before{content:"\e078"}.fa-slack-hash:before,.fa-slack:before{content:"\f198"}.fa-medrt:before{content:"\f3c8"}.fa-usb:before{content:"\f287"}.fa-tumblr:before{content:"\f173"}.fa-vaadin:before{content:"\f408"}.fa-quora:before{content:"\f2c4"}.fa-square-x-twitter:before{content:"\e61a"}.fa-reacteurope:before{content:"\f75d"}.fa-medium-m:before,.fa-medium:before{content:"\f23a"}.fa-amilia:before{content:"\f36d"}.fa-mixcloud:before{content:"\f289"}.fa-flipboard:before{content:"\f44d"}.fa-viacoin:before{content:"\f237"}.fa-critical-role:before{content:"\f6c9"}.fa-sitrox:before{content:"\e44a"}.fa-discourse:before{content:"\f393"}.fa-joomla:before{content:"\f1aa"}.fa-mastodon:before{content:"\f4f6"}.fa-airbnb:before{content:"\f834"}.fa-wolf-pack-battalion:before{content:"\f514"}.fa-buy-n-large:before{content:"\f8a6"}.fa-gulp:before{content:"\f3ae"}.fa-creative-commons-sampling-plus:before{content:"\f4f1"}.fa-strava:before{content:"\f428"}.fa-ember:before{content:"\f423"}.fa-canadian-maple-leaf:before{content:"\f785"}.fa-teamspeak:before{content:"\f4f9"}.fa-pushed:before{content:"\f3e1"}.fa-wordpress-simple:before{content:"\f411"}.fa-nutritionix:before{content:"\f3d6"}.fa-wodu:before{content:"\e088"}.fa-google-pay:before{content:"\e079"}.fa-intercom:before{content:"\f7af"}.fa-zhihu:before{content:"\f63f"}.fa-korvue:before{content:"\f42f"}.fa-pix:before{content:"\e43a"}.fa-steam-symbol:before{content:"\f3f6"}:host,:root{--fa-font-regular:normal 400 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:400;font-display:block;src:url(/-/static/fa-regular-400.woff2) format("woff2"),url(/-/static/fa-regular-400.ttf) format("truetype")}.fa-regular,.far{font-weight:400}:host,:root{--fa-style-family-classic:"Font Awesome 6 Free";--fa-font-solid:normal 900 1em/1 "Font Awesome 6 Free"}@font-face{font-family:"Font Awesome 6 Free";font-style:normal;font-weight:900;font-display:block;src:url(/-/static/fa-solid-900.woff2) format("woff2"),url(/-/static/fa-solid-900.ttf) format("truetype")}.fa-solid,.fas{font-weight:900}@font-face{font-family:"Font Awesome 5 Brands";font-display:block;font-weight:400;src:url(/-/static/fa-brands-400.woff2) format("woff2"),url(/-/static/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:900;src:url(/-/static/fa-solid-900.woff2) format("woff2"),url(/-/static/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"Font Awesome 5 Free";font-display:block;font-weight:400;src:url(/-/static/fa-regular-400.woff2) format("woff2"),url(/-/static/fa-regular-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(/-/static/fa-solid-900.woff2) format("woff2"),url(/-/static/fa-solid-900.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(/-/static/fa-brands-400.woff2) format("woff2"),url(/-/static/fa-brands-400.ttf) format("truetype")}@font-face{font-family:"FontAwesome";font-display:block;src:url(/-/static/fa-regular-400.woff2) format("woff2"),url(/-/static/fa-regular-400.ttf) format("truetype");unicode-range:u+f003,u+f006,u+f014,u+f016-f017,u+f01a-f01b,u+f01d,u+f022,u+f03e,u+f044,u+f046,u+f05c-f05d,u+f06e,u+f070,u+f087-f088,u+f08a,u+f094,u+f096-f097,u+f09d,u+f0a0,u+f0a2,u+f0a4-f0a7,u+f0c5,u+f0c7,u+f0e5-f0e6,u+f0eb,u+f0f6-f0f8,u+f10c,u+f114-f115,u+f118-f11a,u+f11c-f11d,u+f133,u+f147,u+f14e,u+f150-f152,u+f185-f186,u+f18e,u+f190-f192,u+f196,u+f1c1-f1c9,u+f1d9,u+f1db,u+f1e3,u+f1ea,u+f1f7,u+f1f9,u+f20a,u+f247-f248,u+f24a,u+f24d,u+f255-f25b,u+f25d,u+f271-f274,u+f278,u+f27b,u+f28c,u+f28e,u+f29c,u+f2b5,u+f2b7,u+f2ba,u+f2bc,u+f2be,u+f2c0-f2c1,u+f2c3,u+f2d0,u+f2d2,u+f2d4,u+f2dc}@font-face{font-family:"FontAwesome";font-display:block;src:url(/-/static/fa-v4compatibility.woff2) format("woff2"),url(/-/static/fa-v4compatibility.ttf) format("truetype");unicode-range:u+f041,u+f047,u+f065-f066,u+f07d-f07e,u+f080,u+f08b,u+f08e,u+f090,u+f09a,u+f0ac,u+f0ae,u+f0b2,u+f0d0,u+f0d6,u+f0e4,u+f0ec,u+f10a-f10b,u+f123,u+f13e,u+f148-f149,u+f14c,u+f156,u+f15e,u+f160-f161,u+f163,u+f175-f178,u+f195,u+f1f8,u+f219,u+f27a} - -.svg-clipboard { - --svg-clipboard: url(/-/static/clipboard.svg); - -webkit-mask: var(--svg-clipboard) no-repeat center; - mask: var(--svg-clipboard) no-repeat center; - -webkit-mask-size: contain; - mask-size: contain; - display: inline-block; - background-color: currentColor; - height: 1em; - /* pull icon about one stroke width into the descenders */ - margin-bottom: -0.1em; - width: 1.25em; - text-align: center; -} diff --git a/vendor/chartjs/chart.min.css b/vendor/chartjs/chart.min.css deleted file mode 100644 index 9dc5ac2e5..000000000 --- a/vendor/chartjs/chart.min.css +++ /dev/null @@ -1 +0,0 @@ -@keyframes chartjs-render-animation{from{opacity:.99}to{opacity:1}}.chartjs-render-monitor{animation:chartjs-render-animation 1ms}.chartjs-size-monitor,.chartjs-size-monitor-expand,.chartjs-size-monitor-shrink{position:absolute;direction:ltr;left:0;top:0;right:0;bottom:0;overflow:hidden;pointer-events:none;visibility:hidden;z-index:-1}.chartjs-size-monitor-expand>div{position:absolute;width:1000000px;height:1000000px;left:0;top:0}.chartjs-size-monitor-shrink>div{position:absolute;width:200%;height:200%;left:0;top:0} \ No newline at end of file diff --git a/vendor/pure-css/css/grids-responsive-min.css b/vendor/pure-css/css/grids-responsive-min.css deleted file mode 100644 index fc7f6b593..000000000 --- a/vendor/pure-css/css/grids-responsive-min.css +++ /dev/null @@ -1,7 +0,0 @@ -/*! -Pure v2.1.0 -Copyright 2013 Yahoo! -Licensed under the BSD License. -https://github.com/pure-css/pure/blob/master/LICENSE -*/ -@media screen and (min-width:35.5em){.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-1-12,.pure-u-sm-1-2,.pure-u-sm-1-24,.pure-u-sm-1-3,.pure-u-sm-1-4,.pure-u-sm-1-5,.pure-u-sm-1-6,.pure-u-sm-1-8,.pure-u-sm-10-24,.pure-u-sm-11-12,.pure-u-sm-11-24,.pure-u-sm-12-24,.pure-u-sm-13-24,.pure-u-sm-14-24,.pure-u-sm-15-24,.pure-u-sm-16-24,.pure-u-sm-17-24,.pure-u-sm-18-24,.pure-u-sm-19-24,.pure-u-sm-2-24,.pure-u-sm-2-3,.pure-u-sm-2-5,.pure-u-sm-20-24,.pure-u-sm-21-24,.pure-u-sm-22-24,.pure-u-sm-23-24,.pure-u-sm-24-24,.pure-u-sm-3-24,.pure-u-sm-3-4,.pure-u-sm-3-5,.pure-u-sm-3-8,.pure-u-sm-4-24,.pure-u-sm-4-5,.pure-u-sm-5-12,.pure-u-sm-5-24,.pure-u-sm-5-5,.pure-u-sm-5-6,.pure-u-sm-5-8,.pure-u-sm-6-24,.pure-u-sm-7-12,.pure-u-sm-7-24,.pure-u-sm-7-8,.pure-u-sm-8-24,.pure-u-sm-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-sm-1-24{width:4.1667%}.pure-u-sm-1-12,.pure-u-sm-2-24{width:8.3333%}.pure-u-sm-1-8,.pure-u-sm-3-24{width:12.5%}.pure-u-sm-1-6,.pure-u-sm-4-24{width:16.6667%}.pure-u-sm-1-5{width:20%}.pure-u-sm-5-24{width:20.8333%}.pure-u-sm-1-4,.pure-u-sm-6-24{width:25%}.pure-u-sm-7-24{width:29.1667%}.pure-u-sm-1-3,.pure-u-sm-8-24{width:33.3333%}.pure-u-sm-3-8,.pure-u-sm-9-24{width:37.5%}.pure-u-sm-2-5{width:40%}.pure-u-sm-10-24,.pure-u-sm-5-12{width:41.6667%}.pure-u-sm-11-24{width:45.8333%}.pure-u-sm-1-2,.pure-u-sm-12-24{width:50%}.pure-u-sm-13-24{width:54.1667%}.pure-u-sm-14-24,.pure-u-sm-7-12{width:58.3333%}.pure-u-sm-3-5{width:60%}.pure-u-sm-15-24,.pure-u-sm-5-8{width:62.5%}.pure-u-sm-16-24,.pure-u-sm-2-3{width:66.6667%}.pure-u-sm-17-24{width:70.8333%}.pure-u-sm-18-24,.pure-u-sm-3-4{width:75%}.pure-u-sm-19-24{width:79.1667%}.pure-u-sm-4-5{width:80%}.pure-u-sm-20-24,.pure-u-sm-5-6{width:83.3333%}.pure-u-sm-21-24,.pure-u-sm-7-8{width:87.5%}.pure-u-sm-11-12,.pure-u-sm-22-24{width:91.6667%}.pure-u-sm-23-24{width:95.8333%}.pure-u-sm-1,.pure-u-sm-1-1,.pure-u-sm-24-24,.pure-u-sm-5-5{width:100%}}@media screen and (min-width:48em){.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-1-12,.pure-u-md-1-2,.pure-u-md-1-24,.pure-u-md-1-3,.pure-u-md-1-4,.pure-u-md-1-5,.pure-u-md-1-6,.pure-u-md-1-8,.pure-u-md-10-24,.pure-u-md-11-12,.pure-u-md-11-24,.pure-u-md-12-24,.pure-u-md-13-24,.pure-u-md-14-24,.pure-u-md-15-24,.pure-u-md-16-24,.pure-u-md-17-24,.pure-u-md-18-24,.pure-u-md-19-24,.pure-u-md-2-24,.pure-u-md-2-3,.pure-u-md-2-5,.pure-u-md-20-24,.pure-u-md-21-24,.pure-u-md-22-24,.pure-u-md-23-24,.pure-u-md-24-24,.pure-u-md-3-24,.pure-u-md-3-4,.pure-u-md-3-5,.pure-u-md-3-8,.pure-u-md-4-24,.pure-u-md-4-5,.pure-u-md-5-12,.pure-u-md-5-24,.pure-u-md-5-5,.pure-u-md-5-6,.pure-u-md-5-8,.pure-u-md-6-24,.pure-u-md-7-12,.pure-u-md-7-24,.pure-u-md-7-8,.pure-u-md-8-24,.pure-u-md-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-md-1-24{width:4.1667%}.pure-u-md-1-12,.pure-u-md-2-24{width:8.3333%}.pure-u-md-1-8,.pure-u-md-3-24{width:12.5%}.pure-u-md-1-6,.pure-u-md-4-24{width:16.6667%}.pure-u-md-1-5{width:20%}.pure-u-md-5-24{width:20.8333%}.pure-u-md-1-4,.pure-u-md-6-24{width:25%}.pure-u-md-7-24{width:29.1667%}.pure-u-md-1-3,.pure-u-md-8-24{width:33.3333%}.pure-u-md-3-8,.pure-u-md-9-24{width:37.5%}.pure-u-md-2-5{width:40%}.pure-u-md-10-24,.pure-u-md-5-12{width:41.6667%}.pure-u-md-11-24{width:45.8333%}.pure-u-md-1-2,.pure-u-md-12-24{width:50%}.pure-u-md-13-24{width:54.1667%}.pure-u-md-14-24,.pure-u-md-7-12{width:58.3333%}.pure-u-md-3-5{width:60%}.pure-u-md-15-24,.pure-u-md-5-8{width:62.5%}.pure-u-md-16-24,.pure-u-md-2-3{width:66.6667%}.pure-u-md-17-24{width:70.8333%}.pure-u-md-18-24,.pure-u-md-3-4{width:75%}.pure-u-md-19-24{width:79.1667%}.pure-u-md-4-5{width:80%}.pure-u-md-20-24,.pure-u-md-5-6{width:83.3333%}.pure-u-md-21-24,.pure-u-md-7-8{width:87.5%}.pure-u-md-11-12,.pure-u-md-22-24{width:91.6667%}.pure-u-md-23-24{width:95.8333%}.pure-u-md-1,.pure-u-md-1-1,.pure-u-md-24-24,.pure-u-md-5-5{width:100%}}@media screen and (min-width:64em){.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-1-12,.pure-u-lg-1-2,.pure-u-lg-1-24,.pure-u-lg-1-3,.pure-u-lg-1-4,.pure-u-lg-1-5,.pure-u-lg-1-6,.pure-u-lg-1-8,.pure-u-lg-10-24,.pure-u-lg-11-12,.pure-u-lg-11-24,.pure-u-lg-12-24,.pure-u-lg-13-24,.pure-u-lg-14-24,.pure-u-lg-15-24,.pure-u-lg-16-24,.pure-u-lg-17-24,.pure-u-lg-18-24,.pure-u-lg-19-24,.pure-u-lg-2-24,.pure-u-lg-2-3,.pure-u-lg-2-5,.pure-u-lg-20-24,.pure-u-lg-21-24,.pure-u-lg-22-24,.pure-u-lg-23-24,.pure-u-lg-24-24,.pure-u-lg-3-24,.pure-u-lg-3-4,.pure-u-lg-3-5,.pure-u-lg-3-8,.pure-u-lg-4-24,.pure-u-lg-4-5,.pure-u-lg-5-12,.pure-u-lg-5-24,.pure-u-lg-5-5,.pure-u-lg-5-6,.pure-u-lg-5-8,.pure-u-lg-6-24,.pure-u-lg-7-12,.pure-u-lg-7-24,.pure-u-lg-7-8,.pure-u-lg-8-24,.pure-u-lg-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-lg-1-24{width:4.1667%}.pure-u-lg-1-12,.pure-u-lg-2-24{width:8.3333%}.pure-u-lg-1-8,.pure-u-lg-3-24{width:12.5%}.pure-u-lg-1-6,.pure-u-lg-4-24{width:16.6667%}.pure-u-lg-1-5{width:20%}.pure-u-lg-5-24{width:20.8333%}.pure-u-lg-1-4,.pure-u-lg-6-24{width:25%}.pure-u-lg-7-24{width:29.1667%}.pure-u-lg-1-3,.pure-u-lg-8-24{width:33.3333%}.pure-u-lg-3-8,.pure-u-lg-9-24{width:37.5%}.pure-u-lg-2-5{width:40%}.pure-u-lg-10-24,.pure-u-lg-5-12{width:41.6667%}.pure-u-lg-11-24{width:45.8333%}.pure-u-lg-1-2,.pure-u-lg-12-24{width:50%}.pure-u-lg-13-24{width:54.1667%}.pure-u-lg-14-24,.pure-u-lg-7-12{width:58.3333%}.pure-u-lg-3-5{width:60%}.pure-u-lg-15-24,.pure-u-lg-5-8{width:62.5%}.pure-u-lg-16-24,.pure-u-lg-2-3{width:66.6667%}.pure-u-lg-17-24{width:70.8333%}.pure-u-lg-18-24,.pure-u-lg-3-4{width:75%}.pure-u-lg-19-24{width:79.1667%}.pure-u-lg-4-5{width:80%}.pure-u-lg-20-24,.pure-u-lg-5-6{width:83.3333%}.pure-u-lg-21-24,.pure-u-lg-7-8{width:87.5%}.pure-u-lg-11-12,.pure-u-lg-22-24{width:91.6667%}.pure-u-lg-23-24{width:95.8333%}.pure-u-lg-1,.pure-u-lg-1-1,.pure-u-lg-24-24,.pure-u-lg-5-5{width:100%}}@media screen and (min-width:80em){.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-1-12,.pure-u-xl-1-2,.pure-u-xl-1-24,.pure-u-xl-1-3,.pure-u-xl-1-4,.pure-u-xl-1-5,.pure-u-xl-1-6,.pure-u-xl-1-8,.pure-u-xl-10-24,.pure-u-xl-11-12,.pure-u-xl-11-24,.pure-u-xl-12-24,.pure-u-xl-13-24,.pure-u-xl-14-24,.pure-u-xl-15-24,.pure-u-xl-16-24,.pure-u-xl-17-24,.pure-u-xl-18-24,.pure-u-xl-19-24,.pure-u-xl-2-24,.pure-u-xl-2-3,.pure-u-xl-2-5,.pure-u-xl-20-24,.pure-u-xl-21-24,.pure-u-xl-22-24,.pure-u-xl-23-24,.pure-u-xl-24-24,.pure-u-xl-3-24,.pure-u-xl-3-4,.pure-u-xl-3-5,.pure-u-xl-3-8,.pure-u-xl-4-24,.pure-u-xl-4-5,.pure-u-xl-5-12,.pure-u-xl-5-24,.pure-u-xl-5-5,.pure-u-xl-5-6,.pure-u-xl-5-8,.pure-u-xl-6-24,.pure-u-xl-7-12,.pure-u-xl-7-24,.pure-u-xl-7-8,.pure-u-xl-8-24,.pure-u-xl-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xl-1-24{width:4.1667%}.pure-u-xl-1-12,.pure-u-xl-2-24{width:8.3333%}.pure-u-xl-1-8,.pure-u-xl-3-24{width:12.5%}.pure-u-xl-1-6,.pure-u-xl-4-24{width:16.6667%}.pure-u-xl-1-5{width:20%}.pure-u-xl-5-24{width:20.8333%}.pure-u-xl-1-4,.pure-u-xl-6-24{width:25%}.pure-u-xl-7-24{width:29.1667%}.pure-u-xl-1-3,.pure-u-xl-8-24{width:33.3333%}.pure-u-xl-3-8,.pure-u-xl-9-24{width:37.5%}.pure-u-xl-2-5{width:40%}.pure-u-xl-10-24,.pure-u-xl-5-12{width:41.6667%}.pure-u-xl-11-24{width:45.8333%}.pure-u-xl-1-2,.pure-u-xl-12-24{width:50%}.pure-u-xl-13-24{width:54.1667%}.pure-u-xl-14-24,.pure-u-xl-7-12{width:58.3333%}.pure-u-xl-3-5{width:60%}.pure-u-xl-15-24,.pure-u-xl-5-8{width:62.5%}.pure-u-xl-16-24,.pure-u-xl-2-3{width:66.6667%}.pure-u-xl-17-24{width:70.8333%}.pure-u-xl-18-24,.pure-u-xl-3-4{width:75%}.pure-u-xl-19-24{width:79.1667%}.pure-u-xl-4-5{width:80%}.pure-u-xl-20-24,.pure-u-xl-5-6{width:83.3333%}.pure-u-xl-21-24,.pure-u-xl-7-8{width:87.5%}.pure-u-xl-11-12,.pure-u-xl-22-24{width:91.6667%}.pure-u-xl-23-24{width:95.8333%}.pure-u-xl-1,.pure-u-xl-1-1,.pure-u-xl-24-24,.pure-u-xl-5-5{width:100%}}@media screen and (min-width:120em){.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-1-12,.pure-u-xxl-1-2,.pure-u-xxl-1-24,.pure-u-xxl-1-3,.pure-u-xxl-1-4,.pure-u-xxl-1-5,.pure-u-xxl-1-6,.pure-u-xxl-1-8,.pure-u-xxl-10-24,.pure-u-xxl-11-12,.pure-u-xxl-11-24,.pure-u-xxl-12-24,.pure-u-xxl-13-24,.pure-u-xxl-14-24,.pure-u-xxl-15-24,.pure-u-xxl-16-24,.pure-u-xxl-17-24,.pure-u-xxl-18-24,.pure-u-xxl-19-24,.pure-u-xxl-2-24,.pure-u-xxl-2-3,.pure-u-xxl-2-5,.pure-u-xxl-20-24,.pure-u-xxl-21-24,.pure-u-xxl-22-24,.pure-u-xxl-23-24,.pure-u-xxl-24-24,.pure-u-xxl-3-24,.pure-u-xxl-3-4,.pure-u-xxl-3-5,.pure-u-xxl-3-8,.pure-u-xxl-4-24,.pure-u-xxl-4-5,.pure-u-xxl-5-12,.pure-u-xxl-5-24,.pure-u-xxl-5-5,.pure-u-xxl-5-6,.pure-u-xxl-5-8,.pure-u-xxl-6-24,.pure-u-xxl-7-12,.pure-u-xxl-7-24,.pure-u-xxl-7-8,.pure-u-xxl-8-24,.pure-u-xxl-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-xxl-1-24{width:4.1667%}.pure-u-xxl-1-12,.pure-u-xxl-2-24{width:8.3333%}.pure-u-xxl-1-8,.pure-u-xxl-3-24{width:12.5%}.pure-u-xxl-1-6,.pure-u-xxl-4-24{width:16.6667%}.pure-u-xxl-1-5{width:20%}.pure-u-xxl-5-24{width:20.8333%}.pure-u-xxl-1-4,.pure-u-xxl-6-24{width:25%}.pure-u-xxl-7-24{width:29.1667%}.pure-u-xxl-1-3,.pure-u-xxl-8-24{width:33.3333%}.pure-u-xxl-3-8,.pure-u-xxl-9-24{width:37.5%}.pure-u-xxl-2-5{width:40%}.pure-u-xxl-10-24,.pure-u-xxl-5-12{width:41.6667%}.pure-u-xxl-11-24{width:45.8333%}.pure-u-xxl-1-2,.pure-u-xxl-12-24{width:50%}.pure-u-xxl-13-24{width:54.1667%}.pure-u-xxl-14-24,.pure-u-xxl-7-12{width:58.3333%}.pure-u-xxl-3-5{width:60%}.pure-u-xxl-15-24,.pure-u-xxl-5-8{width:62.5%}.pure-u-xxl-16-24,.pure-u-xxl-2-3{width:66.6667%}.pure-u-xxl-17-24{width:70.8333%}.pure-u-xxl-18-24,.pure-u-xxl-3-4{width:75%}.pure-u-xxl-19-24{width:79.1667%}.pure-u-xxl-4-5{width:80%}.pure-u-xxl-20-24,.pure-u-xxl-5-6{width:83.3333%}.pure-u-xxl-21-24,.pure-u-xxl-7-8{width:87.5%}.pure-u-xxl-11-12,.pure-u-xxl-22-24{width:91.6667%}.pure-u-xxl-23-24{width:95.8333%}.pure-u-xxl-1,.pure-u-xxl-1-1,.pure-u-xxl-24-24,.pure-u-xxl-5-5{width:100%}} \ No newline at end of file diff --git a/vendor/pure-css/css/pure-min.css b/vendor/pure-css/css/pure-min.css deleted file mode 100644 index e0cc4089f..000000000 --- a/vendor/pure-css/css/pure-min.css +++ /dev/null @@ -1,11 +0,0 @@ -/*! -Pure v2.1.0 -Copyright 2013 Yahoo! -Licensed under the BSD License. -https://github.com/pure-css/pure/blob/master/LICENSE -*/ -/*! -normalize.css v | MIT License | git.io/normalize -Copyright (c) Nicolas Gallagher and Jonathan Neal -*/ -/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}html{font-family:sans-serif}.hidden,[hidden]{display:none!important}.pure-img{max-width:100%;height:auto;display:block}.pure-g{letter-spacing:-.31em;text-rendering:optimizespeed;font-family:FreeSans,Arimo,"Droid Sans",Helvetica,Arial,sans-serif;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-line-pack:start;align-content:flex-start}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){table .pure-g{display:block}}.opera-only :-o-prefocus,.pure-g{word-spacing:-0.43em}.pure-u{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-g [class*=pure-u]{font-family:sans-serif}.pure-u-1,.pure-u-1-1,.pure-u-1-12,.pure-u-1-2,.pure-u-1-24,.pure-u-1-3,.pure-u-1-4,.pure-u-1-5,.pure-u-1-6,.pure-u-1-8,.pure-u-10-24,.pure-u-11-12,.pure-u-11-24,.pure-u-12-24,.pure-u-13-24,.pure-u-14-24,.pure-u-15-24,.pure-u-16-24,.pure-u-17-24,.pure-u-18-24,.pure-u-19-24,.pure-u-2-24,.pure-u-2-3,.pure-u-2-5,.pure-u-20-24,.pure-u-21-24,.pure-u-22-24,.pure-u-23-24,.pure-u-24-24,.pure-u-3-24,.pure-u-3-4,.pure-u-3-5,.pure-u-3-8,.pure-u-4-24,.pure-u-4-5,.pure-u-5-12,.pure-u-5-24,.pure-u-5-5,.pure-u-5-6,.pure-u-5-8,.pure-u-6-24,.pure-u-7-12,.pure-u-7-24,.pure-u-7-8,.pure-u-8-24,.pure-u-9-24{display:inline-block;letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-u-1-24{width:4.1667%}.pure-u-1-12,.pure-u-2-24{width:8.3333%}.pure-u-1-8,.pure-u-3-24{width:12.5%}.pure-u-1-6,.pure-u-4-24{width:16.6667%}.pure-u-1-5{width:20%}.pure-u-5-24{width:20.8333%}.pure-u-1-4,.pure-u-6-24{width:25%}.pure-u-7-24{width:29.1667%}.pure-u-1-3,.pure-u-8-24{width:33.3333%}.pure-u-3-8,.pure-u-9-24{width:37.5%}.pure-u-2-5{width:40%}.pure-u-10-24,.pure-u-5-12{width:41.6667%}.pure-u-11-24{width:45.8333%}.pure-u-1-2,.pure-u-12-24{width:50%}.pure-u-13-24{width:54.1667%}.pure-u-14-24,.pure-u-7-12{width:58.3333%}.pure-u-3-5{width:60%}.pure-u-15-24,.pure-u-5-8{width:62.5%}.pure-u-16-24,.pure-u-2-3{width:66.6667%}.pure-u-17-24{width:70.8333%}.pure-u-18-24,.pure-u-3-4{width:75%}.pure-u-19-24{width:79.1667%}.pure-u-4-5{width:80%}.pure-u-20-24,.pure-u-5-6{width:83.3333%}.pure-u-21-24,.pure-u-7-8{width:87.5%}.pure-u-11-12,.pure-u-22-24{width:91.6667%}.pure-u-23-24{width:95.8333%}.pure-u-1,.pure-u-1-1,.pure-u-24-24,.pure-u-5-5{width:100%}.pure-button{display:inline-block;line-height:normal;white-space:nowrap;vertical-align:middle;text-align:center;cursor:pointer;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-button::-moz-focus-inner{padding:0;border:0}.pure-button-group{letter-spacing:-.31em;text-rendering:optimizespeed}.opera-only :-o-prefocus,.pure-button-group{word-spacing:-0.43em}.pure-button-group .pure-button{letter-spacing:normal;word-spacing:normal;vertical-align:top;text-rendering:auto}.pure-button{font-family:inherit;font-size:100%;padding:.5em 1em;color:rgba(0,0,0,.8);border:none transparent;background-color:#e6e6e6;text-decoration:none;border-radius:2px}.pure-button-hover,.pure-button:focus,.pure-button:hover{background-image:-webkit-gradient(linear,left top,left bottom,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));background-image:linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1))}.pure-button:focus{outline:0}.pure-button-active,.pure-button:active{-webkit-box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;box-shadow:0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;border-color:#000}.pure-button-disabled,.pure-button-disabled:active,.pure-button-disabled:focus,.pure-button-disabled:hover,.pure-button[disabled]{border:none;background-image:none;opacity:.4;cursor:not-allowed;-webkit-box-shadow:none;box-shadow:none;pointer-events:none}.pure-button-hidden{display:none}.pure-button-primary,.pure-button-selected,a.pure-button-primary,a.pure-button-selected{background-color:#0078e7;color:#fff}.pure-button-group .pure-button{margin:0;border-radius:0;border-right:1px solid rgba(0,0,0,.2)}.pure-button-group .pure-button:first-child{border-top-left-radius:2px;border-bottom-left-radius:2px}.pure-button-group .pure-button:last-child{border-top-right-radius:2px;border-bottom-right-radius:2px;border-right:none}.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form select,.pure-form textarea{padding:.5em .6em;display:inline-block;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 3px #ddd;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;vertical-align:middle;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form input:not([type]){padding:.5em .6em;display:inline-block;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 3px #ddd;box-shadow:inset 0 1px 3px #ddd;border-radius:4px;-webkit-box-sizing:border-box;box-sizing:border-box}.pure-form input[type=color]{padding:.2em .5em}.pure-form input[type=color]:focus,.pure-form input[type=date]:focus,.pure-form input[type=datetime-local]:focus,.pure-form input[type=datetime]:focus,.pure-form input[type=email]:focus,.pure-form input[type=month]:focus,.pure-form input[type=number]:focus,.pure-form input[type=password]:focus,.pure-form input[type=search]:focus,.pure-form input[type=tel]:focus,.pure-form input[type=text]:focus,.pure-form input[type=time]:focus,.pure-form input[type=url]:focus,.pure-form input[type=week]:focus,.pure-form select:focus,.pure-form textarea:focus{outline:0;border-color:#129fea}.pure-form input:not([type]):focus{outline:0;border-color:#129fea}.pure-form input[type=checkbox]:focus,.pure-form input[type=file]:focus,.pure-form input[type=radio]:focus{outline:thin solid #129FEA;outline:1px auto #129FEA}.pure-form .pure-checkbox,.pure-form .pure-radio{margin:.5em 0;display:block}.pure-form input[type=color][disabled],.pure-form input[type=date][disabled],.pure-form input[type=datetime-local][disabled],.pure-form input[type=datetime][disabled],.pure-form input[type=email][disabled],.pure-form input[type=month][disabled],.pure-form input[type=number][disabled],.pure-form input[type=password][disabled],.pure-form input[type=search][disabled],.pure-form input[type=tel][disabled],.pure-form input[type=text][disabled],.pure-form input[type=time][disabled],.pure-form input[type=url][disabled],.pure-form input[type=week][disabled],.pure-form select[disabled],.pure-form textarea[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input:not([type])[disabled]{cursor:not-allowed;background-color:#eaeded;color:#cad2d3}.pure-form input[readonly],.pure-form select[readonly],.pure-form textarea[readonly]{background-color:#eee;color:#777;border-color:#ccc}.pure-form input:focus:invalid,.pure-form select:focus:invalid,.pure-form textarea:focus:invalid{color:#b94a48;border-color:#e9322d}.pure-form input[type=checkbox]:focus:invalid:focus,.pure-form input[type=file]:focus:invalid:focus,.pure-form input[type=radio]:focus:invalid:focus{outline-color:#e9322d}.pure-form select{height:2.25em;border:1px solid #ccc;background-color:#fff}.pure-form select[multiple]{height:auto}.pure-form label{margin:.5em 0 .2em}.pure-form fieldset{margin:0;padding:.35em 0 .75em;border:0}.pure-form legend{display:block;width:100%;padding:.3em 0;margin-bottom:.3em;color:#333;border-bottom:1px solid #e5e5e5}.pure-form-stacked input[type=color],.pure-form-stacked input[type=date],.pure-form-stacked input[type=datetime-local],.pure-form-stacked input[type=datetime],.pure-form-stacked input[type=email],.pure-form-stacked input[type=file],.pure-form-stacked input[type=month],.pure-form-stacked input[type=number],.pure-form-stacked input[type=password],.pure-form-stacked input[type=search],.pure-form-stacked input[type=tel],.pure-form-stacked input[type=text],.pure-form-stacked input[type=time],.pure-form-stacked input[type=url],.pure-form-stacked input[type=week],.pure-form-stacked label,.pure-form-stacked select,.pure-form-stacked textarea{display:block;margin:.25em 0}.pure-form-stacked input:not([type]){display:block;margin:.25em 0}.pure-form-aligned input,.pure-form-aligned select,.pure-form-aligned textarea,.pure-form-message-inline{display:inline-block;vertical-align:middle}.pure-form-aligned textarea{vertical-align:top}.pure-form-aligned .pure-control-group{margin-bottom:.5em}.pure-form-aligned .pure-control-group label{text-align:right;display:inline-block;vertical-align:middle;width:10em;margin:0 1em 0 0}.pure-form-aligned .pure-controls{margin:1.5em 0 0 11em}.pure-form .pure-input-rounded,.pure-form input.pure-input-rounded{border-radius:2em;padding:.5em 1em}.pure-form .pure-group fieldset{margin-bottom:10px}.pure-form .pure-group input,.pure-form .pure-group textarea{display:block;padding:10px;margin:0 0 -1px;border-radius:0;position:relative;top:-1px}.pure-form .pure-group input:focus,.pure-form .pure-group textarea:focus{z-index:3}.pure-form .pure-group input:first-child,.pure-form .pure-group textarea:first-child{top:1px;border-radius:4px 4px 0 0;margin:0}.pure-form .pure-group input:first-child:last-child,.pure-form .pure-group textarea:first-child:last-child{top:1px;border-radius:4px;margin:0}.pure-form .pure-group input:last-child,.pure-form .pure-group textarea:last-child{top:-2px;border-radius:0 0 4px 4px;margin:0}.pure-form .pure-group button{margin:.35em 0}.pure-form .pure-input-1{width:100%}.pure-form .pure-input-3-4{width:75%}.pure-form .pure-input-2-3{width:66%}.pure-form .pure-input-1-2{width:50%}.pure-form .pure-input-1-3{width:33%}.pure-form .pure-input-1-4{width:25%}.pure-form-message-inline{display:inline-block;padding-left:.3em;color:#666;vertical-align:middle;font-size:.875em}.pure-form-message{display:block;color:#666;font-size:.875em}@media only screen and (max-width :480px){.pure-form button[type=submit]{margin:.7em 0 0}.pure-form input:not([type]),.pure-form input[type=color],.pure-form input[type=date],.pure-form input[type=datetime-local],.pure-form input[type=datetime],.pure-form input[type=email],.pure-form input[type=month],.pure-form input[type=number],.pure-form input[type=password],.pure-form input[type=search],.pure-form input[type=tel],.pure-form input[type=text],.pure-form input[type=time],.pure-form input[type=url],.pure-form input[type=week],.pure-form label{margin-bottom:.3em;display:block}.pure-group input:not([type]),.pure-group input[type=color],.pure-group input[type=date],.pure-group input[type=datetime-local],.pure-group input[type=datetime],.pure-group input[type=email],.pure-group input[type=month],.pure-group input[type=number],.pure-group input[type=password],.pure-group input[type=search],.pure-group input[type=tel],.pure-group input[type=text],.pure-group input[type=time],.pure-group input[type=url],.pure-group input[type=week]{margin-bottom:0}.pure-form-aligned .pure-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.pure-form-aligned .pure-controls{margin:1.5em 0 0 0}.pure-form-message,.pure-form-message-inline{display:block;font-size:.75em;padding:.2em 0 .8em}}.pure-menu{-webkit-box-sizing:border-box;box-sizing:border-box}.pure-menu-fixed{position:fixed;left:0;top:0;z-index:3}.pure-menu-item,.pure-menu-list{position:relative}.pure-menu-list{list-style:none;margin:0;padding:0}.pure-menu-item{padding:0;margin:0;height:100%}.pure-menu-heading,.pure-menu-link{display:block;text-decoration:none;white-space:nowrap}.pure-menu-horizontal{width:100%;white-space:nowrap}.pure-menu-horizontal .pure-menu-list{display:inline-block}.pure-menu-horizontal .pure-menu-heading,.pure-menu-horizontal .pure-menu-item,.pure-menu-horizontal .pure-menu-separator{display:inline-block;vertical-align:middle}.pure-menu-item .pure-menu-item{display:block}.pure-menu-children{display:none;position:absolute;left:100%;top:0;margin:0;padding:0;z-index:3}.pure-menu-horizontal .pure-menu-children{left:0;top:auto;width:inherit}.pure-menu-active>.pure-menu-children,.pure-menu-allow-hover:hover>.pure-menu-children{display:block;position:absolute}.pure-menu-has-children>.pure-menu-link:after{padding-left:.5em;content:"\25B8";font-size:small}.pure-menu-horizontal .pure-menu-has-children>.pure-menu-link:after{content:"\25BE"}.pure-menu-scrollable{overflow-y:scroll;overflow-x:hidden}.pure-menu-scrollable .pure-menu-list{display:block}.pure-menu-horizontal.pure-menu-scrollable .pure-menu-list{display:inline-block}.pure-menu-horizontal.pure-menu-scrollable{white-space:nowrap;overflow-y:hidden;overflow-x:auto;padding:.5em 0}.pure-menu-horizontal .pure-menu-children .pure-menu-separator,.pure-menu-separator{background-color:#ccc;height:1px;margin:.3em 0}.pure-menu-horizontal .pure-menu-separator{width:1px;height:1.3em;margin:0 .3em}.pure-menu-horizontal .pure-menu-children .pure-menu-separator{display:block;width:auto}.pure-menu-heading{text-transform:uppercase;color:#565d64}.pure-menu-link{color:#777}.pure-menu-children{background-color:#fff}.pure-menu-heading,.pure-menu-link{padding:.5em 1em}.pure-menu-disabled{opacity:.5}.pure-menu-disabled .pure-menu-link:hover{background-color:transparent;cursor:default}.pure-menu-active>.pure-menu-link,.pure-menu-link:focus,.pure-menu-link:hover{background-color:#eee}.pure-menu-selected>.pure-menu-link,.pure-menu-selected>.pure-menu-link:visited{color:#000}.pure-table{border-collapse:collapse;border-spacing:0;empty-cells:show;border:1px solid #cbcbcb}.pure-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.pure-table td,.pure-table th{border-left:1px solid #cbcbcb;border-width:0 0 0 1px;font-size:inherit;margin:0;overflow:visible;padding:.5em 1em}.pure-table thead{background-color:#e0e0e0;color:#000;text-align:left;vertical-align:bottom}.pure-table td{background-color:transparent}.pure-table-odd td{background-color:#f2f2f2}.pure-table-striped tr:nth-child(2n-1) td{background-color:#f2f2f2}.pure-table-bordered td{border-bottom:1px solid #cbcbcb}.pure-table-bordered tbody>tr:last-child>td{border-bottom-width:0}.pure-table-horizontal td,.pure-table-horizontal th{border-width:0 0 1px 0;border-bottom:1px solid #cbcbcb}.pure-table-horizontal tbody>tr:last-child>td{border-bottom-width:0} \ No newline at end of file From 0a17a33b181e392ad15504d25fce10fd4c3cc520 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 01:59:48 +0100 Subject: [PATCH 19/46] more --- Cargo.lock | 40 +- Cargo.toml | 8 - crates/docs_rs_cargo_metadata/Cargo.toml | 1 + crates/docs_rs_cargo_metadata/src/db.rs | 2 +- crates/docs_rs_registry_api/Cargo.toml | 2 + crates/docs_rs_utils/src/lib.rs | 10 + crates/docs_rs_web/Cargo.toml | 31 + crates/docs_rs_web/src/build_details.rs | 721 +-- crates/docs_rs_web/src/builds.rs | 885 ++- crates/docs_rs_web/src/cache.rs | 482 +- crates/docs_rs_web/src/config.rs | 18 + crates/docs_rs_web/src/crate_details.rs | 3360 ++++++----- crates/docs_rs_web/src/error.rs | 260 +- crates/docs_rs_web/src/extractors/context.rs | 2 +- crates/docs_rs_web/src/extractors/path.rs | 114 +- crates/docs_rs_web/src/extractors/rustdoc.rs | 1868 +++--- crates/docs_rs_web/src/features.rs | 577 +- crates/docs_rs_web/src/file.rs | 327 +- crates/docs_rs_web/src/highlight.rs | 10 +- crates/docs_rs_web/src/lib.rs | 1269 ++-- crates/docs_rs_web/src/markdown.rs | 72 +- crates/docs_rs_web/src/metrics.rs | 318 +- crates/docs_rs_web/src/page/templates.rs | 5 +- crates/docs_rs_web/src/page/web_page.rs | 7 +- crates/docs_rs_web/src/releases.rs | 3018 +++++----- crates/docs_rs_web/src/routes.rs | 156 +- crates/docs_rs_web/src/rustdoc.rs | 5271 ++++++++--------- crates/docs_rs_web/src/sitemap.rs | 275 +- crates/docs_rs_web/src/source.rs | 1004 ++-- crates/docs_rs_web/src/statics.rs | 466 +- crates/docs_rs_web/src/status.rs | 328 +- {src => crates/docs_rs_web/src}/utils/html.rs | 115 +- crates/docs_rs_web/src/utils/mod.rs | 2 + .../docs_rs_web/src}/utils/rustc_version.rs | 0 .../docs_rs_web/templates/crate/details.html | 4 +- .../docs_rs_web/templates/rustdoc/topbar.html | 6 +- crates/docs_rs_web_utils/src/escaped_uri.rs | 416 +- src/config.rs | 246 +- src/lib.rs | 23 - src/test/mod.rs | 1 - src/utils/mod.rs | 1 - 41 files changed, 10886 insertions(+), 10835 deletions(-) create mode 100644 crates/docs_rs_web/src/config.rs rename {src => crates/docs_rs_web/src}/utils/html.rs (79%) create mode 100644 crates/docs_rs_web/src/utils/mod.rs rename {src => crates/docs_rs_web/src}/utils/rustc_version.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index af9d0ebde..13d67251d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1880,11 +1880,9 @@ dependencies = [ "async-stream", "aws-smithy-runtime", "aws-smithy-types", - "base64 0.22.1", "bincode 2.0.1", "chrono", "clap", - "constant_time_eq", "criterion", "derive_more 2.0.1", "docs_rs_build_queue", @@ -1900,7 +1898,6 @@ dependencies = [ "docs_rs_web_utils", "docsrs-metadata", "fn-error-context", - "font-awesome-as-a-crate", "futures-util", "getrandom 0.3.4", "hex", @@ -1911,7 +1908,6 @@ dependencies = [ "itertools 0.14.0", "kuchikiki", "log", - "lol_html", "mime", "mockito", "num_cpus", @@ -1939,12 +1935,9 @@ dependencies = [ "test-case", "thiserror 2.0.17", "tokio", - "tokio-util", "toml 0.9.8", "tower", - "tower-http", "tracing", - "tracing-futures", "tracing-log", "url", "walkdir", @@ -1973,6 +1966,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode 2.0.1", + "derive_more 2.0.1", "docs_rs_database", "semver", "serde", @@ -2087,11 +2081,13 @@ name = "docs_rs_registry_api" version = "0.1.0" dependencies = [ "anyhow", + "bincode 2.0.1", "chrono", "docs_rs_database", "docs_rs_utils", "reqwest", "serde", + "sqlx", "tracing", "url", ] @@ -2187,25 +2183,55 @@ version = "0.1.0" dependencies = [ "anyhow", "askama", + "async-stream", "axum", "axum-extra", + "base64 0.22.1", + "bincode 2.0.1", "chrono", "comrak", + "constant_time_eq", + "derive_more 2.0.1", "docs_rs_build_queue", + "docs_rs_cargo_metadata", "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_headers", "docs_rs_opentelemetry", + "docs_rs_registry_api", "docs_rs_storage", "docs_rs_utils", + "docs_rs_web_utils", + "font-awesome-as-a-crate", "futures-util", "grass", "http 1.4.0", + "itertools 0.14.0", + "lol_html", "md5", + "mime", + "opentelemetry", + "opentelemetry_sdk", "phf 0.13.1", "phf_codegen 0.13.1", + "rayon", + "regex", + "semver", + "sentry", "serde", "serde_json", + "serde_with", + "sqlx", + "strum", "syntect", + "thiserror 2.0.17", + "tokio", + "tokio-util", + "tower", + "tower-http", "tracing", + "tracing-futures", + "url", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index b156d2861..c3d3f7ee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,11 +52,9 @@ mockito = "1.0.2" [dependencies] anyhow = { workspace = true } async-stream = { workspace = true } -base64 = "0.22" bincode = { workspace = true } chrono = { workspace = true } clap = { workspace = true } -constant_time_eq = "0.4.2" derive_more = { workspace = true } docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } docs_rs_utils = { path = "crates/docs_rs_utils" } @@ -71,7 +69,6 @@ docs_rs_logging = { path = "crates/docs_rs_logging" } docs_rs_web_utils = { path = "crates/docs_rs_web_utils" } docsrs-metadata = { path = "crates/metadata" } fn-error-context = "0.2.0" -font-awesome-as-a-crate = { path = "crates/font-awesome-as-a-crate" } futures-util = { workspace = true } getrandom = "0.3.1" hex = "0.4.3" @@ -79,7 +76,6 @@ hostname = "0.4.0" http = { workspace = true } itertools = { workspace = true } log = "0.4" -lol_html = "2.0.0" mime = { workspace = true } num_cpus = "1.15.0" opentelemetry = { workspace = true } @@ -103,13 +99,9 @@ sysinfo = { version = "0.37.2", default-features = false, features = ["system"] tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } -tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } toml = "0.9.2" -tower = "0.5.1" -tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] } tracing = { workspace = true } tracing-log = "0.2.0" -tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } url = { workspace = true } walkdir = { workspace = true } diff --git a/crates/docs_rs_cargo_metadata/Cargo.toml b/crates/docs_rs_cargo_metadata/Cargo.toml index 4d22c1fe5..ae2fe3900 100644 --- a/crates/docs_rs_cargo_metadata/Cargo.toml +++ b/crates/docs_rs_cargo_metadata/Cargo.toml @@ -11,3 +11,4 @@ docs_rs_database = { path = "../docs_rs_database" } semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +derive_more = { workspace = true } diff --git a/crates/docs_rs_cargo_metadata/src/db.rs b/crates/docs_rs_cargo_metadata/src/db.rs index 966b13479..116686db6 100644 --- a/crates/docs_rs_cargo_metadata/src/db.rs +++ b/crates/docs_rs_cargo_metadata/src/db.rs @@ -1,5 +1,5 @@ +use super::Dependency; use derive_more::Deref; -use docs_rs_cargo_metadata::Dependency; use semver::VersionReq; use serde::{Deserialize, Serialize}; diff --git a/crates/docs_rs_registry_api/Cargo.toml b/crates/docs_rs_registry_api/Cargo.toml index 60853f72e..ed0e2477f 100644 --- a/crates/docs_rs_registry_api/Cargo.toml +++ b/crates/docs_rs_registry_api/Cargo.toml @@ -12,3 +12,5 @@ reqwest = { workspace = true } serde = { workspace = true } tracing = { workspace = true } url = { workspace = true } +sqlx = { workspace = true } +bincode = { workspace = true } diff --git a/crates/docs_rs_utils/src/lib.rs b/crates/docs_rs_utils/src/lib.rs index 32faebb49..90c8f780c 100644 --- a/crates/docs_rs_utils/src/lib.rs +++ b/crates/docs_rs_utils/src/lib.rs @@ -24,6 +24,16 @@ pub const APP_USER_AGENT: &str = concat!( " )" ); +/// Where rustdoc's static files are stored in S3. +/// Since the prefix starts with `/`, it needs to be referenced with a double slash in +/// API & AWS CLI. +/// Example: +/// `s3://rust-docs-rs//rustdoc-static/something.css` +pub const RUSTDOC_STATIC_STORAGE_PREFIX: &str = "/rustdoc-static/"; + +/// Maximum number of targets allowed for a crate to be documented on. +pub const DEFAULT_MAX_TARGETS: usize = 10; + /// a wrapper around tokio's `spawn_blocking` that /// enables us to write nicer code when the closure /// returns an `anyhow::Result`. diff --git a/crates/docs_rs_web/Cargo.toml b/crates/docs_rs_web/Cargo.toml index 494a60fef..f35171733 100644 --- a/crates/docs_rs_web/Cargo.toml +++ b/crates/docs_rs_web/Cargo.toml @@ -12,6 +12,17 @@ docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_utils = { path = "../docs_rs_utils" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_storage = { path = "../docs_rs_storage" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_headers = { path = "../docs_rs_headers" } +docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } +docs_rs_registry_api = { path = "../docs_rs_registry_api" } +docs_rs_web_utils = { path = "../docs_rs_web_utils" } +semver = { workspace = true } +sentry = { workspace = true } +serde_with = { workspace = true } +derive_more = { workspace = true } +constant_time_eq = "0.4.2" +url = { workspace = true } axum = { version = "0.8.1", features = ["macros"] } askama = { workspace = true } chrono = { workspace = true } @@ -24,6 +35,26 @@ futures-util = { workspace = true } http = { workspace = true } syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } phf = "0.13.1" +base64 = "0.22" +thiserror = { workspace = true } +itertools = { workspace = true } +bincode = { workspace = true } +tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } +sqlx = { workspace = true } +async-stream = { workspace = true } +lol_html = "2.0.0" +opentelemetry = { workspace = true } +regex = { workspace = true } +tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } +mime = { workspace = true } +tokio = { workspace = true } +tower = "0.5.1" +tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] } +rayon = { workspace = true } +opentelemetry_sdk = { workspace = true } +strum = { workspace = true } +font-awesome-as-a-crate = { path = "../font-awesome-as-a-crate" } + [build-dependencies] anyhow = { workspace = true } diff --git a/crates/docs_rs_web/src/build_details.rs b/crates/docs_rs_web/src/build_details.rs index 38a7380e3..866b74d75 100644 --- a/crates/docs_rs_web/src/build_details.rs +++ b/crates/docs_rs_web/src/build_details.rs @@ -1,11 +1,12 @@ use crate::{ - Config, MetaData, + MetaData, cache::CachePolicy, + config::Config, error::{AxumNope, AxumResult}, extractors::{DbConnection, Path, rustdoc::RustdocParams}, file::File, - filters, match_version, - page::templates::{RenderBrands, RenderRegular, RenderSolid}, + impl_axum_webpage, match_version, + page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, }; use anyhow::Context as _; use askama::Template; @@ -14,7 +15,7 @@ use chrono::{DateTime, Utc}; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; use docs_rs_database::types::{BuildId, BuildStatus, version::Version}; use docs_rs_storage::AsyncStorage; -use docs_rs_utils::BUILD_VERSION; +use docs_rs_utils::{BUILD_VERSION, DEFAULT_MAX_TARGETS}; use futures_util::TryStreamExt; use serde::Deserialize; use std::sync::Arc; @@ -181,359 +182,359 @@ pub(crate) async fn build_details_handler( .into_response()) } -#[cfg(test)] -mod tests { - use crate::{ - db::types::{BuildId, ReleaseId}, - test::{ - AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, V0_1, - async_wrapper, fake_release_that_failed_before_build, - }, - }; - use kuchikiki::traits::TendrilSink; - use test_case::test_case; - - fn get_all_log_links(page: &kuchikiki::NodeRef) -> Vec<(String, String)> { - page.select("ul > li a.release") - .unwrap() - .map(|el| { - let attributes = el.attributes.borrow(); - ( - el.text_contents().trim().to_owned(), - attributes.get("href").unwrap().to_string(), - ) - }) - .collect() - } - - async fn build_ids_for_release( - conn: &mut sqlx::PgConnection, - release_id: ReleaseId, - ) -> Vec { - sqlx::query!( - "SELECT id FROM builds WHERE rid = $1 ORDER BY id ASC", - release_id as _ - ) - .fetch_all(conn) - .await - .unwrap() - .into_iter() - .map(|row| BuildId(row.id)) - .collect() - } - - #[test] - fn test_partial_build_result() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let (_, build_id) = fake_release_that_failed_before_build( - &mut conn, - "foo", - "0.1.0", - "some random error", - ) - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get(&format!("/crate/foo/0.1.0/builds/{build_id}")) - .await? - .error_for_status()? - .text() - .await?, - ); - - let info_text = page.select("pre").unwrap().next().unwrap().text_contents(); - - assert!(info_text.contains("# pre-build errors"), "{}", info_text); - assert!(info_text.contains("some random error"), "{}", info_text); - - Ok(()) - }); - } - - #[test] - fn test_partial_build_result_plus_default_target_from_previous_build() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let (release_id, build_id) = fake_release_that_failed_before_build( - &mut conn, - "foo", - "0.1.0", - "some random error", - ) - .await?; - - sqlx::query!( - "UPDATE releases SET default_target = 'x86_64-unknown-linux-gnu' WHERE id = $1", - release_id.0 - ) - .execute(&mut *conn) - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get(&format!("/crate/foo/0.1.0/builds/{build_id}")) - .await? - .error_for_status()? - .text() - .await?, - ); - - let info_text = page.select("pre").unwrap().next().unwrap().text_contents(); - - assert!(info_text.contains("# pre-build errors"), "{}", info_text); - assert!(info_text.contains("some random error"), "{}", info_text); - - Ok(()) - }); - } - - #[test] - fn db_build_logs() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .builds(vec![ - FakeBuild::default() - .no_s3_build_log() - .db_build_log("A build log"), - ]) - .create() - .await?; - - let web = env.web_app().await; - - let page = kuchikiki::parse_html().one( - web.get("/crate/foo/0.1.0/builds") - .await? - .error_for_status()? - .text() - .await?, - ); - - let node = page.select("ul > li a.release").unwrap().next().unwrap(); - let url = { - let attrs = node.attributes.borrow(); - attrs.get("href").unwrap().to_owned() - }; - - let page = kuchikiki::parse_html().one(web.get(&url).await?.text().await?); - assert!(get_all_log_links(&page).is_empty()); - - let log = page.select("pre").unwrap().next().unwrap().text_contents(); - - assert!(log.contains("A build log")); - - Ok(()) - }); - } - - #[test] - fn s3_build_logs() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .builds(vec![FakeBuild::default().s3_build_log("A build log")]) - .create() - .await?; - - let web = env.web_app().await; - - let page = kuchikiki::parse_html() - .one(web.get("/crate/foo/0.1.0/builds").await?.text().await?); - - let node = page.select("ul > li a.release").unwrap().next().unwrap(); - let build_url = { - let attrs = node.attributes.borrow(); - attrs.get("href").unwrap().to_owned() - }; - - let page = kuchikiki::parse_html().one(web.get(&build_url).await?.text().await?); - - let log = page.select("pre").unwrap().next().unwrap().text_contents(); - - assert!(log.contains("A build log")); - - let all_log_links = get_all_log_links(&page); - assert_eq!( - all_log_links, - vec![( - "x86_64-unknown-linux-gnu.txt".into(), - format!("{build_url}/x86_64-unknown-linux-gnu.txt") - )] - ); - - // now get the log with the specific filename in the URL - let log = kuchikiki::parse_html() - .one(web.get(&all_log_links[0].1).await?.text().await?) - .select("pre") - .unwrap() - .next() - .unwrap() - .text_contents(); - - assert!(log.contains("A build log")); - - Ok(()) - }); - } - - #[test] - fn s3_build_logs_multiple_targets() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .builds(vec![ - FakeBuild::default() - .s3_build_log("A build log") - .build_log_for_other_target("other_target", "other target build log"), - ]) - .create() - .await?; - - let web = env.web_app().await; - - let page = kuchikiki::parse_html() - .one(web.get("/crate/foo/0.1.0/builds").await?.text().await?); - - let node = page.select("ul > li a.release").unwrap().next().unwrap(); - let build_url = { - let attrs = node.attributes.borrow(); - attrs.get("href").unwrap().to_owned() - }; - - let page = kuchikiki::parse_html().one(web.get(&build_url).await?.text().await?); - - let log = page.select("pre").unwrap().next().unwrap().text_contents(); - - assert!(log.contains("A build log")); - - let all_log_links = get_all_log_links(&page); - assert_eq!( - all_log_links, - vec![ - ( - "other_target.txt".into(), - format!("{build_url}/other_target.txt") - ), - ( - "x86_64-unknown-linux-gnu.txt".into(), - format!("{build_url}/x86_64-unknown-linux-gnu.txt"), - ) - ] - ); - - for (url, expected_content) in &[ - (&all_log_links[0].1, "other target build log"), - (&all_log_links[1].1, "A build log"), - ] { - let other_log = kuchikiki::parse_html() - .one(web.get(url).await?.text().await?) - .select("pre") - .unwrap() - .next() - .unwrap() - .text_contents(); - - assert!(other_log.contains(expected_content)); - } - - Ok(()) - }); - } - - #[test] - fn both_build_logs() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .builds(vec![ - FakeBuild::default() - .s3_build_log("A build log") - .db_build_log("Another build log"), - ]) - .create() - .await?; - - let web = env.web_app().await; - - let page = kuchikiki::parse_html() - .one(web.get("/crate/foo/0.1.0/builds").await?.text().await?); - - let node = page.select("ul > li a.release").unwrap().next().unwrap(); - let url = { - let attrs = node.attributes.borrow(); - attrs.get("href").unwrap().to_owned() - }; - - let page = kuchikiki::parse_html().one(web.get(&url).await?.text().await?); - - let log = page.select("pre").unwrap().next().unwrap().text_contents(); - - // Relatively arbitrarily the DB is prioritised - assert!(log.contains("Another build log")); - - Ok(()) - }); - } - - #[test_case("42")] - #[test_case("nan")] - fn non_existing_build(build_id: &str) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .create() - .await?; - - let res = env - .web_app() - .await - .get(&format!("/crate/foo/0.1.0/builds/{build_id}")) - .await?; - assert_eq!(res.status(), 404); - assert!(res.text().await?.contains("no such build")); - - Ok(()) - }); - } - - #[tokio::test(flavor = "multi_thread")] - async fn build_detail_via_latest() -> anyhow::Result<()> { - let env = TestEnvironment::new().await?; - let rid = env - .fake_release() - .await - .name("foo") - .version(V0_1) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - let build_id = { - let ids = build_ids_for_release(&mut conn, rid).await; - assert_eq!(ids.len(), 1); - ids[0] - }; - - env.web_app() - .await - .assert_success(&format!("/crate/foo/latest/builds/{build_id}")) - .await?; - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// db::types::{BuildId, ReleaseId}, +// test::{ +// AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, V0_1, +// async_wrapper, fake_release_that_failed_before_build, +// }, +// }; +// use kuchikiki::traits::TendrilSink; +// use test_case::test_case; + +// fn get_all_log_links(page: &kuchikiki::NodeRef) -> Vec<(String, String)> { +// page.select("ul > li a.release") +// .unwrap() +// .map(|el| { +// let attributes = el.attributes.borrow(); +// ( +// el.text_contents().trim().to_owned(), +// attributes.get("href").unwrap().to_string(), +// ) +// }) +// .collect() +// } + +// async fn build_ids_for_release( +// conn: &mut sqlx::PgConnection, +// release_id: ReleaseId, +// ) -> Vec { +// sqlx::query!( +// "SELECT id FROM builds WHERE rid = $1 ORDER BY id ASC", +// release_id as _ +// ) +// .fetch_all(conn) +// .await +// .unwrap() +// .into_iter() +// .map(|row| BuildId(row.id)) +// .collect() +// } + +// #[test] +// fn test_partial_build_result() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let (_, build_id) = fake_release_that_failed_before_build( +// &mut conn, +// "foo", +// "0.1.0", +// "some random error", +// ) +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get(&format!("/crate/foo/0.1.0/builds/{build_id}")) +// .await? +// .error_for_status()? +// .text() +// .await?, +// ); + +// let info_text = page.select("pre").unwrap().next().unwrap().text_contents(); + +// assert!(info_text.contains("# pre-build errors"), "{}", info_text); +// assert!(info_text.contains("some random error"), "{}", info_text); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_partial_build_result_plus_default_target_from_previous_build() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let (release_id, build_id) = fake_release_that_failed_before_build( +// &mut conn, +// "foo", +// "0.1.0", +// "some random error", +// ) +// .await?; + +// sqlx::query!( +// "UPDATE releases SET default_target = 'x86_64-unknown-linux-gnu' WHERE id = $1", +// release_id.0 +// ) +// .execute(&mut *conn) +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get(&format!("/crate/foo/0.1.0/builds/{build_id}")) +// .await? +// .error_for_status()? +// .text() +// .await?, +// ); + +// let info_text = page.select("pre").unwrap().next().unwrap().text_contents(); + +// assert!(info_text.contains("# pre-build errors"), "{}", info_text); +// assert!(info_text.contains("some random error"), "{}", info_text); + +// Ok(()) +// }); +// } + +// #[test] +// fn db_build_logs() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default() +// .no_s3_build_log() +// .db_build_log("A build log"), +// ]) +// .create() +// .await?; + +// let web = env.web_app().await; + +// let page = kuchikiki::parse_html().one( +// web.get("/crate/foo/0.1.0/builds") +// .await? +// .error_for_status()? +// .text() +// .await?, +// ); + +// let node = page.select("ul > li a.release").unwrap().next().unwrap(); +// let url = { +// let attrs = node.attributes.borrow(); +// attrs.get("href").unwrap().to_owned() +// }; + +// let page = kuchikiki::parse_html().one(web.get(&url).await?.text().await?); +// assert!(get_all_log_links(&page).is_empty()); + +// let log = page.select("pre").unwrap().next().unwrap().text_contents(); + +// assert!(log.contains("A build log")); + +// Ok(()) +// }); +// } + +// #[test] +// fn s3_build_logs() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .builds(vec![FakeBuild::default().s3_build_log("A build log")]) +// .create() +// .await?; + +// let web = env.web_app().await; + +// let page = kuchikiki::parse_html() +// .one(web.get("/crate/foo/0.1.0/builds").await?.text().await?); + +// let node = page.select("ul > li a.release").unwrap().next().unwrap(); +// let build_url = { +// let attrs = node.attributes.borrow(); +// attrs.get("href").unwrap().to_owned() +// }; + +// let page = kuchikiki::parse_html().one(web.get(&build_url).await?.text().await?); + +// let log = page.select("pre").unwrap().next().unwrap().text_contents(); + +// assert!(log.contains("A build log")); + +// let all_log_links = get_all_log_links(&page); +// assert_eq!( +// all_log_links, +// vec![( +// "x86_64-unknown-linux-gnu.txt".into(), +// format!("{build_url}/x86_64-unknown-linux-gnu.txt") +// )] +// ); + +// // now get the log with the specific filename in the URL +// let log = kuchikiki::parse_html() +// .one(web.get(&all_log_links[0].1).await?.text().await?) +// .select("pre") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(); + +// assert!(log.contains("A build log")); + +// Ok(()) +// }); +// } + +// #[test] +// fn s3_build_logs_multiple_targets() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default() +// .s3_build_log("A build log") +// .build_log_for_other_target("other_target", "other target build log"), +// ]) +// .create() +// .await?; + +// let web = env.web_app().await; + +// let page = kuchikiki::parse_html() +// .one(web.get("/crate/foo/0.1.0/builds").await?.text().await?); + +// let node = page.select("ul > li a.release").unwrap().next().unwrap(); +// let build_url = { +// let attrs = node.attributes.borrow(); +// attrs.get("href").unwrap().to_owned() +// }; + +// let page = kuchikiki::parse_html().one(web.get(&build_url).await?.text().await?); + +// let log = page.select("pre").unwrap().next().unwrap().text_contents(); + +// assert!(log.contains("A build log")); + +// let all_log_links = get_all_log_links(&page); +// assert_eq!( +// all_log_links, +// vec![ +// ( +// "other_target.txt".into(), +// format!("{build_url}/other_target.txt") +// ), +// ( +// "x86_64-unknown-linux-gnu.txt".into(), +// format!("{build_url}/x86_64-unknown-linux-gnu.txt"), +// ) +// ] +// ); + +// for (url, expected_content) in &[ +// (&all_log_links[0].1, "other target build log"), +// (&all_log_links[1].1, "A build log"), +// ] { +// let other_log = kuchikiki::parse_html() +// .one(web.get(url).await?.text().await?) +// .select("pre") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(); + +// assert!(other_log.contains(expected_content)); +// } + +// Ok(()) +// }); +// } + +// #[test] +// fn both_build_logs() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default() +// .s3_build_log("A build log") +// .db_build_log("Another build log"), +// ]) +// .create() +// .await?; + +// let web = env.web_app().await; + +// let page = kuchikiki::parse_html() +// .one(web.get("/crate/foo/0.1.0/builds").await?.text().await?); + +// let node = page.select("ul > li a.release").unwrap().next().unwrap(); +// let url = { +// let attrs = node.attributes.borrow(); +// attrs.get("href").unwrap().to_owned() +// }; + +// let page = kuchikiki::parse_html().one(web.get(&url).await?.text().await?); + +// let log = page.select("pre").unwrap().next().unwrap().text_contents(); + +// // Relatively arbitrarily the DB is prioritised +// assert!(log.contains("Another build log")); + +// Ok(()) +// }); +// } + +// #[test_case("42")] +// #[test_case("nan")] +// fn non_existing_build(build_id: &str) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .create() +// .await?; + +// let res = env +// .web_app() +// .await +// .get(&format!("/crate/foo/0.1.0/builds/{build_id}")) +// .await?; +// assert_eq!(res.status(), 404); +// assert!(res.text().await?.contains("no such build")); + +// Ok(()) +// }); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn build_detail_via_latest() -> anyhow::Result<()> { +// let env = TestEnvironment::new().await?; +// let rid = env +// .fake_release() +// .await +// .name("foo") +// .version(V0_1) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// let build_id = { +// let ids = build_ids_for_release(&mut conn, rid).await; +// assert_eq!(ids.len(), 1); +// ids[0] +// }; + +// env.web_app() +// .await +// .assert_success(&format!("/crate/foo/latest/builds/{build_id}")) +// .await?; + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_web/src/builds.rs b/crates/docs_rs_web/src/builds.rs index e0c7eec33..5f81645f5 100644 --- a/crates/docs_rs_web/src/builds.rs +++ b/crates/docs_rs_web/src/builds.rs @@ -1,16 +1,15 @@ use crate::{ - Config, - db::types::BuildStatus, - docbuilder::Limits, + // docbuilder::Limits, + MetaData, + ReqVersion, + cache::CachePolicy, + config::Config, + error::{AxumNope, AxumResult, JsonAxumNope, JsonAxumResult}, + extractors::{DbConnection, Path, rustdoc::RustdocParams}, impl_axum_webpage, - web::{ - MetaData, ReqVersion, - cache::CachePolicy, - error::{AxumNope, AxumResult, JsonAxumNope, JsonAxumResult}, - extractors::{DbConnection, Path, rustdoc::RustdocParams}, - filters, match_version, - page::templates::{RenderBrands, RenderRegular, RenderSolid}, - }, + // filters, + match_version, + page::templates::{RenderBrands, RenderRegular, RenderSolid}, }; use anyhow::{Result, anyhow}; use askama::Template; @@ -22,7 +21,7 @@ use axum_extra::{ use chrono::{DateTime, Utc}; use constant_time_eq::constant_time_eq; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; -use docs_rs_database::types::{BuildId, version::Version}; +use docs_rs_database::types::{BuildId, BuildStatus, version::Version}; use docs_rs_headers::CanonicalUrl; use http::StatusCode; use std::sync::Arc; @@ -207,434 +206,434 @@ async fn get_builds( .await?) } -#[cfg(test)] -mod tests { - use super::BuildStatus; - use crate::{ - db::Overrides, - test::{ - AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, V1, V2, - async_wrapper, fake_release_that_failed_before_build, - }, - web::cache::CachePolicy, - }; - use anyhow::Result; - use axum::{body::Body, http::Request}; - use kuchikiki::traits::TendrilSink; - use reqwest::StatusCode; - use tower::ServiceExt; - - #[test] - fn build_list_empty_build() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - fake_release_that_failed_before_build(&mut conn, "foo", "0.1.0", "some errors").await?; - - let response = env - .web_app() - .await - .get("/crate/foo/0.1.0/builds") - .await? - .error_for_status()?; - response.assert_cache_control(CachePolicy::NoCaching, env.config()); - let page = kuchikiki::parse_html().one(response.text().await?); - - let rows: Vec<_> = page - .select("ul > li a.release") - .unwrap() - .map(|row| row.text_contents()) - .collect(); - - assert_eq!(rows.len(), 1); - // third column contains build-start time, even when the rest is empty - assert_eq!(rows[0].chars().filter(|&c| c == '—').count(), 2); - - Ok(()) - }); - } - - #[test] - fn build_list() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .builds(vec![ - FakeBuild::default() - .rustc_version("rustc (blabla 2019-01-01)") - .docsrs_version("docs.rs 1.0.0"), - FakeBuild::default() - .successful(false) - .rustc_version("rustc (blabla 2020-01-01)") - .docsrs_version("docs.rs 2.0.0"), - FakeBuild::default() - .rustc_version("rustc (blabla 2021-01-01)") - .docsrs_version("docs.rs 3.0.0"), - FakeBuild::default() - .build_status(BuildStatus::InProgress) - .rustc_version("rustc (blabla 2022-01-01)") - .docsrs_version("docs.rs 4.0.0"), - ]) - .create() - .await?; - - let response = env.web_app().await.get("/crate/foo/0.1.0/builds").await?; - response.assert_cache_control(CachePolicy::NoCaching, env.config()); - let page = kuchikiki::parse_html().one(response.text().await?); - - let rows: Vec<_> = page - .select("ul > li a.release") - .unwrap() - .map(|row| row.text_contents()) - .collect(); - - assert!(rows[0].contains("rustc (blabla 2021-01-01)")); - assert!(rows[0].contains("docs.rs 3.0.0")); - assert!(rows[1].contains("rustc (blabla 2020-01-01)")); - assert!(rows[1].contains("docs.rs 2.0.0")); - assert!(rows[2].contains("rustc (blabla 2019-01-01)")); - assert!(rows[2].contains("docs.rs 1.0.0")); - - Ok(()) - }); - } - - #[tokio::test(flavor = "multi_thread")] - async fn build_trigger_rebuild_missing_config() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .cratesio_token(None) - .build()?, - ) - .await?; - - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .create() - .await?; - - { - let response = env - .web_app() - .await - .get("/crate/regex/1.3.1/rebuild") - .await?; - // Needs POST - assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); - } - - { - let response = env - .web_app() - .await - .post("/crate/regex/1.3.1/rebuild") - .await?; - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - let json: serde_json::Value = response.json().await?; - assert_eq!( - json, - serde_json::json!({ - "title": "Unauthorized", - "message": "Endpoint is not configured" - }) - ); - } - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn build_trigger_rebuild_with_config() -> Result<()> { - let correct_token = "foo137"; - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .cratesio_token(Some(correct_token.into())) - .build()?, - ) - .await?; - - env.fake_release() - .await - .name("foo") - .version(V1) - .create() - .await?; - - { - let response = env - .web_app() - .await - .post("/crate/regex/1.3.1/rebuild") - .await?; - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - let json: serde_json::Value = response.json().await?; - assert_eq!( - json, - serde_json::json!({ - "title": "Unauthorized", - "message": "Missing authentication token" - }) - ); - } - - { - let app = env.web_app().await; - let response = app - .oneshot( - Request::builder() - .uri("/crate/regex/1.3.1/rebuild") - .method("POST") - .header("Authorization", "Bearer someinvalidtoken") - .body(Body::empty()) - .unwrap(), - ) - .await?; - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - let json: serde_json::Value = response.json().await?; - assert_eq!( - json, - serde_json::json!({ - "title": "Unauthorized", - "message": "The token used for authentication is not valid" - }) - ); - } - - let build_queue = env.async_build_queue(); - - assert_eq!(build_queue.pending_count().await?, 0); - assert!(!build_queue.has_build_queued("foo", &V1).await?); - - { - let app = env.web_app().await; - let response = app - .oneshot( - Request::builder() - .uri(format!("/crate/foo/{V1}/rebuild")) - .method("POST") - .header("Authorization", &format!("Bearer {correct_token}")) - .body(Body::empty()) - .unwrap(), - ) - .await?; - assert_eq!(response.status(), StatusCode::CREATED); - let json: serde_json::Value = response.json().await?; - assert_eq!(json, serde_json::json!({})); - } - - assert_eq!(build_queue.pending_count().await?, 1); - assert!(build_queue.has_build_queued("foo", &V1).await?); - - { - let app = env.web_app().await; - let response = app - .oneshot( - Request::builder() - .uri(format!("/crate/foo/{V1}/rebuild")) - .method("POST") - .header("Authorization", &format!("Bearer {correct_token}")) - .body(Body::empty()) - .unwrap(), - ) - .await?; - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - let json: serde_json::Value = response.json().await?; - assert_eq!( - json, - serde_json::json!({ - "title": "Bad request", - "message": format!("crate foo {V1} already queued for rebuild") - }) - ); - } - - assert_eq!(build_queue.pending_count().await?, 1); - assert!(build_queue.has_build_queued("foo", &V1).await?); - - Ok(()) - } - - #[test] - fn build_empty_list() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version(V1) - .no_builds() - .create() - .await?; - - let response = env - .web_app() - .await - .get(&format!("/crate/foo/{V1}/builds")) - .await?; - - response.assert_cache_control(CachePolicy::NoCaching, env.config()); - let page = kuchikiki::parse_html().one(response.text().await?); - - let rows: Vec<_> = page - .select("ul > li a.release") - .unwrap() - .map(|row| row.text_contents()) - .collect(); - - assert!(rows.is_empty()); - - let warning = page - .select_first(".warning") - .expect("missing warning element") - .text_contents(); - - assert!(warning.contains("has not built")); - assert!(warning.contains("queued")); - assert!(warning.contains("open an issue")); - - Ok(()) - }); - } - - #[test] - fn limits() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version(V1) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - let limits = Overrides { - memory: Some(6 * 1024 * 1024 * 1024), - targets: Some(1), - timeout: Some(std::time::Duration::from_secs(2 * 60 * 60)), - }; - Overrides::save(&mut conn, "foo", limits).await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get(&format!("/crate/foo/{V1}/builds")) - .await? - .text() - .await?, - ); - - let header = page.select(".about h4").unwrap().next().unwrap(); - assert_eq!(header.text_contents(), "foo's sandbox limits"); - - let values: Vec<_> = page - .select(".about table tr td:last-child") - .unwrap() - .map(|row| row.text_contents()) - .collect(); - let values: Vec<_> = values.iter().map(|v| &**v).collect(); - - assert!(values.contains(&"6.44 GB")); - assert!(values.contains(&"2 hours")); - assert!(values.contains(&"102.4 kB")); - assert!(values.contains(&"blocked")); - assert!(values.contains(&"1")); - - Ok(()) - }); - } - - #[test] - fn latest_200() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("aquarelle") - .version(V1) - .builds(vec![ - FakeBuild::default() - .rustc_version("rustc (blabla 2019-01-01)") - .docsrs_version("docs.rs 1.0.0"), - ]) - .create() - .await?; - - env.fake_release() - .await - .name("aquarelle") - .version(V2) - .builds(vec![ - FakeBuild::default() - .rustc_version("rustc (blabla 2019-01-01)") - .docsrs_version("docs.rs 1.0.0"), - ]) - .create() - .await?; - - let resp = env - .web_app() - .await - .get("/crate/aquarelle/latest/builds") - .await?; - let body = resp.text().await?; - assert!(body.contains(" = page +// .select("ul > li a.release") +// .unwrap() +// .map(|row| row.text_contents()) +// .collect(); + +// assert_eq!(rows.len(), 1); +// // third column contains build-start time, even when the rest is empty +// assert_eq!(rows[0].chars().filter(|&c| c == '—').count(), 2); + +// Ok(()) +// }); +// } + +// #[test] +// fn build_list() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default() +// .rustc_version("rustc (blabla 2019-01-01)") +// .docsrs_version("docs.rs 1.0.0"), +// FakeBuild::default() +// .successful(false) +// .rustc_version("rustc (blabla 2020-01-01)") +// .docsrs_version("docs.rs 2.0.0"), +// FakeBuild::default() +// .rustc_version("rustc (blabla 2021-01-01)") +// .docsrs_version("docs.rs 3.0.0"), +// FakeBuild::default() +// .build_status(BuildStatus::InProgress) +// .rustc_version("rustc (blabla 2022-01-01)") +// .docsrs_version("docs.rs 4.0.0"), +// ]) +// .create() +// .await?; + +// let response = env.web_app().await.get("/crate/foo/0.1.0/builds").await?; +// response.assert_cache_control(CachePolicy::NoCaching, env.config()); +// let page = kuchikiki::parse_html().one(response.text().await?); + +// let rows: Vec<_> = page +// .select("ul > li a.release") +// .unwrap() +// .map(|row| row.text_contents()) +// .collect(); + +// assert!(rows[0].contains("rustc (blabla 2021-01-01)")); +// assert!(rows[0].contains("docs.rs 3.0.0")); +// assert!(rows[1].contains("rustc (blabla 2020-01-01)")); +// assert!(rows[1].contains("docs.rs 2.0.0")); +// assert!(rows[2].contains("rustc (blabla 2019-01-01)")); +// assert!(rows[2].contains("docs.rs 1.0.0")); + +// Ok(()) +// }); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn build_trigger_rebuild_missing_config() -> Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .cratesio_token(None) +// .build()?, +// ) +// .await?; + +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .create() +// .await?; + +// { +// let response = env +// .web_app() +// .await +// .get("/crate/regex/1.3.1/rebuild") +// .await?; +// // Needs POST +// assert_eq!(response.status(), StatusCode::METHOD_NOT_ALLOWED); +// } + +// { +// let response = env +// .web_app() +// .await +// .post("/crate/regex/1.3.1/rebuild") +// .await?; +// assert_eq!(response.status(), StatusCode::UNAUTHORIZED); +// let json: serde_json::Value = response.json().await?; +// assert_eq!( +// json, +// serde_json::json!({ +// "title": "Unauthorized", +// "message": "Endpoint is not configured" +// }) +// ); +// } + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn build_trigger_rebuild_with_config() -> Result<()> { +// let correct_token = "foo137"; +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .cratesio_token(Some(correct_token.into())) +// .build()?, +// ) +// .await?; + +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .create() +// .await?; + +// { +// let response = env +// .web_app() +// .await +// .post("/crate/regex/1.3.1/rebuild") +// .await?; +// assert_eq!(response.status(), StatusCode::UNAUTHORIZED); +// let json: serde_json::Value = response.json().await?; +// assert_eq!( +// json, +// serde_json::json!({ +// "title": "Unauthorized", +// "message": "Missing authentication token" +// }) +// ); +// } + +// { +// let app = env.web_app().await; +// let response = app +// .oneshot( +// Request::builder() +// .uri("/crate/regex/1.3.1/rebuild") +// .method("POST") +// .header("Authorization", "Bearer someinvalidtoken") +// .body(Body::empty()) +// .unwrap(), +// ) +// .await?; +// assert_eq!(response.status(), StatusCode::UNAUTHORIZED); +// let json: serde_json::Value = response.json().await?; +// assert_eq!( +// json, +// serde_json::json!({ +// "title": "Unauthorized", +// "message": "The token used for authentication is not valid" +// }) +// ); +// } + +// let build_queue = env.async_build_queue(); + +// assert_eq!(build_queue.pending_count().await?, 0); +// assert!(!build_queue.has_build_queued("foo", &V1).await?); + +// { +// let app = env.web_app().await; +// let response = app +// .oneshot( +// Request::builder() +// .uri(format!("/crate/foo/{V1}/rebuild")) +// .method("POST") +// .header("Authorization", &format!("Bearer {correct_token}")) +// .body(Body::empty()) +// .unwrap(), +// ) +// .await?; +// assert_eq!(response.status(), StatusCode::CREATED); +// let json: serde_json::Value = response.json().await?; +// assert_eq!(json, serde_json::json!({})); +// } + +// assert_eq!(build_queue.pending_count().await?, 1); +// assert!(build_queue.has_build_queued("foo", &V1).await?); + +// { +// let app = env.web_app().await; +// let response = app +// .oneshot( +// Request::builder() +// .uri(format!("/crate/foo/{V1}/rebuild")) +// .method("POST") +// .header("Authorization", &format!("Bearer {correct_token}")) +// .body(Body::empty()) +// .unwrap(), +// ) +// .await?; +// assert_eq!(response.status(), StatusCode::BAD_REQUEST); +// let json: serde_json::Value = response.json().await?; +// assert_eq!( +// json, +// serde_json::json!({ +// "title": "Bad request", +// "message": format!("crate foo {V1} already queued for rebuild") +// }) +// ); +// } + +// assert_eq!(build_queue.pending_count().await?, 1); +// assert!(build_queue.has_build_queued("foo", &V1).await?); + +// Ok(()) +// } + +// #[test] +// fn build_empty_list() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .no_builds() +// .create() +// .await?; + +// let response = env +// .web_app() +// .await +// .get(&format!("/crate/foo/{V1}/builds")) +// .await?; + +// response.assert_cache_control(CachePolicy::NoCaching, env.config()); +// let page = kuchikiki::parse_html().one(response.text().await?); + +// let rows: Vec<_> = page +// .select("ul > li a.release") +// .unwrap() +// .map(|row| row.text_contents()) +// .collect(); + +// assert!(rows.is_empty()); + +// let warning = page +// .select_first(".warning") +// .expect("missing warning element") +// .text_contents(); + +// assert!(warning.contains("has not built")); +// assert!(warning.contains("queued")); +// assert!(warning.contains("open an issue")); + +// Ok(()) +// }); +// } + +// #[test] +// fn limits() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// let limits = Overrides { +// memory: Some(6 * 1024 * 1024 * 1024), +// targets: Some(1), +// timeout: Some(std::time::Duration::from_secs(2 * 60 * 60)), +// }; +// Overrides::save(&mut conn, "foo", limits).await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get(&format!("/crate/foo/{V1}/builds")) +// .await? +// .text() +// .await?, +// ); + +// let header = page.select(".about h4").unwrap().next().unwrap(); +// assert_eq!(header.text_contents(), "foo's sandbox limits"); + +// let values: Vec<_> = page +// .select(".about table tr td:last-child") +// .unwrap() +// .map(|row| row.text_contents()) +// .collect(); +// let values: Vec<_> = values.iter().map(|v| &**v).collect(); + +// assert!(values.contains(&"6.44 GB")); +// assert!(values.contains(&"2 hours")); +// assert!(values.contains(&"102.4 kB")); +// assert!(values.contains(&"blocked")); +// assert!(values.contains(&"1")); + +// Ok(()) +// }); +// } + +// #[test] +// fn latest_200() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("aquarelle") +// .version(V1) +// .builds(vec![ +// FakeBuild::default() +// .rustc_version("rustc (blabla 2019-01-01)") +// .docsrs_version("docs.rs 1.0.0"), +// ]) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("aquarelle") +// .version(V2) +// .builds(vec![ +// FakeBuild::default() +// .rustc_version("rustc (blabla 2019-01-01)") +// .docsrs_version("docs.rs 1.0.0"), +// ]) +// .create() +// .await?; + +// let resp = env +// .web_app() +// .await +// .get("/crate/aquarelle/latest/builds") +// .await?; +// let body = resp.text().await?; +// assert!(body.contains(" Result<()> { - assert!(!value.as_bytes().is_empty()); - - // first parse attempt. - // The `CacheControl` typed header impl will just skip over unknown directives. - let parsed: CacheControl = test_typed_decode(value.clone())?.unwrap(); - - // So we just re-render it, re-parse and compare both. - let re_rendered = test_typed_encode(parsed.clone()); - let re_parsed: CacheControl = test_typed_decode(re_rendered)?.unwrap(); - - assert_eq!(parsed, re_parsed); - - Ok(()) - } - - #[test] - fn test_const_response_consistency() { - assert_eq!( - FOREVER_IN_FASTLY_CDN.cache_control, - NO_CACHING.cache_control - ); - } - - #[test_matrix( - [true, false], - [Some(86400), None] - )] - fn test_validate_header_syntax_for_all_possible_combinations( - cache_invalidatable_responses: bool, - stale_while_revalidate: Option, - ) -> Result<()> { - let config = TestEnvironment::base_config() - .cache_invalidatable_responses(cache_invalidatable_responses) - .cache_control_stale_while_revalidate(stale_while_revalidate) - .build()?; - - for policy in CachePolicy::iter() { - let headers = policy.render(&config); - - if let Some(cache_control) = headers.cache_control { - validate_cache_control(&cache_control).with_context(|| { - format!( - "couldn't validate Cache-Control header syntax for policy {:?}", - policy - ) - })?; - } - - if let Some(surrogate_control) = headers.surrogate_control { - validate_cache_control(&surrogate_control).with_context(|| { - format!( - "couldn't validate Surrogate-Control header syntax for policy {:?}", - policy - ) - })?; - } - } - Ok(()) - } - - #[test_case(CachePolicy::NoCaching, Some("max-age=0"), None)] - #[test_case( - CachePolicy::NoStoreMustRevalidate, - Some("no-cache, no-store, must-revalidate, max-age=0"), - None - )] - #[test_case( - CachePolicy::ForeverInCdnAndBrowser, - Some("public, max-age=31104000, immutable"), - None - )] - #[test_case(CachePolicy::ForeverInCdn, Some("max-age=0"), Some("max-age=31536000"))] - #[test_case( - CachePolicy::ForeverInCdnAndStaleInBrowser, - Some("stale-while-revalidate=86400"), - Some("max-age=31536000") - )] - fn render_fastly( - cache: CachePolicy, - cache_control: Option<&str>, - surrogate_control: Option<&str>, - ) -> Result<()> { - let config = TestEnvironment::base_config().build()?; - let headers = cache.render(&config); - - assert_eq!( - headers.cache_control, - cache_control.map(|s| HeaderValue::from_str(s).unwrap()) - ); - - assert_eq!( - headers.surrogate_control, - surrogate_control.map(|s| HeaderValue::from_str(s).unwrap()) - ); - - Ok(()) - } - - #[test] - fn render_stale_without_config_fastly() -> Result<()> { - let config = TestEnvironment::base_config() - .cache_control_stale_while_revalidate(None) - .build()?; - - let headers = CachePolicy::ForeverInCdnAndStaleInBrowser.render(&config); - assert_eq!(headers, FOREVER_IN_FASTLY_CDN); - - Ok(()) - } - - #[test] - fn render_stale_with_config_fastly() -> Result<()> { - let config = TestEnvironment::base_config() - .cache_control_stale_while_revalidate(Some(666)) - .build()?; - - let headers = CachePolicy::ForeverInCdnAndStaleInBrowser.render(&config); - assert_eq!(headers.cache_control.unwrap(), "stale-while-revalidate=666"); - assert_eq!( - headers.surrogate_control, - FOREVER_IN_FASTLY_CDN.surrogate_control - ); - - Ok(()) - } - - #[test] - fn render_forever_in_cdn_disabled_fastly() -> Result<()> { - let config = TestEnvironment::base_config() - .cache_invalidatable_responses(false) - .build()?; - - let headers = CachePolicy::ForeverInCdn.render(&config); - assert_eq!(headers.cache_control.unwrap(), "max-age=0"); - assert!(headers.surrogate_control.is_none()); - - Ok(()) - } - - #[test] - fn render_forever_in_cdn_or_stale_disabled_fastly() -> Result<()> { - let config = TestEnvironment::base_config() - .cache_invalidatable_responses(false) - .build()?; - - let headers = CachePolicy::ForeverInCdnAndStaleInBrowser.render(&config); - assert_eq!(headers.cache_control.unwrap(), "max-age=0"); - assert!(headers.surrogate_control.is_none()); - - Ok(()) - } - - #[tokio::test] - async fn test_middleware_reacts_to_fastly_header_in_crate_route() -> Result<()> { - let config = TestEnvironment::base_config() - .cache_invalidatable_responses(true) - .build()?; - - let app = Router::new() - .route( - "/{name}", - get(move || async move { (Extension(CachePolicy::ForeverInCdn), "Hello, World!") }), - ) - .layer( - ServiceBuilder::new() - .layer(Extension(Arc::new(config))) - .layer(axum::middleware::from_fn(cache_middleware)), - ); - - let builder = Request::builder().uri("/krate"); - - let response = app - .clone() - .oneshot(builder.body(Body::empty()).unwrap()) - .await?; - - assert!( - response.status().is_success(), - "{}", - response.text().await.unwrap(), - ); - assert_cache_headers_eq(&response, &FOREVER_IN_FASTLY_CDN); - - Ok(()) - } - - #[tokio::test] - async fn test_middleware_reacts_to_fastly_header_in_other_route() -> Result<()> { - let config = TestEnvironment::base_config().build()?; - - let app = Router::new() - .route( - "/", - get(move || async move { - ( - Extension(CachePolicy::ForeverInCdnAndBrowser), - "Hello, World!", - ) - }), - ) - .layer( - ServiceBuilder::new() - .layer(Extension(Arc::new(config))) - .layer(axum::middleware::from_fn(cache_middleware)), - ); - - let builder = Request::builder().uri("/"); - - let response = app - .clone() - .oneshot(builder.body(Body::empty()).unwrap()) - .await?; - - assert!( - response.status().is_success(), - "{}", - response.text().await.unwrap(), - ); - - // this cache policy leads to the same result in both CDNs - assert_cache_headers_eq(&response, &FOREVER_IN_CDN_AND_BROWSER); - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::{ +// AxumResponseTestExt as _, TestEnvironment, assert_cache_headers_eq, +// headers::{test_typed_decode, test_typed_encode}, +// }; +// use anyhow::{Context as _, Result}; +// use axum::{Router, body::Body, http::Request, routing::get}; +// use axum_extra::headers::CacheControl; +// use strum::IntoEnumIterator as _; +// use test_case::{test_case, test_matrix}; +// use tower::{ServiceBuilder, ServiceExt as _}; + +// fn validate_cache_control(value: &HeaderValue) -> Result<()> { +// assert!(!value.as_bytes().is_empty()); + +// // first parse attempt. +// // The `CacheControl` typed header impl will just skip over unknown directives. +// let parsed: CacheControl = test_typed_decode(value.clone())?.unwrap(); + +// // So we just re-render it, re-parse and compare both. +// let re_rendered = test_typed_encode(parsed.clone()); +// let re_parsed: CacheControl = test_typed_decode(re_rendered)?.unwrap(); + +// assert_eq!(parsed, re_parsed); + +// Ok(()) +// } + +// #[test] +// fn test_const_response_consistency() { +// assert_eq!( +// FOREVER_IN_FASTLY_CDN.cache_control, +// NO_CACHING.cache_control +// ); +// } + +// #[test_matrix( +// [true, false], +// [Some(86400), None] +// )] +// fn test_validate_header_syntax_for_all_possible_combinations( +// cache_invalidatable_responses: bool, +// stale_while_revalidate: Option, +// ) -> Result<()> { +// let config = TestEnvironment::base_config() +// .cache_invalidatable_responses(cache_invalidatable_responses) +// .cache_control_stale_while_revalidate(stale_while_revalidate) +// .build()?; + +// for policy in CachePolicy::iter() { +// let headers = policy.render(&config); + +// if let Some(cache_control) = headers.cache_control { +// validate_cache_control(&cache_control).with_context(|| { +// format!( +// "couldn't validate Cache-Control header syntax for policy {:?}", +// policy +// ) +// })?; +// } + +// if let Some(surrogate_control) = headers.surrogate_control { +// validate_cache_control(&surrogate_control).with_context(|| { +// format!( +// "couldn't validate Surrogate-Control header syntax for policy {:?}", +// policy +// ) +// })?; +// } +// } +// Ok(()) +// } + +// #[test_case(CachePolicy::NoCaching, Some("max-age=0"), None)] +// #[test_case( +// CachePolicy::NoStoreMustRevalidate, +// Some("no-cache, no-store, must-revalidate, max-age=0"), +// None +// )] +// #[test_case( +// CachePolicy::ForeverInCdnAndBrowser, +// Some("public, max-age=31104000, immutable"), +// None +// )] +// #[test_case(CachePolicy::ForeverInCdn, Some("max-age=0"), Some("max-age=31536000"))] +// #[test_case( +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// Some("stale-while-revalidate=86400"), +// Some("max-age=31536000") +// )] +// fn render_fastly( +// cache: CachePolicy, +// cache_control: Option<&str>, +// surrogate_control: Option<&str>, +// ) -> Result<()> { +// let config = TestEnvironment::base_config().build()?; +// let headers = cache.render(&config); + +// assert_eq!( +// headers.cache_control, +// cache_control.map(|s| HeaderValue::from_str(s).unwrap()) +// ); + +// assert_eq!( +// headers.surrogate_control, +// surrogate_control.map(|s| HeaderValue::from_str(s).unwrap()) +// ); + +// Ok(()) +// } + +// #[test] +// fn render_stale_without_config_fastly() -> Result<()> { +// let config = TestEnvironment::base_config() +// .cache_control_stale_while_revalidate(None) +// .build()?; + +// let headers = CachePolicy::ForeverInCdnAndStaleInBrowser.render(&config); +// assert_eq!(headers, FOREVER_IN_FASTLY_CDN); + +// Ok(()) +// } + +// #[test] +// fn render_stale_with_config_fastly() -> Result<()> { +// let config = TestEnvironment::base_config() +// .cache_control_stale_while_revalidate(Some(666)) +// .build()?; + +// let headers = CachePolicy::ForeverInCdnAndStaleInBrowser.render(&config); +// assert_eq!(headers.cache_control.unwrap(), "stale-while-revalidate=666"); +// assert_eq!( +// headers.surrogate_control, +// FOREVER_IN_FASTLY_CDN.surrogate_control +// ); + +// Ok(()) +// } + +// #[test] +// fn render_forever_in_cdn_disabled_fastly() -> Result<()> { +// let config = TestEnvironment::base_config() +// .cache_invalidatable_responses(false) +// .build()?; + +// let headers = CachePolicy::ForeverInCdn.render(&config); +// assert_eq!(headers.cache_control.unwrap(), "max-age=0"); +// assert!(headers.surrogate_control.is_none()); + +// Ok(()) +// } + +// #[test] +// fn render_forever_in_cdn_or_stale_disabled_fastly() -> Result<()> { +// let config = TestEnvironment::base_config() +// .cache_invalidatable_responses(false) +// .build()?; + +// let headers = CachePolicy::ForeverInCdnAndStaleInBrowser.render(&config); +// assert_eq!(headers.cache_control.unwrap(), "max-age=0"); +// assert!(headers.surrogate_control.is_none()); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_middleware_reacts_to_fastly_header_in_crate_route() -> Result<()> { +// let config = TestEnvironment::base_config() +// .cache_invalidatable_responses(true) +// .build()?; + +// let app = Router::new() +// .route( +// "/{name}", +// get(move || async move { (Extension(CachePolicy::ForeverInCdn), "Hello, World!") }), +// ) +// .layer( +// ServiceBuilder::new() +// .layer(Extension(Arc::new(config))) +// .layer(axum::middleware::from_fn(cache_middleware)), +// ); + +// let builder = Request::builder().uri("/krate"); + +// let response = app +// .clone() +// .oneshot(builder.body(Body::empty()).unwrap()) +// .await?; + +// assert!( +// response.status().is_success(), +// "{}", +// response.text().await.unwrap(), +// ); +// assert_cache_headers_eq(&response, &FOREVER_IN_FASTLY_CDN); + +// Ok(()) +// } + +// #[tokio::test] +// async fn test_middleware_reacts_to_fastly_header_in_other_route() -> Result<()> { +// let config = TestEnvironment::base_config().build()?; + +// let app = Router::new() +// .route( +// "/", +// get(move || async move { +// ( +// Extension(CachePolicy::ForeverInCdnAndBrowser), +// "Hello, World!", +// ) +// }), +// ) +// .layer( +// ServiceBuilder::new() +// .layer(Extension(Arc::new(config))) +// .layer(axum::middleware::from_fn(cache_middleware)), +// ); + +// let builder = Request::builder().uri("/"); + +// let response = app +// .clone() +// .oneshot(builder.body(Body::empty()).unwrap()) +// .await?; + +// assert!( +// response.status().is_success(), +// "{}", +// response.text().await.unwrap(), +// ); + +// // this cache policy leads to the same result in both CDNs +// assert_cache_headers_eq(&response, &FOREVER_IN_CDN_AND_BROWSER); + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_web/src/config.rs b/crates/docs_rs_web/src/config.rs new file mode 100644 index 000000000..6de4b3168 --- /dev/null +++ b/crates/docs_rs_web/src/config.rs @@ -0,0 +1,18 @@ +use docs_rs_env_vars::{env, maybe_env}; +use url::Url; + +#[derive(Debug)] +pub struct Config {} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + // api_host: env( + // "DOCSRS_FASTLY_API_HOST", + // "https://api.fastly.com".parse().unwrap(), + // )?, + // api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, + // service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, + }) + } +} diff --git a/crates/docs_rs_web/src/crate_details.rs b/crates/docs_rs_web/src/crate_details.rs index 8bcb055d6..ed443707a 100644 --- a/crates/docs_rs_web/src/crate_details.rs +++ b/crates/docs_rs_web/src/crate_details.rs @@ -1,19 +1,14 @@ use crate::{ - db::types::{BuildStatus, dependencies::ReleaseDependencyList}, - impl_axum_webpage, - registry_api::OwnerKind, - utils::get_correct_docsrs_style_file, - web::{ - MatchedRelease, MetaData, ReqVersion, - cache::CachePolicy, - error::{AxumNope, AxumResult}, - extractors::{ - DbConnection, - rustdoc::{PageKind, RustdocParams}, - }, - match_version, - page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, + MatchedRelease, MetaData, ReqVersion, + cache::CachePolicy, + error::{AxumNope, AxumResult}, + extractors::{ + DbConnection, + rustdoc::{PageKind, RustdocParams}, }, + impl_axum_webpage, match_version, + page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, + utils::rustc_version::get_correct_docsrs_style_file, }; use anyhow::{Context, Result, anyhow}; use askama::Template; @@ -22,15 +17,16 @@ use axum::{ response::{IntoResponse, Response as AxumResponse}, }; use chrono::{DateTime, Utc}; -use docs_rs_cargo_metadata::Dependency; +use docs_rs_cargo_metadata::{Dependency, db::ReleaseDependencyList}; use docs_rs_database::types::krate_name::KrateName; -use docs_rs_database::types::{BuildId, CrateId, ReleaseId, version::Version}; +use docs_rs_database::types::{BuildId, BuildStatus, CrateId, ReleaseId, version::Version}; use docs_rs_headers::CanonicalUrl; +use docs_rs_registry_api::OwnerKind; use docs_rs_storage::{AsyncStorage, errors::PathNotFoundError}; use futures_util::stream::TryStreamExt; -use log::warn; use serde_json::Value; use std::sync::Arc; +use tracing::warn; // TODO: Add target name and versions #[derive(Debug, Clone, PartialEq)] @@ -309,7 +305,6 @@ impl CrateDetails { Ok(Some(crate_details)) } - #[fn_error_context::context("fetching readme for {} {}", self.name, self.version)] async fn fetch_readme(&self, storage: &AsyncStorage) -> anyhow::Result> { let manifest = match storage .fetch_source_file( @@ -707,1669 +702,1666 @@ pub(crate) async fn get_all_platforms( get_all_platforms_inner(params, conn, false).await } -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{ - AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestDatabase, TestEnvironment, - async_wrapper, fake_release_that_failed_before_build, - }; - use crate::{db::update_build_status, registry_api::CrateOwner}; - use anyhow::Error; - use http::StatusCode; - use kuchikiki::traits::TendrilSink; - use pretty_assertions::assert_eq; - use std::collections::HashMap; - use test_case::test_case; - - async fn release_build_status( - conn: &mut sqlx::PgConnection, - name: &str, - version: &str, - ) -> BuildStatus { - let version: Version = version.parse().expect("invalid version"); - - let status = sqlx::query_scalar!( - r#" - SELECT build_status as "build_status!: BuildStatus" - FROM crates - INNER JOIN releases ON crates.id = releases.crate_id - INNER JOIN release_build_status ON releases.id = release_build_status.rid - WHERE crates.name = $1 AND releases.version = $2"#, - name, - version as _ - ) - .fetch_one(&mut *conn) - .await - .unwrap(); - - assert_eq!( - crate_details(&mut *conn, name, version, None) - .await - .build_status, - status - ); - - status - } - - async fn crate_details( - conn: &mut sqlx::PgConnection, - name: &str, - version: V, - req_version: Option, - ) -> CrateDetails - where - V: TryInto, - V::Error: std::error::Error + Send + Sync + 'static, - { - let version = version.try_into().expect("invalid version"); - - let crate_id = sqlx::query_scalar!( - r#"SELECT id as "id: CrateId" FROM crates WHERE name = $1"#, - name - ) - .fetch_one(&mut *conn) - .await - .unwrap(); - - let releases = releases_for_crate(&mut *conn, crate_id).await.unwrap(); - - CrateDetails::new(&mut *conn, name, &version, req_version, releases) - .await - .unwrap() - .unwrap() - } - - #[fn_error_context::context( - "assert_last_successful_build_equals({package}, {version}, {expected_last_successful_build:?})" - )] - async fn assert_last_successful_build_equals( - db: &TestDatabase, - package: &str, - version: &str, - expected_last_successful_build: Option, - ) -> Result<(), Error> { - let version = version.parse::()?; - let mut conn = db.async_conn().await; - let details = crate_details(&mut conn, package, version, None).await; - - anyhow::ensure!( - details.last_successful_build == expected_last_successful_build, - "didn't expect {:?}", - details.last_successful_build, - ); - - Ok(()) - } - - #[test] - fn test_crate_details_documentation_url_is_none_when_url_is_docs_rs() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .documentation_url(Some("https://foo.com".into())) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.2.0") - .documentation_url(Some("https://docs.rs/foo/".into())) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.3.0") - .documentation_url(None) - .create() - .await?; - - let details_0_1 = crate_details(&mut conn, "foo", "0.1.0", None).await; - let details_0_2 = crate_details(&mut conn, "foo", "0.2.0", None).await; - let details_0_3 = crate_details(&mut conn, "foo", "0.3.0", None).await; - - assert_eq!( - details_0_1.documentation_url, - Some("https://foo.com".into()) - ); - assert_eq!(details_0_2.documentation_url, None); - assert_eq!(details_0_3.documentation_url, None); - - Ok(()) - }); - } - - #[test] - fn test_last_successful_build_when_last_releases_failed_or_yanked() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.4") - .yanked(true) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.5") - .build_result_failed() - .yanked(true) - .create() - .await?; - - assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.2", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.3", Some("0.0.2".parse().unwrap())) - .await?; - assert_last_successful_build_equals(db, "foo", "0.0.4", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.5", Some("0.0.2".parse().unwrap())) - .await?; - Ok(()) - }); - } - - #[test] - fn test_last_successful_build_when_all_releases_failed_or_yanked() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .yanked(true) - .create() - .await?; - - assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.2", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.3", None).await?; - Ok(()) - }); - } - - #[test] - fn test_last_successful_build_with_intermittent_releases_failed_or_yanked() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .yanked(true) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.4") - .create() - .await?; - - assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.2", Some("0.0.4".parse().unwrap())) - .await?; - assert_last_successful_build_equals(db, "foo", "0.0.3", None).await?; - assert_last_successful_build_equals(db, "foo", "0.0.4", None).await?; - Ok(()) - }); - } - - #[test] - fn test_releases_should_be_sorted() { - async_wrapper(|env| async move { - let db = env.async_db(); - - // Add new releases of 'foo' out-of-order since CrateDetails should sort them descending - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.1.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.3.0") - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("1.0.0") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.12.0") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.2.0") - .yanked(true) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.2.0-alpha") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .build_result_failed() - .binary(true) - .create() - .await?; - - let mut conn = db.async_conn().await; - let mut details = crate_details(&mut conn, "foo", "0.2.0", None).await; - for detail in &mut details.releases { - detail.release_time = None; - } - - assert_eq!( - details.releases, - vec![ - Release { - version: Version::parse("1.0.0")?, - build_status: BuildStatus::Success, - yanked: Some(false), - is_library: Some(true), - rustdoc_status: Some(true), - id: details.releases[0].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.12.0")?, - build_status: BuildStatus::Success, - yanked: Some(false), - is_library: Some(true), - rustdoc_status: Some(true), - id: details.releases[1].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.3.0")?, - build_status: BuildStatus::Failure, - yanked: Some(false), - is_library: Some(true), - rustdoc_status: Some(false), - id: details.releases[2].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.2.0")?, - build_status: BuildStatus::Success, - yanked: Some(true), - is_library: Some(true), - rustdoc_status: Some(true), - id: details.releases[3].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.2.0-alpha")?, - build_status: BuildStatus::Success, - yanked: Some(false), - is_library: Some(true), - rustdoc_status: Some(true), - id: details.releases[4].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.1.1")?, - build_status: BuildStatus::Success, - yanked: Some(false), - is_library: Some(true), - rustdoc_status: Some(true), - id: details.releases[5].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.1.0")?, - build_status: BuildStatus::Success, - yanked: Some(false), - is_library: Some(true), - rustdoc_status: Some(true), - id: details.releases[6].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - Release { - version: Version::parse("0.0.1")?, - build_status: BuildStatus::Failure, - yanked: Some(false), - is_library: Some(false), - rustdoc_status: Some(false), - id: details.releases[7].id, - target_name: Some("foo".to_owned()), - release_time: None, - default_target: Some("x86_64-unknown-linux-gnu".into()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), - }, - ] - ); - - Ok(()) - }); - } - - #[test] - fn test_canonical_url() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .create() - .await?; - - let response = env.web_app().await.get("/crate/foo/0.0.1").await?; - response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); - - assert!( - response - .text() - .await? - .contains("rel=\"canonical\" href=\"https://docs.rs/crate/foo/latest") - ); - - Ok(()) - }) - } - - #[test] - fn test_latest_version() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .create() - .await?; - - let mut conn = db.async_conn().await; - for version in &["0.0.1", "0.0.2", "0.0.3"] { - let details = crate_details(&mut conn, "foo", *version, None).await; - assert_eq!( - details.latest_release().unwrap().version, - Version::parse("0.0.3")? - ); - } - - Ok(()) - }) - } - - #[test] - fn test_latest_version_ignores_prerelease() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3-pre.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .create() - .await?; - - let mut conn = db.async_conn().await; - for &version in &["0.0.1", "0.0.2", "0.0.3-pre.1"] { - let details = crate_details(&mut conn, "foo", version, None).await; - assert_eq!( - details.latest_release().unwrap().version, - Version::parse("0.0.2")? - ); - } - - Ok(()) - }) - } - - #[test] - fn test_latest_version_ignores_yanked() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .yanked(true) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .create() - .await?; - - let mut conn = db.async_conn().await; - for &version in &["0.0.1", "0.0.2", "0.0.3"] { - let details = crate_details(&mut conn, "foo", version, None).await; - assert_eq!( - details.latest_release().unwrap().version, - Version::parse("0.0.2")? - ); - } - - Ok(()) - }) - } - - #[test] - fn test_latest_version_only_yanked() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .yanked(true) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .yanked(true) - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .yanked(true) - .create() - .await?; - - let mut conn = db.async_conn().await; - for &version in &["0.0.1", "0.0.2", "0.0.3"] { - let details = crate_details(&mut conn, "foo", version, None).await; - assert_eq!( - details.latest_release().unwrap().version, - Version::parse("0.0.3")? - ); - } - - Ok(()) - }) - } - - #[test] - fn test_latest_version_in_progress() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .create() - .await?; - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .builds(vec![ - FakeBuild::default().build_status(BuildStatus::InProgress), - ]) - .create() - .await?; - - let mut conn = db.async_conn().await; - for &version in &["0.0.1", "0.0.2"] { - let details = crate_details(&mut conn, "foo", version, None).await; - assert_eq!( - details.latest_release().unwrap().version, - Version::parse("0.0.1")? - ); - } - - Ok(()) - }) - } - - #[test] - fn releases_dropdowns_show_binary_warning() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("binary") - .version("0.1.0") - .binary(true) - .create() - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/binary/latest") - .await? - .text() - .await?, - ); - let link = page - .select_first("a.pure-menu-link[href='/crate/binary/0.1.0']") - .unwrap(); - - assert_eq!( - link.as_node() - .as_element() - .unwrap() - .attributes - .borrow() - .get("title") - .unwrap(), - "binary-0.1.0 is not a library" - ); - - Ok(()) - }); - } - - #[test] - fn releases_dropdowns_show_in_progress() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .builds(vec![ - FakeBuild::default().build_status(BuildStatus::InProgress), - ]) - .create() - .await?; - - let response = env.web_app().await.get("/crate/foo/latest").await?; - - let page = kuchikiki::parse_html().one(response.text().await?); - let link = page - .select_first("a.pure-menu-link[href='/crate/foo/0.1.0']") - .unwrap(); - - assert_eq!( - link.as_node() - .as_element() - .unwrap() - .attributes - .borrow() - .get("title") - .unwrap(), - "foo-0.1.0 is currently being built" - ); - - Ok(()) - }); - } - - #[test] - fn test_updating_owners() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .add_owner(CrateOwner { - login: "foobar".into(), - avatar: "https://example.org/foobar".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - - let mut conn = db.async_conn().await; - let details = crate_details(&mut conn, "foo", "0.0.1", None).await; - assert_eq!( - details.owners, - vec![( - "foobar".into(), - "https://example.org/foobar".into(), - OwnerKind::User - )] - ); - - // Adding a new owner, and changing details on an existing owner - env.fake_release() - .await - .name("foo") - .version("0.0.2") - .add_owner(CrateOwner { - login: "foobar".into(), - avatar: "https://example.org/foobarv2".into(), - kind: OwnerKind::User, - }) - .add_owner(CrateOwner { - login: "barfoo".into(), - avatar: "https://example.org/barfoo".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - - let details = crate_details(&mut conn, "foo", "0.0.1", None).await; - let mut owners = details.owners; - owners.sort(); - assert_eq!( - owners, - vec![ - ( - "barfoo".into(), - "https://example.org/barfoo".into(), - OwnerKind::User - ), - ( - "foobar".into(), - "https://example.org/foobarv2".into(), - OwnerKind::User - ) - ] - ); - - // Removing an existing owner - env.fake_release() - .await - .name("foo") - .version("0.0.3") - .add_owner(CrateOwner { - login: "barfoo".into(), - avatar: "https://example.org/barfoo".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - - let mut conn = db.async_conn().await; - let details = crate_details(&mut conn, "foo", "0.0.1", None).await; - assert_eq!( - details.owners, - vec![( - "barfoo".into(), - "https://example.org/barfoo".into(), - OwnerKind::User - )] - ); - - // Changing owner details on another of their crates applies the change to both - env.fake_release() - .await - .name("bar") - .version("0.0.1") - .add_owner(CrateOwner { - login: "barfoo".into(), - avatar: "https://example.org/barfoov2".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - - let mut conn = db.async_conn().await; - let details = crate_details(&mut conn, "foo", "0.0.1", None).await; - assert_eq!( - details.owners, - vec![( - "barfoo".into(), - "https://example.org/barfoov2".into(), - OwnerKind::User - )] - ); - - Ok(()) - }); - } - - #[test] - fn feature_flags_report_empty() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("library") - .version("0.1.0") - .features(HashMap::new()) - .create() - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/library/0.1.0/features") - .await? - .text() - .await?, - ); - assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_ok()); - Ok(()) - }); - } - - #[test] - fn feature_private_feature_flags_are_hidden() { - async_wrapper(|env| async move { - let features = [("_private".into(), Vec::new())] - .iter() - .cloned() - .collect::>>(); - env.fake_release() - .await - .name("library") - .version("0.1.0") - .features(features) - .create() - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/library/0.1.0/features") - .await? - .text() - .await?, - ); - assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_ok()); - Ok(()) - }); - } - - #[test] - fn feature_flags_without_default() { - async_wrapper(|env| async move { - let features = [("feature1".into(), Vec::new())] - .iter() - .cloned() - .collect::>>(); - env.fake_release() - .await - .name("library") - .version("0.1.0") - .features(features) - .create() - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/library/0.1.0/features") - .await? - .text() - .await?, - ); - assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err()); - let def_len = page - .select_first(r#"b[data-id="default-feature-len"]"#) - .unwrap(); - assert_eq!(def_len.text_contents(), "0"); - Ok(()) - }); - } - - #[test] - fn feature_flags_with_nested_default() { - async_wrapper(|env| async move { - let features = [ - ("default".into(), vec!["feature1".into()]), - ("feature1".into(), vec!["feature2".into()]), - ("feature2".into(), Vec::new()), - ] - .iter() - .cloned() - .collect::>>(); - env.fake_release() - .await - .name("library") - .version("0.1.0") - .features(features) - .create() - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/library/0.1.0/features") - .await? - .text() - .await?, - ); - assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err()); - let def_len = page - .select_first(r#"b[data-id="default-feature-len"]"#) - .unwrap(); - assert_eq!(def_len.text_contents(), "2"); - Ok(()) - }); - } - - #[test] - fn details_with_repository_and_stats_can_render_icon() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("library") - .version("0.1.0") - .repo("https://github.com/org/repo") - .github_stats("org/repo", 10, 10, 10) - .create() - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .assert_success("/crate/library/0.1.0") - .await? - .text() - .await?, - ); - - let link = page - .select_first("a.pure-menu-link[href='https://github.com/org/repo']") - .unwrap(); - - let icon_node = link.as_node().children().nth(1).unwrap(); - assert_eq!( - icon_node - .as_element() - .unwrap() - .attributes - .borrow() - .get("class") - .unwrap(), - "fa fa-solid fa-code-branch " - ); - - Ok(()) - }); - } - - #[test] - fn feature_flags_report_null() { - async_wrapper(|env| async move { - let id = env - .fake_release() - .await - .name("library") - .version("0.1.0") - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - sqlx::query!("UPDATE releases SET features = NULL WHERE id = $1", id.0) - .execute(&mut *conn) - .await?; - - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/library/0.1.0/features") - .await? - .text() - .await?, - ); - assert!(page.select_first(r#"p[data-id="null-features"]"#).is_ok()); - Ok(()) - }); - } - - #[test] - fn test_minimal_failed_release_doesnt_error_features() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - fake_release_that_failed_before_build(&mut conn, "foo", "0.1.0", "some errors").await?; - - let text_content = env - .web_app() - .await - .get("/crate/foo/0.1.0/features") - .await? - .error_for_status()? - .text() - .await?; - - assert!(text_content.contains( - "Feature flags are not available for this release because \ - the build failed before we could retrieve them" - )); - - Ok(()) - }); - } - - #[test] - fn test_minimal_failed_release_doesnt_error() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - fake_release_that_failed_before_build(&mut conn, "foo", "0.1.0", "some errors").await?; - - let text_content = env - .web_app() - .await - .get("/crate/foo/0.1.0") - .await? - .error_for_status()? - .text() - .await?; - - assert!(text_content.contains("docs.rs failed to build foo")); - - Ok(()) - }); - } - - #[test] - fn platform_links_are_direct_and_without_nofollow() { - fn check_links( - response_text: String, - ajax: bool, - should_contain_redirect: bool, - ) -> Vec<(String, String, String)> { - let platform_links: Vec<(String, String, String)> = kuchikiki::parse_html() - .one(response_text) - .select(&format!(r#"{}li a"#, if ajax { "" } else { "#platforms " })) - .expect("invalid selector") - .map(|el| { - let attributes = el.attributes.borrow(); - let url = attributes.get("href").expect("href").to_string(); - let rel = attributes.get("rel").unwrap_or("").to_string(); - (el.text_contents(), url, rel) - }) - .collect(); - - dbg!(&platform_links); - - assert_eq!(platform_links.len(), 2); - - for (_, url, rel) in &platform_links { - assert_eq!( - url.contains("/target-redirect/"), - should_contain_redirect, - "url: {url:?}, ajax: {ajax:?}, should_contain_redirect: {should_contain_redirect:?}", - ); - if !should_contain_redirect { - assert_eq!(rel, ""); - } else { - assert_eq!(rel, "nofollow"); - } - } - platform_links - } - - async fn run_check_links_redir( - env: &TestEnvironment, - url: &str, - should_contain_redirect: bool, - ) { - let response = env.web_app().await.get(dbg!(url)).await.unwrap(); - let status = response.status(); - assert!( - status.is_success(), - "no success, status: {}, url: {}, target: {}", - status, - url, - response.redirect_target().unwrap_or_default(), - ); - let text = response.text().await.unwrap(); - let list1 = dbg!(check_links( - text.clone(), - false, - dbg!(should_contain_redirect) - )); - - // Same test with AJAX endpoint. - let platform_menu_url = kuchikiki::parse_html() - .one(text) - .select_first("#platforms") - .expect("invalid selector") - .attributes - .borrow() - .get("data-url") - .expect("data-url") - .to_string(); - let response = env - .web_app() - .await - .get(&dbg!(platform_menu_url)) - .await - .unwrap(); - assert!( - response.status().is_success(), - "{}", - response.text().await.unwrap() - ); - response.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - let list2 = dbg!(check_links( - response.text().await.unwrap(), - true, - should_contain_redirect, - )); - assert_eq!(list1, list2); - } - - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.4.0") - .rustdoc_file("dummy/index.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/struct.A.html") - .default_target("x86_64-unknown-linux-gnu") - .add_target("x86_64-pc-windows-msvc") - .source_file("README.md", b"storage readme") - .create() - .await?; - - run_check_links_redir(&env, "/crate/dummy/0.4.0/features", false).await; - run_check_links_redir(&env, "/crate/dummy/0.4.0/builds", false).await; - run_check_links_redir(&env, "/crate/dummy/0.4.0/source/", false).await; - run_check_links_redir(&env, "/crate/dummy/0.4.0/source/README.md", false).await; - run_check_links_redir(&env, "/crate/dummy/0.4.0", false).await; - - run_check_links_redir(&env, "/dummy/latest/dummy/", true).await; - run_check_links_redir(&env, "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", true).await; - run_check_links_redir( - &env, - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.A.html", - true, - ) - .await; - - Ok(()) - }); - } - - #[test] - fn check_crate_name_in_redirect() { - async fn check_links(env: &TestEnvironment, url: &str, links: Vec) { - let response = env.web_app().await.get(url).await.unwrap(); - assert!(response.status().is_success()); - - let platform_links: Vec = kuchikiki::parse_html() - .one(response.text().await.unwrap()) - .select("li a") - .expect("invalid selector") - .map(|el| { - let attributes = el.attributes.borrow(); - attributes.get("href").expect("href").to_string() - }) - .collect(); - - assert_eq!(platform_links, links,); - } - - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy-ba") - .version("0.4.0") - .rustdoc_file("dummy-ba/index.html") - .rustdoc_file("x86_64-unknown-linux-gnu/dummy-ba/index.html") - .add_target("x86_64-unknown-linux-gnu") - .default_target("aarch64-apple-darwin") - .create() - .await?; - env.fake_release() - .await - .name("dummy-ba") - .version("0.5.0") - .rustdoc_file("dummy-ba/index.html") - .rustdoc_file("x86_64-unknown-linux-gnu/dummy-ba/index.html") - .add_target("x86_64-unknown-linux-gnu") - .default_target("aarch64-apple-darwin") - .create() - .await?; - - check_links( - // https://github.com/rust-lang/docs.rs/issues/2922 - &env, - "/crate/dummy-ba/0.5.0/menus/releases/x86_64-unknown-linux-gnu/src/dummy_ba/de.rs.html", - vec![ - "/crate/dummy-ba/0.5.0/target-redirect/x86_64-unknown-linux-gnu/src/dummy_ba/de.rs.html".to_string(), - "/crate/dummy-ba/0.4.0/target-redirect/x86_64-unknown-linux-gnu/src/dummy_ba/de.rs.html".to_string(), - ], - ) - .await; - - check_links( - &env, - "/crate/dummy-ba/latest/menus/releases/dummy_ba/index.html", - vec![ - "/crate/dummy-ba/0.5.0/target-redirect/dummy_ba/".to_string(), - "/crate/dummy-ba/0.4.0/target-redirect/dummy_ba/".to_string(), - ], - ) - .await; - - check_links( - &env, - "/crate/dummy-ba/latest/menus/releases/x86_64-unknown-linux-gnu/dummy_ba/index.html", - vec![ - "/crate/dummy-ba/0.5.0/target-redirect/x86_64-unknown-linux-gnu/dummy_ba/".to_string(), - "/crate/dummy-ba/0.4.0/target-redirect/x86_64-unknown-linux-gnu/dummy_ba/".to_string(), - ], - ).await; - - Ok(()) - }); - } - - // Ensure that if there are more than a given number of targets, it will not generate them in - // the HTML directly (they will be loaded by AJAX if the user opens the menu). - #[test] - #[allow(clippy::assertions_on_constants)] - fn platform_menu_ajax() { - assert!(crate::DEFAULT_MAX_TARGETS > 2); - - fn check_count(nb_targets: usize, expected: usize) { - async_wrapper(|env| async move { - let mut rel = env - .fake_release() - .await - .name("dummy") - .version("0.4.0") - .rustdoc_file("dummy/index.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") - .default_target("x86_64-unknown-linux-gnu"); - - for nb in 0..nb_targets - 1 { - rel = rel.add_target(&format!("x86_64-pc-windows-msvc{nb}")); - } - rel.create().await?; - - let response = env.web_app().await.get("/crate/dummy/0.4.0").await?; - assert!(response.status().is_success()); - - let nb_li = kuchikiki::parse_html() - .one(response.text().await?) - .select(r#"#platforms li a"#) - .expect("invalid selector") - .count(); - assert_eq!(nb_li, expected); - Ok(()) - }); - } - - // First we check that with 2 releases, the platforms list should be in the HTML. - check_count(2, 2); - // Then we check the same thing but with number of targets equal - // to `DEFAULT_MAX_TARGETS`. - check_count(crate::DEFAULT_MAX_TARGETS, 0); - } - - #[test] - fn latest_url() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.4.0") - .rustdoc_file("dummy/index.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") - .default_target("x86_64-unknown-linux-gnu") - .add_target("x86_64-pc-windows-msvc") - .create() - .await?; - let web = env.web_app().await; - - let resp = web.get("/crate/dummy/latest").await?; - assert!(resp.status().is_success()); - resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - let body = resp.text().await?; - assert!(body.contains(" anyhow::Result<()> { - let env = TestEnvironment::new().await?; - env.fake_release() - .await - .name("rayon") - .version("1.11.0") - .create() - .await?; - let web = env.web_app().await; - - web.assert_redirect(path, expected_target).await?; - - Ok(()) - } - - #[test] - fn readme() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .readme_only_database("database readme") - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .readme_only_database("database readme") - .source_file("README.md", b"storage readme") - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.3.0") - .source_file("README.md", b"storage readme") - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.4.0") - .readme_only_database("database readme") - .source_file("MEREAD", b"storage meread") - .source_file("Cargo.toml", br#"package.readme = "MEREAD""#) - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.5.0") - .readme_only_database("database readme") - .source_file("README.md", b"storage readme") - .no_cargo_toml() - .create() - .await?; - - let check_readme = |path: String, content: String| { - let env = env.clone(); - async move { - let resp = env.web_app().await.get(&path).await.unwrap(); - let body = resp.text().await.unwrap(); - assert!(body.contains(&content)); - } - }; - - check_readme("/crate/dummy/0.1.0".into(), "database readme".into()).await; - check_readme("/crate/dummy/0.2.0".into(), "storage readme".into()).await; - check_readme("/crate/dummy/0.3.0".into(), "storage readme".into()).await; - check_readme("/crate/dummy/0.4.0".into(), "storage meread".into()).await; - - let mut conn = env.async_db().async_conn().await; - let details = crate_details(&mut conn, "dummy", "0.5.0", None).await; - assert!(matches!( - details.fetch_readme(env.async_storage()).await, - Ok(None) - )); - Ok(()) - }); - } - - #[test] - fn no_readme() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .source_file( - "Cargo.toml", - br#"[package] -name = "dummy" -version = "0.2.0" - -[lib] -name = "dummy" -path = "src/lib.rs" -"#, - ) - .source_file( - "src/lib.rs", - b"//! # Crate-level docs -//! -//! ``` -//! let x = 21; -//! ``` -", - ) - .target_source("src/lib.rs") - .create() - .await?; - - let web = env.web_app().await; - let response = web.get("/crate/dummy/0.2.0").await?; - assert!(response.status().is_success()); - - let dom = kuchikiki::parse_html().one(response.text().await?); - dom.select_first("#main").expect("not main crate docs"); - // First we check that the crate-level docs have been rendered as expected. - assert_eq!( - dom.select_first("#main h1") - .expect("no h1 found") - .text_contents(), - "Crate-level docs" - ); - // Then we check that by default, the language used for highlighting is rust. - assert_eq!( - dom.select_first("#main pre .syntax-source.syntax-rust") - .expect("no rust code block found") - .text_contents(), - "let x = 21;\n" - ); - Ok(()) - }); - } - - #[test] - fn test_crate_name_with_other_uri_chars() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .create() - .await?; - - let resp = env.web_app().await.get("/crate/dummy%3E").await?; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - Ok(()) - }) - } - - #[test_case("/crate/dummy"; "without")] - #[test_case("/crate/dummy/"; "slash")] - fn test_unknown_crate_not_found_doesnt_redirect(path: &str) { - async_wrapper(|env| async move { - let resp = env.web_app().await.get(path).await?; - assert_eq!(resp.status(), StatusCode::NOT_FOUND); - - Ok(()) - }) - } - - #[test] - fn test_build_status_no_builds() { - async_wrapper(|env| async move { - let release_id = env - .fake_release() - .await - .name("dummy") - .version("0.1.0") - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - sqlx::query!("DELETE FROM builds") - .execute(&mut *conn) - .await?; - - update_build_status(&mut conn, release_id).await?; - - assert_eq!( - release_build_status(&mut conn, "dummy", "0.1.0").await, - BuildStatus::InProgress - ); - - Ok(()) - }) - } - - #[test] - fn test_build_status_successful() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .builds(vec![ - FakeBuild::default().build_status(BuildStatus::Success), - FakeBuild::default().build_status(BuildStatus::Failure), - FakeBuild::default().build_status(BuildStatus::InProgress), - ]) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - - assert_eq!( - release_build_status(&mut conn, "dummy", "0.1.0").await, - BuildStatus::Success - ); - - Ok(()) - }) - } - - #[test] - fn test_build_status_failed() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .builds(vec![ - FakeBuild::default().build_status(BuildStatus::Failure), - FakeBuild::default().build_status(BuildStatus::InProgress), - ]) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - - assert_eq!( - release_build_status(&mut conn, "dummy", "0.1.0").await, - BuildStatus::Failure - ); - - Ok(()) - }) - } - - #[test] - fn test_build_status_in_progress() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .builds(vec![ - FakeBuild::default().build_status(BuildStatus::InProgress), - ]) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - - assert_eq!( - release_build_status(&mut conn, "dummy", "0.1.0").await, - BuildStatus::InProgress - ); - - Ok(()) - }) - } - - #[test] - fn test_sizes_display() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.4.0") - .rustdoc_file("dummy/index.html") - .create() - .await?; - - let web = env.web_app().await; - let response = web.get("/crate/dummy/0.4.0").await?; - assert!(response.status().is_success()); - - let mut has_source_code_size = false; - let mut has_doc_size = false; - for span in kuchikiki::parse_html() - .one(response.text().await?) - .select(r#".pure-menu-item span.documented-info"#) - .expect("invalid selector") - { - if span.text_contents().starts_with("Source code size:") { - has_source_code_size = true; - } else if span.text_contents().starts_with("Documentation size:") { - has_doc_size = true; - } - } - assert!(has_source_code_size); - assert!(has_doc_size); - Ok(()) - }); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::{ +// AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestDatabase, TestEnvironment, +// async_wrapper, fake_release_that_failed_before_build, +// }; +// use crate::{db::update_build_status, registry_api::CrateOwner}; +// use anyhow::Error; +// use http::StatusCode; +// use kuchikiki::traits::TendrilSink; +// use pretty_assertions::assert_eq; +// use std::collections::HashMap; +// use test_case::test_case; + +// async fn release_build_status( +// conn: &mut sqlx::PgConnection, +// name: &str, +// version: &str, +// ) -> BuildStatus { +// let version: Version = version.parse().expect("invalid version"); + +// let status = sqlx::query_scalar!( +// r#" +// SELECT build_status as "build_status!: BuildStatus" +// FROM crates +// INNER JOIN releases ON crates.id = releases.crate_id +// INNER JOIN release_build_status ON releases.id = release_build_status.rid +// WHERE crates.name = $1 AND releases.version = $2"#, +// name, +// version as _ +// ) +// .fetch_one(&mut *conn) +// .await +// .unwrap(); + +// assert_eq!( +// crate_details(&mut *conn, name, version, None) +// .await +// .build_status, +// status +// ); + +// status +// } + +// async fn crate_details( +// conn: &mut sqlx::PgConnection, +// name: &str, +// version: V, +// req_version: Option, +// ) -> CrateDetails +// where +// V: TryInto, +// V::Error: std::error::Error + Send + Sync + 'static, +// { +// let version = version.try_into().expect("invalid version"); + +// let crate_id = sqlx::query_scalar!( +// r#"SELECT id as "id: CrateId" FROM crates WHERE name = $1"#, +// name +// ) +// .fetch_one(&mut *conn) +// .await +// .unwrap(); + +// let releases = releases_for_crate(&mut *conn, crate_id).await.unwrap(); + +// CrateDetails::new(&mut *conn, name, &version, req_version, releases) +// .await +// .unwrap() +// .unwrap() +// } + +// async fn assert_last_successful_build_equals( +// db: &TestDatabase, +// package: &str, +// version: &str, +// expected_last_successful_build: Option, +// ) -> Result<(), Error> { +// let version = version.parse::()?; +// let mut conn = db.async_conn().await; +// let details = crate_details(&mut conn, package, version, None).await; + +// anyhow::ensure!( +// details.last_successful_build == expected_last_successful_build, +// "didn't expect {:?}", +// details.last_successful_build, +// ); + +// Ok(()) +// } + +// #[test] +// fn test_crate_details_documentation_url_is_none_when_url_is_docs_rs() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .documentation_url(Some("https://foo.com".into())) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.0") +// .documentation_url(Some("https://docs.rs/foo/".into())) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.3.0") +// .documentation_url(None) +// .create() +// .await?; + +// let details_0_1 = crate_details(&mut conn, "foo", "0.1.0", None).await; +// let details_0_2 = crate_details(&mut conn, "foo", "0.2.0", None).await; +// let details_0_3 = crate_details(&mut conn, "foo", "0.3.0", None).await; + +// assert_eq!( +// details_0_1.documentation_url, +// Some("https://foo.com".into()) +// ); +// assert_eq!(details_0_2.documentation_url, None); +// assert_eq!(details_0_3.documentation_url, None); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_last_successful_build_when_last_releases_failed_or_yanked() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.4") +// .yanked(true) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.5") +// .build_result_failed() +// .yanked(true) +// .create() +// .await?; + +// assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.2", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.3", Some("0.0.2".parse().unwrap())) +// .await?; +// assert_last_successful_build_equals(db, "foo", "0.0.4", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.5", Some("0.0.2".parse().unwrap())) +// .await?; +// Ok(()) +// }); +// } + +// #[test] +// fn test_last_successful_build_when_all_releases_failed_or_yanked() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .yanked(true) +// .create() +// .await?; + +// assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.2", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.3", None).await?; +// Ok(()) +// }); +// } + +// #[test] +// fn test_last_successful_build_with_intermittent_releases_failed_or_yanked() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .yanked(true) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.4") +// .create() +// .await?; + +// assert_last_successful_build_equals(db, "foo", "0.0.1", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.2", Some("0.0.4".parse().unwrap())) +// .await?; +// assert_last_successful_build_equals(db, "foo", "0.0.3", None).await?; +// assert_last_successful_build_equals(db, "foo", "0.0.4", None).await?; +// Ok(()) +// }); +// } + +// #[test] +// fn test_releases_should_be_sorted() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// // Add new releases of 'foo' out-of-order since CrateDetails should sort them descending +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.3.0") +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("1.0.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.12.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.0") +// .yanked(true) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.0-alpha") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .build_result_failed() +// .binary(true) +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// let mut details = crate_details(&mut conn, "foo", "0.2.0", None).await; +// for detail in &mut details.releases { +// detail.release_time = None; +// } + +// assert_eq!( +// details.releases, +// vec![ +// Release { +// version: Version::parse("1.0.0")?, +// build_status: BuildStatus::Success, +// yanked: Some(false), +// is_library: Some(true), +// rustdoc_status: Some(true), +// id: details.releases[0].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.12.0")?, +// build_status: BuildStatus::Success, +// yanked: Some(false), +// is_library: Some(true), +// rustdoc_status: Some(true), +// id: details.releases[1].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.3.0")?, +// build_status: BuildStatus::Failure, +// yanked: Some(false), +// is_library: Some(true), +// rustdoc_status: Some(false), +// id: details.releases[2].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.2.0")?, +// build_status: BuildStatus::Success, +// yanked: Some(true), +// is_library: Some(true), +// rustdoc_status: Some(true), +// id: details.releases[3].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.2.0-alpha")?, +// build_status: BuildStatus::Success, +// yanked: Some(false), +// is_library: Some(true), +// rustdoc_status: Some(true), +// id: details.releases[4].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.1.1")?, +// build_status: BuildStatus::Success, +// yanked: Some(false), +// is_library: Some(true), +// rustdoc_status: Some(true), +// id: details.releases[5].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.1.0")?, +// build_status: BuildStatus::Success, +// yanked: Some(false), +// is_library: Some(true), +// rustdoc_status: Some(true), +// id: details.releases[6].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// Release { +// version: Version::parse("0.0.1")?, +// build_status: BuildStatus::Failure, +// yanked: Some(false), +// is_library: Some(false), +// rustdoc_status: Some(false), +// id: details.releases[7].id, +// target_name: Some("foo".to_owned()), +// release_time: None, +// default_target: Some("x86_64-unknown-linux-gnu".into()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".into()]), +// }, +// ] +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_canonical_url() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .create() +// .await?; + +// let response = env.web_app().await.get("/crate/foo/0.0.1").await?; +// response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); + +// assert!( +// response +// .text() +// .await? +// .contains("rel=\"canonical\" href=\"https://docs.rs/crate/foo/latest") +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_latest_version() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// for version in &["0.0.1", "0.0.2", "0.0.3"] { +// let details = crate_details(&mut conn, "foo", *version, None).await; +// assert_eq!( +// details.latest_release().unwrap().version, +// Version::parse("0.0.3")? +// ); +// } + +// Ok(()) +// }) +// } + +// #[test] +// fn test_latest_version_ignores_prerelease() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3-pre.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// for &version in &["0.0.1", "0.0.2", "0.0.3-pre.1"] { +// let details = crate_details(&mut conn, "foo", version, None).await; +// assert_eq!( +// details.latest_release().unwrap().version, +// Version::parse("0.0.2")? +// ); +// } + +// Ok(()) +// }) +// } + +// #[test] +// fn test_latest_version_ignores_yanked() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .yanked(true) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// for &version in &["0.0.1", "0.0.2", "0.0.3"] { +// let details = crate_details(&mut conn, "foo", version, None).await; +// assert_eq!( +// details.latest_release().unwrap().version, +// Version::parse("0.0.2")? +// ); +// } + +// Ok(()) +// }) +// } + +// #[test] +// fn test_latest_version_only_yanked() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .yanked(true) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .yanked(true) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .yanked(true) +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// for &version in &["0.0.1", "0.0.2", "0.0.3"] { +// let details = crate_details(&mut conn, "foo", version, None).await; +// assert_eq!( +// details.latest_release().unwrap().version, +// Version::parse("0.0.3")? +// ); +// } + +// Ok(()) +// }) +// } + +// #[test] +// fn test_latest_version_in_progress() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .builds(vec![ +// FakeBuild::default().build_status(BuildStatus::InProgress), +// ]) +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// for &version in &["0.0.1", "0.0.2"] { +// let details = crate_details(&mut conn, "foo", version, None).await; +// assert_eq!( +// details.latest_release().unwrap().version, +// Version::parse("0.0.1")? +// ); +// } + +// Ok(()) +// }) +// } + +// #[test] +// fn releases_dropdowns_show_binary_warning() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("binary") +// .version("0.1.0") +// .binary(true) +// .create() +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/binary/latest") +// .await? +// .text() +// .await?, +// ); +// let link = page +// .select_first("a.pure-menu-link[href='/crate/binary/0.1.0']") +// .unwrap(); + +// assert_eq!( +// link.as_node() +// .as_element() +// .unwrap() +// .attributes +// .borrow() +// .get("title") +// .unwrap(), +// "binary-0.1.0 is not a library" +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn releases_dropdowns_show_in_progress() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default().build_status(BuildStatus::InProgress), +// ]) +// .create() +// .await?; + +// let response = env.web_app().await.get("/crate/foo/latest").await?; + +// let page = kuchikiki::parse_html().one(response.text().await?); +// let link = page +// .select_first("a.pure-menu-link[href='/crate/foo/0.1.0']") +// .unwrap(); + +// assert_eq!( +// link.as_node() +// .as_element() +// .unwrap() +// .attributes +// .borrow() +// .get("title") +// .unwrap(), +// "foo-0.1.0 is currently being built" +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_updating_owners() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .add_owner(CrateOwner { +// login: "foobar".into(), +// avatar: "https://example.org/foobar".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// let details = crate_details(&mut conn, "foo", "0.0.1", None).await; +// assert_eq!( +// details.owners, +// vec![( +// "foobar".into(), +// "https://example.org/foobar".into(), +// OwnerKind::User +// )] +// ); + +// // Adding a new owner, and changing details on an existing owner +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.2") +// .add_owner(CrateOwner { +// login: "foobar".into(), +// avatar: "https://example.org/foobarv2".into(), +// kind: OwnerKind::User, +// }) +// .add_owner(CrateOwner { +// login: "barfoo".into(), +// avatar: "https://example.org/barfoo".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; + +// let details = crate_details(&mut conn, "foo", "0.0.1", None).await; +// let mut owners = details.owners; +// owners.sort(); +// assert_eq!( +// owners, +// vec![ +// ( +// "barfoo".into(), +// "https://example.org/barfoo".into(), +// OwnerKind::User +// ), +// ( +// "foobar".into(), +// "https://example.org/foobarv2".into(), +// OwnerKind::User +// ) +// ] +// ); + +// // Removing an existing owner +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.3") +// .add_owner(CrateOwner { +// login: "barfoo".into(), +// avatar: "https://example.org/barfoo".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// let details = crate_details(&mut conn, "foo", "0.0.1", None).await; +// assert_eq!( +// details.owners, +// vec![( +// "barfoo".into(), +// "https://example.org/barfoo".into(), +// OwnerKind::User +// )] +// ); + +// // Changing owner details on another of their crates applies the change to both +// env.fake_release() +// .await +// .name("bar") +// .version("0.0.1") +// .add_owner(CrateOwner { +// login: "barfoo".into(), +// avatar: "https://example.org/barfoov2".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; + +// let mut conn = db.async_conn().await; +// let details = crate_details(&mut conn, "foo", "0.0.1", None).await; +// assert_eq!( +// details.owners, +// vec![( +// "barfoo".into(), +// "https://example.org/barfoov2".into(), +// OwnerKind::User +// )] +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn feature_flags_report_empty() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("library") +// .version("0.1.0") +// .features(HashMap::new()) +// .create() +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/library/0.1.0/features") +// .await? +// .text() +// .await?, +// ); +// assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_ok()); +// Ok(()) +// }); +// } + +// #[test] +// fn feature_private_feature_flags_are_hidden() { +// async_wrapper(|env| async move { +// let features = [("_private".into(), Vec::new())] +// .iter() +// .cloned() +// .collect::>>(); +// env.fake_release() +// .await +// .name("library") +// .version("0.1.0") +// .features(features) +// .create() +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/library/0.1.0/features") +// .await? +// .text() +// .await?, +// ); +// assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_ok()); +// Ok(()) +// }); +// } + +// #[test] +// fn feature_flags_without_default() { +// async_wrapper(|env| async move { +// let features = [("feature1".into(), Vec::new())] +// .iter() +// .cloned() +// .collect::>>(); +// env.fake_release() +// .await +// .name("library") +// .version("0.1.0") +// .features(features) +// .create() +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/library/0.1.0/features") +// .await? +// .text() +// .await?, +// ); +// assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err()); +// let def_len = page +// .select_first(r#"b[data-id="default-feature-len"]"#) +// .unwrap(); +// assert_eq!(def_len.text_contents(), "0"); +// Ok(()) +// }); +// } + +// #[test] +// fn feature_flags_with_nested_default() { +// async_wrapper(|env| async move { +// let features = [ +// ("default".into(), vec!["feature1".into()]), +// ("feature1".into(), vec!["feature2".into()]), +// ("feature2".into(), Vec::new()), +// ] +// .iter() +// .cloned() +// .collect::>>(); +// env.fake_release() +// .await +// .name("library") +// .version("0.1.0") +// .features(features) +// .create() +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/library/0.1.0/features") +// .await? +// .text() +// .await?, +// ); +// assert!(page.select_first(r#"p[data-id="empty-features"]"#).is_err()); +// let def_len = page +// .select_first(r#"b[data-id="default-feature-len"]"#) +// .unwrap(); +// assert_eq!(def_len.text_contents(), "2"); +// Ok(()) +// }); +// } + +// #[test] +// fn details_with_repository_and_stats_can_render_icon() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("library") +// .version("0.1.0") +// .repo("https://github.com/org/repo") +// .github_stats("org/repo", 10, 10, 10) +// .create() +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .assert_success("/crate/library/0.1.0") +// .await? +// .text() +// .await?, +// ); + +// let link = page +// .select_first("a.pure-menu-link[href='https://github.com/org/repo']") +// .unwrap(); + +// let icon_node = link.as_node().children().nth(1).unwrap(); +// assert_eq!( +// icon_node +// .as_element() +// .unwrap() +// .attributes +// .borrow() +// .get("class") +// .unwrap(), +// "fa fa-solid fa-code-branch " +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn feature_flags_report_null() { +// async_wrapper(|env| async move { +// let id = env +// .fake_release() +// .await +// .name("library") +// .version("0.1.0") +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!("UPDATE releases SET features = NULL WHERE id = $1", id.0) +// .execute(&mut *conn) +// .await?; + +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/library/0.1.0/features") +// .await? +// .text() +// .await?, +// ); +// assert!(page.select_first(r#"p[data-id="null-features"]"#).is_ok()); +// Ok(()) +// }); +// } + +// #[test] +// fn test_minimal_failed_release_doesnt_error_features() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// fake_release_that_failed_before_build(&mut conn, "foo", "0.1.0", "some errors").await?; + +// let text_content = env +// .web_app() +// .await +// .get("/crate/foo/0.1.0/features") +// .await? +// .error_for_status()? +// .text() +// .await?; + +// assert!(text_content.contains( +// "Feature flags are not available for this release because \ +// the build failed before we could retrieve them" +// )); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_minimal_failed_release_doesnt_error() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// fake_release_that_failed_before_build(&mut conn, "foo", "0.1.0", "some errors").await?; + +// let text_content = env +// .web_app() +// .await +// .get("/crate/foo/0.1.0") +// .await? +// .error_for_status()? +// .text() +// .await?; + +// assert!(text_content.contains("docs.rs failed to build foo")); + +// Ok(()) +// }); +// } + +// #[test] +// fn platform_links_are_direct_and_without_nofollow() { +// fn check_links( +// response_text: String, +// ajax: bool, +// should_contain_redirect: bool, +// ) -> Vec<(String, String, String)> { +// let platform_links: Vec<(String, String, String)> = kuchikiki::parse_html() +// .one(response_text) +// .select(&format!(r#"{}li a"#, if ajax { "" } else { "#platforms " })) +// .expect("invalid selector") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// let url = attributes.get("href").expect("href").to_string(); +// let rel = attributes.get("rel").unwrap_or("").to_string(); +// (el.text_contents(), url, rel) +// }) +// .collect(); + +// dbg!(&platform_links); + +// assert_eq!(platform_links.len(), 2); + +// for (_, url, rel) in &platform_links { +// assert_eq!( +// url.contains("/target-redirect/"), +// should_contain_redirect, +// "url: {url:?}, ajax: {ajax:?}, should_contain_redirect: {should_contain_redirect:?}", +// ); +// if !should_contain_redirect { +// assert_eq!(rel, ""); +// } else { +// assert_eq!(rel, "nofollow"); +// } +// } +// platform_links +// } + +// async fn run_check_links_redir( +// env: &TestEnvironment, +// url: &str, +// should_contain_redirect: bool, +// ) { +// let response = env.web_app().await.get(dbg!(url)).await.unwrap(); +// let status = response.status(); +// assert!( +// status.is_success(), +// "no success, status: {}, url: {}, target: {}", +// status, +// url, +// response.redirect_target().unwrap_or_default(), +// ); +// let text = response.text().await.unwrap(); +// let list1 = dbg!(check_links( +// text.clone(), +// false, +// dbg!(should_contain_redirect) +// )); + +// // Same test with AJAX endpoint. +// let platform_menu_url = kuchikiki::parse_html() +// .one(text) +// .select_first("#platforms") +// .expect("invalid selector") +// .attributes +// .borrow() +// .get("data-url") +// .expect("data-url") +// .to_string(); +// let response = env +// .web_app() +// .await +// .get(&dbg!(platform_menu_url)) +// .await +// .unwrap(); +// assert!( +// response.status().is_success(), +// "{}", +// response.text().await.unwrap() +// ); +// response.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// let list2 = dbg!(check_links( +// response.text().await.unwrap(), +// true, +// should_contain_redirect, +// )); +// assert_eq!(list1, list2); +// } + +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.4.0") +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/struct.A.html") +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("x86_64-pc-windows-msvc") +// .source_file("README.md", b"storage readme") +// .create() +// .await?; + +// run_check_links_redir(&env, "/crate/dummy/0.4.0/features", false).await; +// run_check_links_redir(&env, "/crate/dummy/0.4.0/builds", false).await; +// run_check_links_redir(&env, "/crate/dummy/0.4.0/source/", false).await; +// run_check_links_redir(&env, "/crate/dummy/0.4.0/source/README.md", false).await; +// run_check_links_redir(&env, "/crate/dummy/0.4.0", false).await; + +// run_check_links_redir(&env, "/dummy/latest/dummy/", true).await; +// run_check_links_redir(&env, "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", true).await; +// run_check_links_redir( +// &env, +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.A.html", +// true, +// ) +// .await; + +// Ok(()) +// }); +// } + +// #[test] +// fn check_crate_name_in_redirect() { +// async fn check_links(env: &TestEnvironment, url: &str, links: Vec) { +// let response = env.web_app().await.get(url).await.unwrap(); +// assert!(response.status().is_success()); + +// let platform_links: Vec = kuchikiki::parse_html() +// .one(response.text().await.unwrap()) +// .select("li a") +// .expect("invalid selector") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// attributes.get("href").expect("href").to_string() +// }) +// .collect(); + +// assert_eq!(platform_links, links,); +// } + +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy-ba") +// .version("0.4.0") +// .rustdoc_file("dummy-ba/index.html") +// .rustdoc_file("x86_64-unknown-linux-gnu/dummy-ba/index.html") +// .add_target("x86_64-unknown-linux-gnu") +// .default_target("aarch64-apple-darwin") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("dummy-ba") +// .version("0.5.0") +// .rustdoc_file("dummy-ba/index.html") +// .rustdoc_file("x86_64-unknown-linux-gnu/dummy-ba/index.html") +// .add_target("x86_64-unknown-linux-gnu") +// .default_target("aarch64-apple-darwin") +// .create() +// .await?; + +// check_links( +// // https://github.com/rust-lang/docs.rs/issues/2922 +// &env, +// "/crate/dummy-ba/0.5.0/menus/releases/x86_64-unknown-linux-gnu/src/dummy_ba/de.rs.html", +// vec![ +// "/crate/dummy-ba/0.5.0/target-redirect/x86_64-unknown-linux-gnu/src/dummy_ba/de.rs.html".to_string(), +// "/crate/dummy-ba/0.4.0/target-redirect/x86_64-unknown-linux-gnu/src/dummy_ba/de.rs.html".to_string(), +// ], +// ) +// .await; + +// check_links( +// &env, +// "/crate/dummy-ba/latest/menus/releases/dummy_ba/index.html", +// vec![ +// "/crate/dummy-ba/0.5.0/target-redirect/dummy_ba/".to_string(), +// "/crate/dummy-ba/0.4.0/target-redirect/dummy_ba/".to_string(), +// ], +// ) +// .await; + +// check_links( +// &env, +// "/crate/dummy-ba/latest/menus/releases/x86_64-unknown-linux-gnu/dummy_ba/index.html", +// vec![ +// "/crate/dummy-ba/0.5.0/target-redirect/x86_64-unknown-linux-gnu/dummy_ba/".to_string(), +// "/crate/dummy-ba/0.4.0/target-redirect/x86_64-unknown-linux-gnu/dummy_ba/".to_string(), +// ], +// ).await; + +// Ok(()) +// }); +// } + +// // Ensure that if there are more than a given number of targets, it will not generate them in +// // the HTML directly (they will be loaded by AJAX if the user opens the menu). +// #[test] +// #[allow(clippy::assertions_on_constants)] +// fn platform_menu_ajax() { +// assert!(crate::DEFAULT_MAX_TARGETS > 2); + +// fn check_count(nb_targets: usize, expected: usize) { +// async_wrapper(|env| async move { +// let mut rel = env +// .fake_release() +// .await +// .name("dummy") +// .version("0.4.0") +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") +// .default_target("x86_64-unknown-linux-gnu"); + +// for nb in 0..nb_targets - 1 { +// rel = rel.add_target(&format!("x86_64-pc-windows-msvc{nb}")); +// } +// rel.create().await?; + +// let response = env.web_app().await.get("/crate/dummy/0.4.0").await?; +// assert!(response.status().is_success()); + +// let nb_li = kuchikiki::parse_html() +// .one(response.text().await?) +// .select(r#"#platforms li a"#) +// .expect("invalid selector") +// .count(); +// assert_eq!(nb_li, expected); +// Ok(()) +// }); +// } + +// // First we check that with 2 releases, the platforms list should be in the HTML. +// check_count(2, 2); +// // Then we check the same thing but with number of targets equal +// // to `DEFAULT_MAX_TARGETS`. +// check_count(crate::DEFAULT_MAX_TARGETS, 0); +// } + +// #[test] +// fn latest_url() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.4.0") +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("x86_64-pc-windows-msvc") +// .create() +// .await?; +// let web = env.web_app().await; + +// let resp = web.get("/crate/dummy/latest").await?; +// assert!(resp.status().is_success()); +// resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// let body = resp.text().await?; +// assert!(body.contains(" anyhow::Result<()> { +// let env = TestEnvironment::new().await?; +// env.fake_release() +// .await +// .name("rayon") +// .version("1.11.0") +// .create() +// .await?; +// let web = env.web_app().await; + +// web.assert_redirect(path, expected_target).await?; + +// Ok(()) +// } + +// #[test] +// fn readme() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .readme_only_database("database readme") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .readme_only_database("database readme") +// .source_file("README.md", b"storage readme") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.3.0") +// .source_file("README.md", b"storage readme") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.4.0") +// .readme_only_database("database readme") +// .source_file("MEREAD", b"storage meread") +// .source_file("Cargo.toml", br#"package.readme = "MEREAD""#) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.5.0") +// .readme_only_database("database readme") +// .source_file("README.md", b"storage readme") +// .no_cargo_toml() +// .create() +// .await?; + +// let check_readme = |path: String, content: String| { +// let env = env.clone(); +// async move { +// let resp = env.web_app().await.get(&path).await.unwrap(); +// let body = resp.text().await.unwrap(); +// assert!(body.contains(&content)); +// } +// }; + +// check_readme("/crate/dummy/0.1.0".into(), "database readme".into()).await; +// check_readme("/crate/dummy/0.2.0".into(), "storage readme".into()).await; +// check_readme("/crate/dummy/0.3.0".into(), "storage readme".into()).await; +// check_readme("/crate/dummy/0.4.0".into(), "storage meread".into()).await; + +// let mut conn = env.async_db().async_conn().await; +// let details = crate_details(&mut conn, "dummy", "0.5.0", None).await; +// assert!(matches!( +// details.fetch_readme(env.async_storage()).await, +// Ok(None) +// )); +// Ok(()) +// }); +// } + +// #[test] +// fn no_readme() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .source_file( +// "Cargo.toml", +// br#"[package] +// name = "dummy" +// version = "0.2.0" + +// [lib] +// name = "dummy" +// path = "src/lib.rs" +// "#, +// ) +// .source_file( +// "src/lib.rs", +// b"//! # Crate-level docs +// //! +// //! ``` +// //! let x = 21; +// //! ``` +// ", +// ) +// .target_source("src/lib.rs") +// .create() +// .await?; + +// let web = env.web_app().await; +// let response = web.get("/crate/dummy/0.2.0").await?; +// assert!(response.status().is_success()); + +// let dom = kuchikiki::parse_html().one(response.text().await?); +// dom.select_first("#main").expect("not main crate docs"); +// // First we check that the crate-level docs have been rendered as expected. +// assert_eq!( +// dom.select_first("#main h1") +// .expect("no h1 found") +// .text_contents(), +// "Crate-level docs" +// ); +// // Then we check that by default, the language used for highlighting is rust. +// assert_eq!( +// dom.select_first("#main pre .syntax-source.syntax-rust") +// .expect("no rust code block found") +// .text_contents(), +// "let x = 21;\n" +// ); +// Ok(()) +// }); +// } + +// #[test] +// fn test_crate_name_with_other_uri_chars() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .create() +// .await?; + +// let resp = env.web_app().await.get("/crate/dummy%3E").await?; +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); + +// Ok(()) +// }) +// } + +// #[test_case("/crate/dummy"; "without")] +// #[test_case("/crate/dummy/"; "slash")] +// fn test_unknown_crate_not_found_doesnt_redirect(path: &str) { +// async_wrapper(|env| async move { +// let resp = env.web_app().await.get(path).await?; +// assert_eq!(resp.status(), StatusCode::NOT_FOUND); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_build_status_no_builds() { +// async_wrapper(|env| async move { +// let release_id = env +// .fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!("DELETE FROM builds") +// .execute(&mut *conn) +// .await?; + +// update_build_status(&mut conn, release_id).await?; + +// assert_eq!( +// release_build_status(&mut conn, "dummy", "0.1.0").await, +// BuildStatus::InProgress +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_build_status_successful() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default().build_status(BuildStatus::Success), +// FakeBuild::default().build_status(BuildStatus::Failure), +// FakeBuild::default().build_status(BuildStatus::InProgress), +// ]) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; + +// assert_eq!( +// release_build_status(&mut conn, "dummy", "0.1.0").await, +// BuildStatus::Success +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_build_status_failed() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default().build_status(BuildStatus::Failure), +// FakeBuild::default().build_status(BuildStatus::InProgress), +// ]) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; + +// assert_eq!( +// release_build_status(&mut conn, "dummy", "0.1.0").await, +// BuildStatus::Failure +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_build_status_in_progress() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default().build_status(BuildStatus::InProgress), +// ]) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; + +// assert_eq!( +// release_build_status(&mut conn, "dummy", "0.1.0").await, +// BuildStatus::InProgress +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_sizes_display() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.4.0") +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; + +// let web = env.web_app().await; +// let response = web.get("/crate/dummy/0.4.0").await?; +// assert!(response.status().is_success()); + +// let mut has_source_code_size = false; +// let mut has_doc_size = false; +// for span in kuchikiki::parse_html() +// .one(response.text().await?) +// .select(r#".pure-menu-item span.documented-info"#) +// .expect("invalid selector") +// { +// if span.text_contents().starts_with("Source code size:") { +// has_source_code_size = true; +// } else if span.text_contents().starts_with("Documentation size:") { +// has_doc_size = true; +// } +// } +// assert!(has_source_code_size); +// assert!(has_doc_size); +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_web/src/error.rs b/crates/docs_rs_web/src/error.rs index ee74f5825..5d2ee760c 100644 --- a/crates/docs_rs_web/src/error.rs +++ b/crates/docs_rs_web/src/error.rs @@ -1,4 +1,4 @@ -use crate::web::{AxumErrorPage, cache::CachePolicy, releases::Search}; +use crate::{AxumErrorPage, cache::CachePolicy, releases::Search}; use anyhow::{Result, anyhow}; use axum::{ Json, @@ -220,146 +220,146 @@ impl From for AxumNope { pub(crate) type AxumResult = Result; pub(crate) type JsonAxumResult = Result; -#[cfg(test)] -mod tests { - use super::{AxumNope, EscapedURI, IntoResponse}; - use crate::test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; - use crate::web::cache::CachePolicy; - use kuchikiki::traits::TendrilSink; +// #[cfg(test)] +// mod tests { +// use super::{AxumNope, EscapedURI, IntoResponse}; +// use crate::test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; +// use crate::web::cache::CachePolicy; +// use kuchikiki::traits::TendrilSink; - #[test] - fn test_redirect_error_encodes_url_path() { - let response = AxumNope::Redirect( - EscapedURI::from_path("/something>"), - CachePolicy::ForeverInCdnAndBrowser, - ) - .into_response(); +// #[test] +// fn test_redirect_error_encodes_url_path() { +// let response = AxumNope::Redirect( +// EscapedURI::from_path("/something>"), +// CachePolicy::ForeverInCdnAndBrowser, +// ) +// .into_response(); - assert_eq!(response.status(), 302); - assert_eq!(response.headers().get("Location").unwrap(), "/something%3E"); - } +// assert_eq!(response.status(), 302); +// assert_eq!(response.headers().get("Location").unwrap(), "/something%3E"); +// } - #[test] - fn check_404_page_content_crate() { - async_wrapper(|env| async move { - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate-which-doesnt-exist") - .await? - .text() - .await?, - ); - assert_eq!(page.select("#crate-title").unwrap().count(), 1); - assert_eq!( - page.select("#crate-title") - .unwrap() - .next() - .unwrap() - .text_contents(), - "The requested crate does not exist", - ); +// #[test] +// fn check_404_page_content_crate() { +// async_wrapper(|env| async move { +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate-which-doesnt-exist") +// .await? +// .text() +// .await?, +// ); +// assert_eq!(page.select("#crate-title").unwrap().count(), 1); +// assert_eq!( +// page.select("#crate-title") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "The requested crate does not exist", +// ); - Ok(()) - }); - } +// Ok(()) +// }); +// } - #[test] - fn check_404_page_content_resource() { - async_wrapper(|env| async move { - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/resource-which-doesnt-exist.js") - .await? - .text() - .await?, - ); - assert_eq!(page.select("#crate-title").unwrap().count(), 1); - assert_eq!( - page.select("#crate-title") - .unwrap() - .next() - .unwrap() - .text_contents(), - "The requested resource does not exist", - ); +// #[test] +// fn check_404_page_content_resource() { +// async_wrapper(|env| async move { +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/resource-which-doesnt-exist.js") +// .await? +// .text() +// .await?, +// ); +// assert_eq!(page.select("#crate-title").unwrap().count(), 1); +// assert_eq!( +// page.select("#crate-title") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "The requested resource does not exist", +// ); - Ok(()) - }); - } +// Ok(()) +// }); +// } - #[test] - fn check_400_page_content_not_semver_version() { - async_wrapper(|env| async move { - env.fake_release().await.name("dummy").create().await?; +// #[test] +// fn check_400_page_content_not_semver_version() { +// async_wrapper(|env| async move { +// env.fake_release().await.name("dummy").create().await?; - let response = env.web_app().await.get("/dummy/not-semver").await?; - assert_eq!(response.status(), 400); +// let response = env.web_app().await.get("/dummy/not-semver").await?; +// assert_eq!(response.status(), 400); - let page = kuchikiki::parse_html().one(response.text().await?); - assert_eq!(page.select("#crate-title").unwrap().count(), 1); - assert_eq!( - page.select("#crate-title") - .unwrap() - .next() - .unwrap() - .text_contents(), - "Bad request" - ); +// let page = kuchikiki::parse_html().one(response.text().await?); +// assert_eq!(page.select("#crate-title").unwrap().count(), 1); +// assert_eq!( +// page.select("#crate-title") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "Bad request" +// ); - Ok(()) - }); - } +// Ok(()) +// }); +// } - #[test] - fn check_404_page_content_nonexistent_version() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .create() - .await?; - let page = kuchikiki::parse_html() - .one(env.web_app().await.get("/dummy/2.0").await?.text().await?); - assert_eq!(page.select("#crate-title").unwrap().count(), 1); - assert_eq!( - page.select("#crate-title") - .unwrap() - .next() - .unwrap() - .text_contents(), - "The requested version does not exist", - ); +// #[test] +// fn check_404_page_content_nonexistent_version() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .create() +// .await?; +// let page = kuchikiki::parse_html() +// .one(env.web_app().await.get("/dummy/2.0").await?.text().await?); +// assert_eq!(page.select("#crate-title").unwrap().count(), 1); +// assert_eq!( +// page.select("#crate-title") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "The requested version does not exist", +// ); - Ok(()) - }); - } +// Ok(()) +// }); +// } - #[test] - fn check_404_page_content_any_version_all_yanked() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .yanked(true) - .create() - .await?; - let page = kuchikiki::parse_html() - .one(env.web_app().await.get("/dummy/*").await?.text().await?); - assert_eq!(page.select("#crate-title").unwrap().count(), 1); - assert_eq!( - page.select("#crate-title") - .unwrap() - .next() - .unwrap() - .text_contents(), - "The requested version does not exist", - ); +// #[test] +// fn check_404_page_content_any_version_all_yanked() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .yanked(true) +// .create() +// .await?; +// let page = kuchikiki::parse_html() +// .one(env.web_app().await.get("/dummy/*").await?.text().await?); +// assert_eq!(page.select("#crate-title").unwrap().count(), 1); +// assert_eq!( +// page.select("#crate-title") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "The requested version does not exist", +// ); - Ok(()) - }); - } -} +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_web/src/extractors/context.rs b/crates/docs_rs_web/src/extractors/context.rs index 3e7675646..313ced245 100644 --- a/crates/docs_rs_web/src/extractors/context.rs +++ b/crates/docs_rs_web/src/extractors/context.rs @@ -1,6 +1,6 @@ //! a collection of custom extractors related to our app-context (context::Context) -use crate::web::error::AxumNope; +use crate::error::AxumNope; use anyhow::Context as _; use axum::{ RequestPartsExt, diff --git a/crates/docs_rs_web/src/extractors/path.rs b/crates/docs_rs_web/src/extractors/path.rs index dfa2b4fc4..bdd4efdaa 100644 --- a/crates/docs_rs_web/src/extractors/path.rs +++ b/crates/docs_rs_web/src/extractors/path.rs @@ -1,5 +1,5 @@ //! custom axum extractors for path parameters -use crate::web::error::AxumNope; +use crate::error::AxumNope; use anyhow::anyhow; use axum::{ RequestPartsExt, @@ -151,59 +151,59 @@ where } } -#[cfg(test)] -mod tests { - use super::*; - use crate::test::{AxumResponseTestExt, AxumRouterTestExt}; - use axum::{Router, routing::get}; - use http::StatusCode; - - #[tokio::test] - async fn test_path_file_ext() -> anyhow::Result<()> { - let app = Router::new() - .route( - "/mandatory/something.pdf", - get(|PathFileExtension(ext): PathFileExtension| async move { - format!("mandatory: {ext}") - }), - ) - .route( - "/mandatory_missing/something", - get(|PathFileExtension(_ext): PathFileExtension| async move { "never called" }), - ) - .route( - "/", - get(|PathFileExtension(_ext): PathFileExtension| async move { "never called" }), - ) - .route( - "/optional/something.pdf", - get(|ext: Option| async move { format!("option: {ext:?}") }), - ) - .route( - "/optional_missing/something", - get(|ext: Option| async move { format!("option: {ext:?}") }), - ); - - let res = app.get("/mandatory/something.pdf").await?; - assert!(res.status().is_success()); - assert_eq!(res.text().await?, "mandatory: pdf"); - - for path in &["/mandatory_missing/something", "/"] { - let res = app.get(path).await?; - assert_eq!(res.status(), StatusCode::BAD_REQUEST); - } - - let res = app.get("/optional/something.pdf").await?; - assert!(res.status().is_success()); - assert_eq!( - res.text().await?, - "option: Some(PathFileExtension(\"pdf\"))" - ); - - let res = app.get("/optional_missing/something").await?; - assert!(res.status().is_success()); - assert_eq!(res.text().await?, "option: None"); - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::{AxumResponseTestExt, AxumRouterTestExt}; +// use axum::{Router, routing::get}; +// use http::StatusCode; + +// #[tokio::test] +// async fn test_path_file_ext() -> anyhow::Result<()> { +// let app = Router::new() +// .route( +// "/mandatory/something.pdf", +// get(|PathFileExtension(ext): PathFileExtension| async move { +// format!("mandatory: {ext}") +// }), +// ) +// .route( +// "/mandatory_missing/something", +// get(|PathFileExtension(_ext): PathFileExtension| async move { "never called" }), +// ) +// .route( +// "/", +// get(|PathFileExtension(_ext): PathFileExtension| async move { "never called" }), +// ) +// .route( +// "/optional/something.pdf", +// get(|ext: Option| async move { format!("option: {ext:?}") }), +// ) +// .route( +// "/optional_missing/something", +// get(|ext: Option| async move { format!("option: {ext:?}") }), +// ); + +// let res = app.get("/mandatory/something.pdf").await?; +// assert!(res.status().is_success()); +// assert_eq!(res.text().await?, "mandatory: pdf"); + +// for path in &["/mandatory_missing/something", "/"] { +// let res = app.get(path).await?; +// assert_eq!(res.status(), StatusCode::BAD_REQUEST); +// } + +// let res = app.get("/optional/something.pdf").await?; +// assert!(res.status().is_success()); +// assert_eq!( +// res.text().await?, +// "option: Some(PathFileExtension(\"pdf\"))" +// ); + +// let res = app.get("/optional_missing/something").await?; +// assert!(res.status().is_success()); +// assert_eq!(res.text().await?, "option: None"); + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_web/src/extractors/rustdoc.rs b/crates/docs_rs_web/src/extractors/rustdoc.rs index 72f43f3d4..089cb51ad 100644 --- a/crates/docs_rs_web/src/extractors/rustdoc.rs +++ b/crates/docs_rs_web/src/extractors/rustdoc.rs @@ -1,6 +1,6 @@ //! special rustdoc extractors -use crate::web::{MatchedRelease, MetaData, ReqVersion, error::AxumNope, extractors::Path}; +use crate::{MatchedRelease, MetaData, ReqVersion, error::AxumNope, extractors::Path}; use anyhow::Result; use axum::{ RequestPartsExt, @@ -902,936 +902,936 @@ fn find_static_route_suffix<'a, 'b>(route: &'a str, path: &'b str) -> Option Some("help/some.html".into()); - "suffix with path" - )] - #[test_case("/{name}/{version}/help.html", "/foo/1.2.3/help.html" => Some("help.html".into()); "simple suffix")] - #[test_case("help.html", "help.html" => Some("help.html".into()); "simple suffix without other components")] - #[test_case("/{name}/{version}/help/", "/foo/1.2.3/help/" => Some("help/".into()); "suffix is folder")] - #[test_case("{name}/{version}/help/", "foo/1.2.3/help/" => Some("help/".into()); "without leading slash")] - #[test_case("/{name}/{version}/{*path}", "/foo/1.2.3/help.html" => None; "no suffix in route")] - #[test_case("/{name}/{version}/help.html", "/foo/1.2.3/other.html" => None; "different suffix")] - #[test_case( - "/{name}/{version}/some/help.html", - "/foo/1.2.3/other/help.html" - => None; - "different suffix later" - )] - #[test_case("", "" => None; "empty strings")] - #[test_case("/", "" => None; "one slash, one empty")] - fn test_find_static_route_suffix(route: &str, path: &str) -> Option { - find_static_route_suffix(route, path) - } - - #[test_case( - "/{name}", - RustdocParams::new(KRATE) - .try_with_original_uri("/krate").unwrap(); - "just name" - )] - #[test_case( - "/{name}/", - RustdocParams::new(KRATE) - .try_with_original_uri("/krate/").unwrap(); - "just name with trailing slash" - )] - #[test_case( - "/{name}/{version}", - RustdocParams::new(KRATE) - .try_with_original_uri("/krate/latest").unwrap(); - "just name and version" - )] - #[test_case( - "/{name}/{version}/{*path}", - RustdocParams::new(KRATE) - .try_with_original_uri("/krate/latest/static.html").unwrap() - .with_inner_path("static.html"); - "name, version, path extract" - )] - #[test_case( - "/{name}/{version}/{path}/static.html", - RustdocParams::new(KRATE) - .try_with_original_uri("/krate/latest/path_add/static.html").unwrap() - .with_inner_path("path_add") - .with_static_route_suffix("static.html"); - "name, version, path extract, static suffix" - )] - #[test_case( - "/{name}/{version}/clapproc%20%60macro.html", - RustdocParams::new("clap") - .try_with_original_uri("/clap/latest/clapproc%20%60macro.html").unwrap() - .with_static_route_suffix("clapproc `macro.html"); - "name, version, static suffix with some urlencoding" - )] - #[test_case( - "/{name}/{version}/static.html", - RustdocParams::new(KRATE) - .try_with_original_uri("/krate/latest/static.html").unwrap() - .with_static_route_suffix("static.html"); - "name, version, static suffix" - )] - #[test_case( - "/{name}/{version}/{target}", - RustdocParams::new(KRATE) - .try_with_req_version("1.2.3").unwrap() - .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}")).unwrap() - .with_doc_target(OTHER_TARGET); - "name, version, target" - )] - #[test_case( - "/{name}/{version}/{target}/folder/something.html", - RustdocParams::new(KRATE) - .try_with_req_version("1.2.3").unwrap() - .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/folder/something.html")).unwrap() - .with_doc_target(OTHER_TARGET) - .with_static_route_suffix("folder/something.html"); - "name, version, target, static suffix" - )] - #[test_case( - "/{name}/{version}/{target}/", - RustdocParams::new(KRATE) - .try_with_req_version("1.2.3").unwrap() - .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/")).unwrap() - .with_doc_target(OTHER_TARGET); - "name, version, target trailing slash" - )] - #[test_case( - "/{name}/{version}/{target}/{*path}", - RustdocParams::new(KRATE) - .try_with_req_version("1.2.3").unwrap() - .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/some/path/to/a/file.html")).unwrap() - .with_doc_target(OTHER_TARGET) - .with_inner_path("some/path/to/a/file.html"); - "name, version, target, path" - )] - #[test_case( - "/{name}/{version}/{target}/{path}/path/to/a/file.html", - RustdocParams::new(KRATE) - .try_with_req_version("1.2.3").unwrap() - .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/path_add/path/to/a/file.html")).unwrap() - .with_doc_target(OTHER_TARGET) - .with_inner_path("path_add") - .with_static_route_suffix("path/to/a/file.html"); - "name, version, target, path, static suffix" - )] - #[tokio::test] - async fn test_extract_rustdoc_params_from_request( - route: &str, - expected: RustdocParams, - ) -> anyhow::Result<()> { - let expected = expected.with_page_kind(PageKind::Rustdoc); - - let app = Router::new().route( - route, - get(|params: RustdocParams| async move { - format!("{:?}", params.with_page_kind(PageKind::Rustdoc)) - }), - ); - - let path = expected.original_uri.as_ref().unwrap().path().to_owned(); - - let res = app.get(&path).await?; - assert!(res.status().is_success()); - assert_eq!(res.text().await?, format!("{:?}", expected)); - - Ok(()) - } - - #[test_case( - None, None, false, - None, "", "krate/index.html"; - "super empty 1" - )] - #[test_case( - Some(""), Some(""), false, - None, "", "krate/index.html"; - "super empty 2" - )] - // test cases when no separate "target" component was present in the params - #[test_case( - None, Some("/"), true, - None, "", "krate/index.html"; - "just slash" - )] - #[test_case( - None, Some("something"), false, - None, "something", "krate/something"; - "without trailing slash" - )] - #[test_case( - None, Some("settings.html"), false, - None, "settings.html", "settings.html"; - "without trailing slash, but known root name" - )] - #[test_case( - None, Some("/something"), false, - None, "something", "krate/something"; - "leading slash is cut" - )] - #[test_case( - None, Some("something/"), true, - None, "something/", "something/index.html"; - "with trailing slash" - )] - // a target is given, but as first component of the path, for routes without separate - // "target" component - #[test_case( - None, Some(DEFAULT_TARGET), false, - Some(DEFAULT_TARGET), "", "krate/index.html"; - "just target without trailing slash" - )] - #[test_case( - None, Some(&format!("{DEFAULT_TARGET}/")), true, - Some(DEFAULT_TARGET), "", "krate/index.html"; - "just default target with trailing slash" - )] - #[test_case( - None, Some(&format!("{DEFAULT_TARGET}/one")), false, - Some(DEFAULT_TARGET), "one", "krate/one"; - "target + one without trailing slash" - )] - #[test_case( - None, Some(&format!("{DEFAULT_TARGET}/one/")), true, - Some(DEFAULT_TARGET), "one/", "one/index.html"; - "target + one target with trailing slash" - )] - #[test_case( - None, Some(&format!("{UNKNOWN_TARGET}/one/")), true, - None, &format!("{UNKNOWN_TARGET}/one/"), &format!("{UNKNOWN_TARGET}/one/index.html"); - "unknown target stays in path" - )] - #[test_case( - None, Some(&format!("{DEFAULT_TARGET}/some/inner/path")), false, - Some(DEFAULT_TARGET), "some/inner/path", "some/inner/path"; - "all without trailing slash" - )] - #[test_case( - None, Some(&format!("{DEFAULT_TARGET}/some/inner/path/")), true, - Some(DEFAULT_TARGET), "some/inner/path/", "some/inner/path/index.html"; - "all with trailing slash" - )] - // here we have a separate target path parameter, we check it and use it accordingly - #[test_case( - Some(DEFAULT_TARGET), None, false, - Some(DEFAULT_TARGET), "", "krate/index.html"; - "actual target, that is default" - )] - #[test_case( - Some(DEFAULT_TARGET), Some("inner/path.html"), false, - Some(DEFAULT_TARGET), "inner/path.html", "inner/path.html"; - "actual target with path" - )] - #[test_case( - Some(DEFAULT_TARGET), Some("inner/path/"), true, - Some(DEFAULT_TARGET), "inner/path/", "inner/path/index.html"; - "actual target with path slash" - )] - #[test_case( - Some(UNKNOWN_TARGET), None, true, - None, &format!("{UNKNOWN_TARGET}/"), &format!("{UNKNOWN_TARGET}/index.html"); - "unknown target" - )] - #[test_case( - Some(UNKNOWN_TARGET), None, false, - None, UNKNOWN_TARGET, &format!("krate/{UNKNOWN_TARGET}"); - "unknown target without trailing slash" - )] - #[test_case( - Some(UNKNOWN_TARGET), Some("inner/path.html"), false, - None, &format!("{UNKNOWN_TARGET}/inner/path.html"), &format!("{UNKNOWN_TARGET}/inner/path.html"); - "unknown target with path" - )] - #[test_case( - Some(OTHER_TARGET), Some("inner/path.html"), false, - Some(OTHER_TARGET), "inner/path.html", &format!("{OTHER_TARGET}/inner/path.html"); - "other target with path" - )] - #[test_case( - Some(UNKNOWN_TARGET), Some("inner/path/"), true, - None, &format!("{UNKNOWN_TARGET}/inner/path/"), &format!("{UNKNOWN_TARGET}/inner/path/index.html"); - "unknown target with path slash" - )] - #[test_case( - Some(OTHER_TARGET), Some("inner/path/"), true, - Some(OTHER_TARGET), "inner/path/", &format!("{OTHER_TARGET}/inner/path/index.html"); - "other target with path slash" - )] - #[test_case( - Some(DEFAULT_TARGET), None, false, - Some(DEFAULT_TARGET), "", "krate/index.html"; - "pure default target, without trailing slash" - )] - fn test_parse( - target: Option<&str>, - path: Option<&str>, - had_trailing_slash: bool, - expected_target: Option<&str>, - expected_path: &str, - expected_storage_path: &str, - ) { - let mut dummy_path = match (target, path) { - (Some(target), Some(path)) => format!("{}/{}", target, path), - (Some(target), None) => target.to_string(), - (None, Some(path)) => path.to_string(), - (None, None) => String::new(), - }; - dummy_path.insert(0, '/'); - if had_trailing_slash && !dummy_path.is_empty() { - dummy_path.push('/'); - } - - let parsed = RustdocParams::new(KRATE) - .with_page_kind(PageKind::Rustdoc) - .with_req_version(ReqVersion::Latest) - .with_maybe_doc_target(target) - .with_maybe_inner_path(path) - .try_with_original_uri(&dummy_path[..]) - .unwrap() - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - assert_eq!(parsed.name(), KRATE); - assert_eq!(parsed.req_version(), &ReqVersion::Latest); - assert_eq!(parsed.doc_target(), expected_target); - assert_eq!(parsed.inner_path(), expected_path); - assert_eq!(parsed.storage_path(), expected_storage_path); - assert_eq!( - parsed.path_is_folder(), - had_trailing_slash || dummy_path.ends_with('/') || dummy_path.is_empty() - ); - } - - #[test_case("dummy/struct.WindowsOnly.html", Some("WindowsOnly"))] - #[test_case("dummy/some_module/struct.SomeItem.html", Some("SomeItem"))] - #[test_case("dummy/some_module/index.html", Some("some_module"))] - #[test_case("dummy/some_module/", Some("some_module"))] - #[test_case("src/folder1/folder2/logic.rs.html", Some("logic"))] - #[test_case("src/non_source_file.rs", None)] - #[test_case("html", None; "plain file without extension")] - #[test_case("something.html", Some("html"); "plain file")] - #[test_case("", None)] - fn test_generate_fallback_search(path: &str, search: Option<&str>) { - let mut params = RustdocParams::new("dummy") - .try_with_req_version("0.4.0") - .unwrap() - // non-default target, target stays in the url - .with_doc_target(OTHER_TARGET) - .with_inner_path(path) - .with_default_target(DEFAULT_TARGET) - .with_target_name("dummy") - .with_doc_targets(TARGETS.iter().cloned()); - - assert_eq!(params.generate_fallback_search().as_deref(), search); - assert_eq!( - params.generate_fallback_url().to_string(), - format!( - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/{}", - search.map(|s| format!("?search={}", s)).unwrap_or_default() - ) - ); - - // change to default target, check url again - params = params.with_doc_target(DEFAULT_TARGET); - - assert_eq!(params.generate_fallback_search().as_deref(), search); - assert_eq!( - params.generate_fallback_url().to_string(), - format!( - "/dummy/0.4.0/dummy/{}", - search.map(|s| format!("?search={}", s)).unwrap_or_default() - ) - ); - } - - #[test] - fn test_parse_source() { - let params = RustdocParams::new("dummy") - .try_with_req_version("0.4.0") - .unwrap() - .with_inner_path("README.md") - .with_page_kind(PageKind::Source) - .try_with_original_uri("/crate/dummy/0.4.0/source/README.md") - .unwrap() - .with_default_target(DEFAULT_TARGET) - .with_target_name("dummy") - .with_doc_targets(TARGETS.iter().cloned()); - - assert_eq!(params.rustdoc_url().to_string(), "/dummy/0.4.0/dummy/"); - assert_eq!( - params.source_url().to_string(), - "/crate/dummy/0.4.0/source/README.md" - ); - assert_eq!( - params.target_redirect_url().to_string(), - "/crate/dummy/0.4.0/target-redirect/dummy/" - ); - } - - #[test_case( - None, None, None, None => "" - )] - #[test_case( - Some("target_name"), None, None, None => "target_name/" - )] - #[test_case( - None, None, None, Some("path/index.html") => "path/"; - "cuts trailing /index.html" - )] - #[test_case( - Some("target_name"), None, - Some(DEFAULT_TARGET), Some("inner/path.html") - => "x86_64-unknown-linux-gnu/inner/path.html"; - "default target, but we don't know about it, keeps target" - )] - #[test_case( - Some("target_name"), None, - Some(DEFAULT_TARGET), None - => "x86_64-unknown-linux-gnu/target_name/"; - "default target, we don't know about it, without path" - )] - #[test_case( - Some("target_name"), Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), None - => "target_name/"; - "default-target, without path, target_name is used to generate the inner path" - )] - #[test_case( - Some("target_name"), Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), Some("inner/path.html") - => "inner/path.html"; - "default target, with path, target_name is ignored" - )] - #[test_case( - None, Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), Some("inner/path/index.html") - => "inner/path/"; - "default target, with path as folder with index.html" - )] - #[test_case( - None, Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), Some("inner/path/") - => "inner/path/"; - "default target, with path as folder" - )] - #[test_case( - Some("target_name"), Some(DEFAULT_TARGET), - Some(OTHER_TARGET), None - => "x86_64-pc-windows-msvc/target_name/"; - "non-default-target, without path, target_name is used to generate the inner path" - )] - #[test_case( - Some("target_name"), Some(DEFAULT_TARGET), - Some(OTHER_TARGET), Some("inner/path.html") - => "x86_64-pc-windows-msvc/inner/path.html"; - "non-default target, with path, target_name is ignored" - )] - fn test_generate_rustdoc_path_for_url( - target_name: Option<&str>, - default_target: Option<&str>, - doc_target: Option<&str>, - inner_path: Option<&str>, - ) -> String { - generate_rustdoc_path_for_url(target_name, default_target, doc_target, inner_path) - } - - #[test] - fn test_case_1() { - let params = RustdocParams::new("dummy") - .try_with_req_version("0.2.0") - .unwrap() - .with_doc_target("dummy") - .with_inner_path("struct.Dummy.html") - .with_page_kind(PageKind::Rustdoc) - .try_with_original_uri("/dummy/0.2.0/dummy/struct.Dummy.html") - .unwrap() - .with_default_target(DEFAULT_TARGET) - .with_target_name("dummy") - .with_doc_targets(TARGETS.iter().cloned()); - - dbg!(¶ms); - - assert!(params.doc_target().is_none()); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - assert_eq!(params.storage_path(), "dummy/struct.Dummy.html"); - - let params = params.with_doc_target(DEFAULT_TARGET); - dbg!(¶ms); - assert_eq!(params.doc_target(), Some(DEFAULT_TARGET)); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - assert_eq!(params.storage_path(), "dummy/struct.Dummy.html"); - - let params = params.with_doc_target(OTHER_TARGET); - assert_eq!(params.doc_target(), Some(OTHER_TARGET)); - assert_eq!( - params.storage_path(), - format!("{OTHER_TARGET}/dummy/struct.Dummy.html") - ); - assert_eq!( - params.storage_path(), - format!("{OTHER_TARGET}/dummy/struct.Dummy.html") - ); - } - - #[test_case( - "/", - None, None, - None, "" - ; "no target, no path" - )] - #[test_case( - &format!("/{DEFAULT_TARGET}"), - Some(DEFAULT_TARGET), None, - Some(DEFAULT_TARGET), ""; - "existing target, no path" - )] - #[test_case( - &format!("/{UNKNOWN_TARGET}"), - Some(UNKNOWN_TARGET), None, - None, UNKNOWN_TARGET; - "unknown target, no path" - )] - #[test_case( - &format!("/{UNKNOWN_TARGET}/"), - Some(UNKNOWN_TARGET), Some("something/file.html"), - None, &format!("{UNKNOWN_TARGET}/something/file.html"); - "unknown target, with path, trailling slash is kept" - )] - #[test_case( - &format!("/{UNKNOWN_TARGET}/"), - Some(UNKNOWN_TARGET), None, - None, &format!("{UNKNOWN_TARGET}/"); - "unknown target, no path, trailling slash is kept" - )] - fn test_with_fixed_target_and_path( - original_uri: &str, - target: Option<&str>, - path: Option<&str>, - expected_target: Option<&str>, - expected_path: &str, - ) { - let params = RustdocParams::new(KRATE) - .try_with_req_version("0.4.0") - .unwrap() - .try_with_original_uri(original_uri) - .unwrap() - .with_maybe_doc_target(target) - .with_maybe_inner_path(path) - .with_doc_targets(TARGETS.iter().cloned()); - - dbg!(¶ms); - - assert_eq!(params.doc_target(), expected_target); - assert_eq!(params.inner_path(), expected_path); - } - - #[test_case( - None, None, - None, None - => ""; - "empty" - )] - #[test_case( - None, None, - None, Some("folder/index.html") - => "folder/"; - "just folder index.html will be removed" - )] - #[test_case( - None, None, - None, Some(INDEX_HTML) - => ""; - "just root index.html will be removed" - )] - #[test_case( - None, Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), None - => ""; - "just default target" - )] - #[test_case( - None, Some(DEFAULT_TARGET), - Some(OTHER_TARGET), None - => format!("{OTHER_TARGET}/"); - "just other target" - )] - #[test_case( - Some(KRATE), Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), None - => format!("{KRATE}/"); - "full with default target, target name is used" - )] - #[test_case( - Some(KRATE), Some(DEFAULT_TARGET), - Some(OTHER_TARGET), None - => format!("{OTHER_TARGET}/{KRATE}/"); - "full with other target, target name is used" - )] - #[test_case( - Some(KRATE), Some(DEFAULT_TARGET), - Some(DEFAULT_TARGET), Some("inner/something.html") - => "inner/something.html"; - "full with default target, target name is ignored" - )] - #[test_case( - Some(KRATE), Some(DEFAULT_TARGET), - Some(OTHER_TARGET), Some("inner/something.html") - => format!("{OTHER_TARGET}/inner/something.html"); - "full with other target, target name is ignored" - )] - fn test_rustdoc_path_for_url( - target_name: Option<&str>, - default_target: Option<&str>, - doc_target: Option<&str>, - inner_path: Option<&str>, - ) -> String { - generate_rustdoc_path_for_url(target_name, default_target, doc_target, inner_path) - } - - #[test] - fn test_override_page_kind() { - let params = RustdocParams::new(KRATE) - .try_with_original_uri("/krate/latest/path_add/static.html") - .unwrap() - .with_inner_path("path_add") - .with_static_route_suffix("static.html") - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - // without page kind, rustdoc path doesn' thave a path, and static suffix ignored - assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); - assert_eq!(params.source_url(), "/crate/krate/latest/source/"); - assert_eq!( - params.target_redirect_url(), - "/crate/krate/latest/target-redirect/krate/" - ); - - let params = params.with_page_kind(PageKind::Rustdoc); - assert_eq!(params.rustdoc_url(), "/krate/latest/path_add/static.html"); - assert_eq!(params.source_url(), "/crate/krate/latest/source/"); - assert_eq!( - params.target_redirect_url(), - "/crate/krate/latest/target-redirect/path_add/static.html" - ); - - let params = params.with_page_kind(PageKind::Source); - assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); - // just path added, not static suffix - assert_eq!(params.source_url(), "/crate/krate/latest/source/path_add"); - assert_eq!( - params.target_redirect_url(), - "/crate/krate/latest/target-redirect/krate/" - ); - } - - #[test] - fn test_override_page_kind_with_target() { - let params = RustdocParams::new(KRATE) - .try_with_original_uri(format!("/krate/latest/{OTHER_TARGET}/path_add/static.html")) - .unwrap() - .with_inner_path("path_add") - .with_static_route_suffix("static.html") - .with_doc_target(OTHER_TARGET) - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - // without page kind, rustdoc path doesn' thave a path, and static suffix ignored - assert_eq!( - params.rustdoc_url(), - format!("/krate/latest/{OTHER_TARGET}/krate/") - ); - assert_eq!(params.source_url(), "/crate/krate/latest/source/"); - assert_eq!( - params.target_redirect_url(), - format!("/crate/krate/latest/target-redirect/{OTHER_TARGET}/krate/") - ); - - // same when the pagekind is "Source" - let params = params.with_page_kind(PageKind::Source); - assert_eq!( - params.rustdoc_url(), - format!("/krate/latest/{OTHER_TARGET}/krate/") - ); - assert_eq!(params.source_url(), "/crate/krate/latest/source/path_add"); - assert_eq!( - params.target_redirect_url(), - format!("/crate/krate/latest/target-redirect/{OTHER_TARGET}/krate/") - ); - - // with page-kind "Rustdoc", we get the full path with static suffix - let params = params.with_page_kind(PageKind::Rustdoc); - dbg!(¶ms); - assert_eq!( - params.rustdoc_url(), - format!("/krate/latest/{OTHER_TARGET}/path_add/static.html") - ); - assert_eq!(params.source_url(), format!("/crate/krate/latest/source/")); - assert_eq!( - params.target_redirect_url(), - format!("/crate/krate/latest/target-redirect/{OTHER_TARGET}/path_add/static.html") - ); - } - - #[test] - fn test_debug_output() { - let params = RustdocParams::new("dummy") - .try_with_req_version("0.2.0") - .unwrap() - .with_inner_path("struct.Dummy.html") - .with_doc_target("dummy") - .with_page_kind(PageKind::Rustdoc) - .try_with_original_uri("/dummy/0.2.0/dummy/struct.Dummy.html") - .unwrap() - .with_default_target(DEFAULT_TARGET) - .with_target_name("dummy") - .with_doc_targets(TARGETS.iter().cloned()); - - let debug_output = format!("{:?}", params); - - assert!(debug_output.contains("EscapedURI")); - assert!(debug_output.contains("rustdoc_url()")); - assert!(debug_output.contains("generate_fallback_url()")); - } - - #[test] - fn test_override_doc_target_when_old_doc_target_was_path() { - // params as if they would have come from a route like - // `/{name}/{version}/{target}/{*path}`, - // where in the `{target}` place we have part of the path. - let params = RustdocParams::new(KRATE) - .with_req_version(ReqVersion::Exact(VERSION)) - .try_with_original_uri("/dummy/0.1.0/dummy/struct.Dummy.html") - .unwrap() - .with_doc_target("dummy") - .with_inner_path("struct.Dummy.html"); - - dbg!(¶ms); - - // initial params, doc-target is "dummy", not validated - assert_eq!(params.doc_target(), Some("dummy")); - assert_eq!(params.inner_path(), "struct.Dummy.html"); - - // after parsing, we recognize that the doc target is not a target, and attach - // it to the inner_path. - let params = params - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - dbg!(¶ms); - - assert_eq!(params.doc_target(), None); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - - // now, in some cases, we now want to generate a variation of these params, - // with an actual non-default doc target. - // Then we expect the path to be intact still, and the target to be set, even - // though the folder-part of the path was initially generated from the doc_target field. - let params = params.with_doc_target(OTHER_TARGET); - dbg!(¶ms); - assert_eq!(params.doc_target(), Some(OTHER_TARGET)); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - } - - #[test] - fn test_if_order_matters_1() { - let params = RustdocParams::new(KRATE) - .with_req_version(ReqVersion::Exact(VERSION)) - .try_with_original_uri("/dummy/0.1.0/dummy/struct.Dummy.html") - .unwrap() - .with_inner_path("dummy/struct.Dummy.html") - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - assert_eq!(params.doc_target(), None); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - - let params = params.with_doc_target(OTHER_TARGET); - assert_eq!(params.doc_target(), Some(OTHER_TARGET)); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - } - - #[test] - fn test_if_order_matters_2() { - let params = RustdocParams::new(KRATE) - .with_req_version(ReqVersion::Exact(VERSION)) - .try_with_original_uri(format!( - "/dummy/0.1.0/{OTHER_TARGET}/dummy/struct.Dummy.html" - )) - .unwrap() - .with_inner_path(format!("{OTHER_TARGET}/dummy/struct.Dummy.html")) - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - assert_eq!(params.doc_target(), Some(OTHER_TARGET)); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - - let params = params.with_doc_target(DEFAULT_TARGET); - assert_eq!(params.doc_target(), Some(DEFAULT_TARGET)); - assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); - } - - #[test] - fn test_parse_something() { - // test for https://github.com/rust-lang/docs.rs/issues/2989 - let params = dbg!( - RustdocParams::new(KRATE) - .with_page_kind(PageKind::Rustdoc) - .try_with_original_uri(format!("/{KRATE}/latest/{KRATE}")) - .unwrap() - .with_req_version(ReqVersion::Latest) - .with_doc_target(KRATE) - ); - - assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); - - let params = dbg!( - params - .with_target_name(KRATE) - .with_default_target(DEFAULT_TARGET) - .with_doc_targets(TARGETS.iter().cloned()) - ); - - assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); - } - - #[test_case("other_path.html", "/krate/latest/krate/other_path.html")] - #[test_case("other_path", "/krate/latest/krate/other_path"; "without .html")] - #[test_case("other_path.html", "/krate/latest/krate/other_path.html"; "with .html")] - #[test_case("settings.html", "/krate/latest/settings.html"; "static routes")] - #[test_case(KRATE, "/krate/latest/krate/"; "same as target name, without slash")] - fn test_redirect_some_odd_paths_we_saw(inner_path: &str, expected_url: &str) { - // test for https://github.com/rust-lang/docs.rs/issues/2989 - let params = RustdocParams::new(KRATE) - .with_page_kind(PageKind::Rustdoc) - .try_with_original_uri(format!("/{KRATE}/latest/{inner_path}")) - .unwrap() - .with_req_version(ReqVersion::Latest) - .with_maybe_doc_target(None::) - .with_inner_path(inner_path) - .with_default_target(DEFAULT_TARGET) - .with_target_name(KRATE) - .with_doc_targets(TARGETS.iter().cloned()); - - dbg!(¶ms); - - assert_eq!(params.rustdoc_url(), expected_url); - } - - #[test] - fn test_item_with_semver_url() { - // https://github.com/rust-lang/docs.rs/issues/3036 - // This fixes an issue where we mistakenly attached a - // trailing `/` to a rustdoc URL when redirecting - // to the exact version, coming from a semver version. - - let ver: Version = "0.14.0".parse().unwrap(); - let params = RustdocParams::new(KRATE) - .with_page_kind(PageKind::Rustdoc) - .with_req_version(ReqVersion::Exact(ver)) - .with_doc_target(KRATE) - .with_inner_path("trait.Itertools.html"); - - dbg!(¶ms); - - assert_eq!( - params.rustdoc_url(), - format!("/{KRATE}/0.14.0/{KRATE}/trait.Itertools.html") - ) - } - - #[test_case(None)] - #[test_case(Some(CompressionAlgorithm::Gzip))] - #[test_case(Some(CompressionAlgorithm::Zstd))] - fn test_plain_json_url(wanted_compression: Option) { - let mut params = RustdocParams::new(KRATE) - .with_page_kind(PageKind::Rustdoc) - .with_req_version(ReqVersion::Exact(V1)); - - assert_eq!( - params.json_download_url(wanted_compression, None), - format!( - "/crate/{KRATE}/{V1}/json{}", - wanted_compression - .map(|c| format!(".{}", c.file_extension())) - .unwrap_or_default() - ) - ); - - params = params.with_doc_target("some-target"); - - assert_eq!( - params.json_download_url(wanted_compression, None), - format!( - "/crate/{KRATE}/{V1}/some-target/json{}", - wanted_compression - .map(|c| format!(".{}", c.file_extension())) - .unwrap_or_default() - ) - ); - } - - #[test_case(None)] - #[test_case(Some(CompressionAlgorithm::Gzip))] - #[test_case(Some(CompressionAlgorithm::Zstd))] - fn test_plain_json_url_with_format(wanted_compression: Option) { - let mut params = RustdocParams::new(KRATE) - .with_page_kind(PageKind::Rustdoc) - .with_req_version(ReqVersion::Exact(V1)); - - assert_eq!( - params.json_download_url(wanted_compression, Some("42")), - format!( - "/crate/{KRATE}/{V1}/json/42{}", - wanted_compression - .map(|c| format!(".{}", c.file_extension())) - .unwrap_or_default() - ) - ); - - params = params.with_doc_target("some-target"); - - assert_eq!( - params.json_download_url(wanted_compression, Some("42")), - format!( - "/crate/{KRATE}/{V1}/some-target/json/42{}", - wanted_compression - .map(|c| format!(".{}", c.file_extension())) - .unwrap_or_default() - ) - ); - } - - #[test] - fn test_zip_download_url() { - let params = RustdocParams::new(KRATE).with_req_version(ReqVersion::Exact(V1)); - assert_eq!( - params.zip_download_url(), - format!("/crate/{KRATE}/{V1}/download") - ); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// db::types::version::Version, +// test::{AxumResponseTestExt, AxumRouterTestExt, V1}, +// }; +// use axum::{Router, routing::get}; +// use test_case::test_case; + +// static KRATE: &str = "krate"; +// const VERSION: Version = Version::new(0, 1, 0); +// static DEFAULT_TARGET: &str = "x86_64-unknown-linux-gnu"; +// static OTHER_TARGET: &str = "x86_64-pc-windows-msvc"; +// static UNKNOWN_TARGET: &str = "some-unknown-target"; +// static TARGETS: &[&str] = &[DEFAULT_TARGET, OTHER_TARGET]; + +// #[test_case( +// "/{name}/{version}/help/some.html", +// "/foo/1.2.3/help/some.html" +// => Some("help/some.html".into()); +// "suffix with path" +// )] +// #[test_case("/{name}/{version}/help.html", "/foo/1.2.3/help.html" => Some("help.html".into()); "simple suffix")] +// #[test_case("help.html", "help.html" => Some("help.html".into()); "simple suffix without other components")] +// #[test_case("/{name}/{version}/help/", "/foo/1.2.3/help/" => Some("help/".into()); "suffix is folder")] +// #[test_case("{name}/{version}/help/", "foo/1.2.3/help/" => Some("help/".into()); "without leading slash")] +// #[test_case("/{name}/{version}/{*path}", "/foo/1.2.3/help.html" => None; "no suffix in route")] +// #[test_case("/{name}/{version}/help.html", "/foo/1.2.3/other.html" => None; "different suffix")] +// #[test_case( +// "/{name}/{version}/some/help.html", +// "/foo/1.2.3/other/help.html" +// => None; +// "different suffix later" +// )] +// #[test_case("", "" => None; "empty strings")] +// #[test_case("/", "" => None; "one slash, one empty")] +// fn test_find_static_route_suffix(route: &str, path: &str) -> Option { +// find_static_route_suffix(route, path) +// } + +// #[test_case( +// "/{name}", +// RustdocParams::new(KRATE) +// .try_with_original_uri("/krate").unwrap(); +// "just name" +// )] +// #[test_case( +// "/{name}/", +// RustdocParams::new(KRATE) +// .try_with_original_uri("/krate/").unwrap(); +// "just name with trailing slash" +// )] +// #[test_case( +// "/{name}/{version}", +// RustdocParams::new(KRATE) +// .try_with_original_uri("/krate/latest").unwrap(); +// "just name and version" +// )] +// #[test_case( +// "/{name}/{version}/{*path}", +// RustdocParams::new(KRATE) +// .try_with_original_uri("/krate/latest/static.html").unwrap() +// .with_inner_path("static.html"); +// "name, version, path extract" +// )] +// #[test_case( +// "/{name}/{version}/{path}/static.html", +// RustdocParams::new(KRATE) +// .try_with_original_uri("/krate/latest/path_add/static.html").unwrap() +// .with_inner_path("path_add") +// .with_static_route_suffix("static.html"); +// "name, version, path extract, static suffix" +// )] +// #[test_case( +// "/{name}/{version}/clapproc%20%60macro.html", +// RustdocParams::new("clap") +// .try_with_original_uri("/clap/latest/clapproc%20%60macro.html").unwrap() +// .with_static_route_suffix("clapproc `macro.html"); +// "name, version, static suffix with some urlencoding" +// )] +// #[test_case( +// "/{name}/{version}/static.html", +// RustdocParams::new(KRATE) +// .try_with_original_uri("/krate/latest/static.html").unwrap() +// .with_static_route_suffix("static.html"); +// "name, version, static suffix" +// )] +// #[test_case( +// "/{name}/{version}/{target}", +// RustdocParams::new(KRATE) +// .try_with_req_version("1.2.3").unwrap() +// .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}")).unwrap() +// .with_doc_target(OTHER_TARGET); +// "name, version, target" +// )] +// #[test_case( +// "/{name}/{version}/{target}/folder/something.html", +// RustdocParams::new(KRATE) +// .try_with_req_version("1.2.3").unwrap() +// .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/folder/something.html")).unwrap() +// .with_doc_target(OTHER_TARGET) +// .with_static_route_suffix("folder/something.html"); +// "name, version, target, static suffix" +// )] +// #[test_case( +// "/{name}/{version}/{target}/", +// RustdocParams::new(KRATE) +// .try_with_req_version("1.2.3").unwrap() +// .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/")).unwrap() +// .with_doc_target(OTHER_TARGET); +// "name, version, target trailing slash" +// )] +// #[test_case( +// "/{name}/{version}/{target}/{*path}", +// RustdocParams::new(KRATE) +// .try_with_req_version("1.2.3").unwrap() +// .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/some/path/to/a/file.html")).unwrap() +// .with_doc_target(OTHER_TARGET) +// .with_inner_path("some/path/to/a/file.html"); +// "name, version, target, path" +// )] +// #[test_case( +// "/{name}/{version}/{target}/{path}/path/to/a/file.html", +// RustdocParams::new(KRATE) +// .try_with_req_version("1.2.3").unwrap() +// .try_with_original_uri(format!("/krate/1.2.3/{OTHER_TARGET}/path_add/path/to/a/file.html")).unwrap() +// .with_doc_target(OTHER_TARGET) +// .with_inner_path("path_add") +// .with_static_route_suffix("path/to/a/file.html"); +// "name, version, target, path, static suffix" +// )] +// #[tokio::test] +// async fn test_extract_rustdoc_params_from_request( +// route: &str, +// expected: RustdocParams, +// ) -> anyhow::Result<()> { +// let expected = expected.with_page_kind(PageKind::Rustdoc); + +// let app = Router::new().route( +// route, +// get(|params: RustdocParams| async move { +// format!("{:?}", params.with_page_kind(PageKind::Rustdoc)) +// }), +// ); + +// let path = expected.original_uri.as_ref().unwrap().path().to_owned(); + +// let res = app.get(&path).await?; +// assert!(res.status().is_success()); +// assert_eq!(res.text().await?, format!("{:?}", expected)); + +// Ok(()) +// } + +// #[test_case( +// None, None, false, +// None, "", "krate/index.html"; +// "super empty 1" +// )] +// #[test_case( +// Some(""), Some(""), false, +// None, "", "krate/index.html"; +// "super empty 2" +// )] +// // test cases when no separate "target" component was present in the params +// #[test_case( +// None, Some("/"), true, +// None, "", "krate/index.html"; +// "just slash" +// )] +// #[test_case( +// None, Some("something"), false, +// None, "something", "krate/something"; +// "without trailing slash" +// )] +// #[test_case( +// None, Some("settings.html"), false, +// None, "settings.html", "settings.html"; +// "without trailing slash, but known root name" +// )] +// #[test_case( +// None, Some("/something"), false, +// None, "something", "krate/something"; +// "leading slash is cut" +// )] +// #[test_case( +// None, Some("something/"), true, +// None, "something/", "something/index.html"; +// "with trailing slash" +// )] +// // a target is given, but as first component of the path, for routes without separate +// // "target" component +// #[test_case( +// None, Some(DEFAULT_TARGET), false, +// Some(DEFAULT_TARGET), "", "krate/index.html"; +// "just target without trailing slash" +// )] +// #[test_case( +// None, Some(&format!("{DEFAULT_TARGET}/")), true, +// Some(DEFAULT_TARGET), "", "krate/index.html"; +// "just default target with trailing slash" +// )] +// #[test_case( +// None, Some(&format!("{DEFAULT_TARGET}/one")), false, +// Some(DEFAULT_TARGET), "one", "krate/one"; +// "target + one without trailing slash" +// )] +// #[test_case( +// None, Some(&format!("{DEFAULT_TARGET}/one/")), true, +// Some(DEFAULT_TARGET), "one/", "one/index.html"; +// "target + one target with trailing slash" +// )] +// #[test_case( +// None, Some(&format!("{UNKNOWN_TARGET}/one/")), true, +// None, &format!("{UNKNOWN_TARGET}/one/"), &format!("{UNKNOWN_TARGET}/one/index.html"); +// "unknown target stays in path" +// )] +// #[test_case( +// None, Some(&format!("{DEFAULT_TARGET}/some/inner/path")), false, +// Some(DEFAULT_TARGET), "some/inner/path", "some/inner/path"; +// "all without trailing slash" +// )] +// #[test_case( +// None, Some(&format!("{DEFAULT_TARGET}/some/inner/path/")), true, +// Some(DEFAULT_TARGET), "some/inner/path/", "some/inner/path/index.html"; +// "all with trailing slash" +// )] +// // here we have a separate target path parameter, we check it and use it accordingly +// #[test_case( +// Some(DEFAULT_TARGET), None, false, +// Some(DEFAULT_TARGET), "", "krate/index.html"; +// "actual target, that is default" +// )] +// #[test_case( +// Some(DEFAULT_TARGET), Some("inner/path.html"), false, +// Some(DEFAULT_TARGET), "inner/path.html", "inner/path.html"; +// "actual target with path" +// )] +// #[test_case( +// Some(DEFAULT_TARGET), Some("inner/path/"), true, +// Some(DEFAULT_TARGET), "inner/path/", "inner/path/index.html"; +// "actual target with path slash" +// )] +// #[test_case( +// Some(UNKNOWN_TARGET), None, true, +// None, &format!("{UNKNOWN_TARGET}/"), &format!("{UNKNOWN_TARGET}/index.html"); +// "unknown target" +// )] +// #[test_case( +// Some(UNKNOWN_TARGET), None, false, +// None, UNKNOWN_TARGET, &format!("krate/{UNKNOWN_TARGET}"); +// "unknown target without trailing slash" +// )] +// #[test_case( +// Some(UNKNOWN_TARGET), Some("inner/path.html"), false, +// None, &format!("{UNKNOWN_TARGET}/inner/path.html"), &format!("{UNKNOWN_TARGET}/inner/path.html"); +// "unknown target with path" +// )] +// #[test_case( +// Some(OTHER_TARGET), Some("inner/path.html"), false, +// Some(OTHER_TARGET), "inner/path.html", &format!("{OTHER_TARGET}/inner/path.html"); +// "other target with path" +// )] +// #[test_case( +// Some(UNKNOWN_TARGET), Some("inner/path/"), true, +// None, &format!("{UNKNOWN_TARGET}/inner/path/"), &format!("{UNKNOWN_TARGET}/inner/path/index.html"); +// "unknown target with path slash" +// )] +// #[test_case( +// Some(OTHER_TARGET), Some("inner/path/"), true, +// Some(OTHER_TARGET), "inner/path/", &format!("{OTHER_TARGET}/inner/path/index.html"); +// "other target with path slash" +// )] +// #[test_case( +// Some(DEFAULT_TARGET), None, false, +// Some(DEFAULT_TARGET), "", "krate/index.html"; +// "pure default target, without trailing slash" +// )] +// fn test_parse( +// target: Option<&str>, +// path: Option<&str>, +// had_trailing_slash: bool, +// expected_target: Option<&str>, +// expected_path: &str, +// expected_storage_path: &str, +// ) { +// let mut dummy_path = match (target, path) { +// (Some(target), Some(path)) => format!("{}/{}", target, path), +// (Some(target), None) => target.to_string(), +// (None, Some(path)) => path.to_string(), +// (None, None) => String::new(), +// }; +// dummy_path.insert(0, '/'); +// if had_trailing_slash && !dummy_path.is_empty() { +// dummy_path.push('/'); +// } + +// let parsed = RustdocParams::new(KRATE) +// .with_page_kind(PageKind::Rustdoc) +// .with_req_version(ReqVersion::Latest) +// .with_maybe_doc_target(target) +// .with_maybe_inner_path(path) +// .try_with_original_uri(&dummy_path[..]) +// .unwrap() +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// assert_eq!(parsed.name(), KRATE); +// assert_eq!(parsed.req_version(), &ReqVersion::Latest); +// assert_eq!(parsed.doc_target(), expected_target); +// assert_eq!(parsed.inner_path(), expected_path); +// assert_eq!(parsed.storage_path(), expected_storage_path); +// assert_eq!( +// parsed.path_is_folder(), +// had_trailing_slash || dummy_path.ends_with('/') || dummy_path.is_empty() +// ); +// } + +// #[test_case("dummy/struct.WindowsOnly.html", Some("WindowsOnly"))] +// #[test_case("dummy/some_module/struct.SomeItem.html", Some("SomeItem"))] +// #[test_case("dummy/some_module/index.html", Some("some_module"))] +// #[test_case("dummy/some_module/", Some("some_module"))] +// #[test_case("src/folder1/folder2/logic.rs.html", Some("logic"))] +// #[test_case("src/non_source_file.rs", None)] +// #[test_case("html", None; "plain file without extension")] +// #[test_case("something.html", Some("html"); "plain file")] +// #[test_case("", None)] +// fn test_generate_fallback_search(path: &str, search: Option<&str>) { +// let mut params = RustdocParams::new("dummy") +// .try_with_req_version("0.4.0") +// .unwrap() +// // non-default target, target stays in the url +// .with_doc_target(OTHER_TARGET) +// .with_inner_path(path) +// .with_default_target(DEFAULT_TARGET) +// .with_target_name("dummy") +// .with_doc_targets(TARGETS.iter().cloned()); + +// assert_eq!(params.generate_fallback_search().as_deref(), search); +// assert_eq!( +// params.generate_fallback_url().to_string(), +// format!( +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/{}", +// search.map(|s| format!("?search={}", s)).unwrap_or_default() +// ) +// ); + +// // change to default target, check url again +// params = params.with_doc_target(DEFAULT_TARGET); + +// assert_eq!(params.generate_fallback_search().as_deref(), search); +// assert_eq!( +// params.generate_fallback_url().to_string(), +// format!( +// "/dummy/0.4.0/dummy/{}", +// search.map(|s| format!("?search={}", s)).unwrap_or_default() +// ) +// ); +// } + +// #[test] +// fn test_parse_source() { +// let params = RustdocParams::new("dummy") +// .try_with_req_version("0.4.0") +// .unwrap() +// .with_inner_path("README.md") +// .with_page_kind(PageKind::Source) +// .try_with_original_uri("/crate/dummy/0.4.0/source/README.md") +// .unwrap() +// .with_default_target(DEFAULT_TARGET) +// .with_target_name("dummy") +// .with_doc_targets(TARGETS.iter().cloned()); + +// assert_eq!(params.rustdoc_url().to_string(), "/dummy/0.4.0/dummy/"); +// assert_eq!( +// params.source_url().to_string(), +// "/crate/dummy/0.4.0/source/README.md" +// ); +// assert_eq!( +// params.target_redirect_url().to_string(), +// "/crate/dummy/0.4.0/target-redirect/dummy/" +// ); +// } + +// #[test_case( +// None, None, None, None => "" +// )] +// #[test_case( +// Some("target_name"), None, None, None => "target_name/" +// )] +// #[test_case( +// None, None, None, Some("path/index.html") => "path/"; +// "cuts trailing /index.html" +// )] +// #[test_case( +// Some("target_name"), None, +// Some(DEFAULT_TARGET), Some("inner/path.html") +// => "x86_64-unknown-linux-gnu/inner/path.html"; +// "default target, but we don't know about it, keeps target" +// )] +// #[test_case( +// Some("target_name"), None, +// Some(DEFAULT_TARGET), None +// => "x86_64-unknown-linux-gnu/target_name/"; +// "default target, we don't know about it, without path" +// )] +// #[test_case( +// Some("target_name"), Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), None +// => "target_name/"; +// "default-target, without path, target_name is used to generate the inner path" +// )] +// #[test_case( +// Some("target_name"), Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), Some("inner/path.html") +// => "inner/path.html"; +// "default target, with path, target_name is ignored" +// )] +// #[test_case( +// None, Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), Some("inner/path/index.html") +// => "inner/path/"; +// "default target, with path as folder with index.html" +// )] +// #[test_case( +// None, Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), Some("inner/path/") +// => "inner/path/"; +// "default target, with path as folder" +// )] +// #[test_case( +// Some("target_name"), Some(DEFAULT_TARGET), +// Some(OTHER_TARGET), None +// => "x86_64-pc-windows-msvc/target_name/"; +// "non-default-target, without path, target_name is used to generate the inner path" +// )] +// #[test_case( +// Some("target_name"), Some(DEFAULT_TARGET), +// Some(OTHER_TARGET), Some("inner/path.html") +// => "x86_64-pc-windows-msvc/inner/path.html"; +// "non-default target, with path, target_name is ignored" +// )] +// fn test_generate_rustdoc_path_for_url( +// target_name: Option<&str>, +// default_target: Option<&str>, +// doc_target: Option<&str>, +// inner_path: Option<&str>, +// ) -> String { +// generate_rustdoc_path_for_url(target_name, default_target, doc_target, inner_path) +// } + +// #[test] +// fn test_case_1() { +// let params = RustdocParams::new("dummy") +// .try_with_req_version("0.2.0") +// .unwrap() +// .with_doc_target("dummy") +// .with_inner_path("struct.Dummy.html") +// .with_page_kind(PageKind::Rustdoc) +// .try_with_original_uri("/dummy/0.2.0/dummy/struct.Dummy.html") +// .unwrap() +// .with_default_target(DEFAULT_TARGET) +// .with_target_name("dummy") +// .with_doc_targets(TARGETS.iter().cloned()); + +// dbg!(¶ms); + +// assert!(params.doc_target().is_none()); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); +// assert_eq!(params.storage_path(), "dummy/struct.Dummy.html"); + +// let params = params.with_doc_target(DEFAULT_TARGET); +// dbg!(¶ms); +// assert_eq!(params.doc_target(), Some(DEFAULT_TARGET)); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); +// assert_eq!(params.storage_path(), "dummy/struct.Dummy.html"); + +// let params = params.with_doc_target(OTHER_TARGET); +// assert_eq!(params.doc_target(), Some(OTHER_TARGET)); +// assert_eq!( +// params.storage_path(), +// format!("{OTHER_TARGET}/dummy/struct.Dummy.html") +// ); +// assert_eq!( +// params.storage_path(), +// format!("{OTHER_TARGET}/dummy/struct.Dummy.html") +// ); +// } + +// #[test_case( +// "/", +// None, None, +// None, "" +// ; "no target, no path" +// )] +// #[test_case( +// &format!("/{DEFAULT_TARGET}"), +// Some(DEFAULT_TARGET), None, +// Some(DEFAULT_TARGET), ""; +// "existing target, no path" +// )] +// #[test_case( +// &format!("/{UNKNOWN_TARGET}"), +// Some(UNKNOWN_TARGET), None, +// None, UNKNOWN_TARGET; +// "unknown target, no path" +// )] +// #[test_case( +// &format!("/{UNKNOWN_TARGET}/"), +// Some(UNKNOWN_TARGET), Some("something/file.html"), +// None, &format!("{UNKNOWN_TARGET}/something/file.html"); +// "unknown target, with path, trailling slash is kept" +// )] +// #[test_case( +// &format!("/{UNKNOWN_TARGET}/"), +// Some(UNKNOWN_TARGET), None, +// None, &format!("{UNKNOWN_TARGET}/"); +// "unknown target, no path, trailling slash is kept" +// )] +// fn test_with_fixed_target_and_path( +// original_uri: &str, +// target: Option<&str>, +// path: Option<&str>, +// expected_target: Option<&str>, +// expected_path: &str, +// ) { +// let params = RustdocParams::new(KRATE) +// .try_with_req_version("0.4.0") +// .unwrap() +// .try_with_original_uri(original_uri) +// .unwrap() +// .with_maybe_doc_target(target) +// .with_maybe_inner_path(path) +// .with_doc_targets(TARGETS.iter().cloned()); + +// dbg!(¶ms); + +// assert_eq!(params.doc_target(), expected_target); +// assert_eq!(params.inner_path(), expected_path); +// } + +// #[test_case( +// None, None, +// None, None +// => ""; +// "empty" +// )] +// #[test_case( +// None, None, +// None, Some("folder/index.html") +// => "folder/"; +// "just folder index.html will be removed" +// )] +// #[test_case( +// None, None, +// None, Some(INDEX_HTML) +// => ""; +// "just root index.html will be removed" +// )] +// #[test_case( +// None, Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), None +// => ""; +// "just default target" +// )] +// #[test_case( +// None, Some(DEFAULT_TARGET), +// Some(OTHER_TARGET), None +// => format!("{OTHER_TARGET}/"); +// "just other target" +// )] +// #[test_case( +// Some(KRATE), Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), None +// => format!("{KRATE}/"); +// "full with default target, target name is used" +// )] +// #[test_case( +// Some(KRATE), Some(DEFAULT_TARGET), +// Some(OTHER_TARGET), None +// => format!("{OTHER_TARGET}/{KRATE}/"); +// "full with other target, target name is used" +// )] +// #[test_case( +// Some(KRATE), Some(DEFAULT_TARGET), +// Some(DEFAULT_TARGET), Some("inner/something.html") +// => "inner/something.html"; +// "full with default target, target name is ignored" +// )] +// #[test_case( +// Some(KRATE), Some(DEFAULT_TARGET), +// Some(OTHER_TARGET), Some("inner/something.html") +// => format!("{OTHER_TARGET}/inner/something.html"); +// "full with other target, target name is ignored" +// )] +// fn test_rustdoc_path_for_url( +// target_name: Option<&str>, +// default_target: Option<&str>, +// doc_target: Option<&str>, +// inner_path: Option<&str>, +// ) -> String { +// generate_rustdoc_path_for_url(target_name, default_target, doc_target, inner_path) +// } + +// #[test] +// fn test_override_page_kind() { +// let params = RustdocParams::new(KRATE) +// .try_with_original_uri("/krate/latest/path_add/static.html") +// .unwrap() +// .with_inner_path("path_add") +// .with_static_route_suffix("static.html") +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// // without page kind, rustdoc path doesn' thave a path, and static suffix ignored +// assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); +// assert_eq!(params.source_url(), "/crate/krate/latest/source/"); +// assert_eq!( +// params.target_redirect_url(), +// "/crate/krate/latest/target-redirect/krate/" +// ); + +// let params = params.with_page_kind(PageKind::Rustdoc); +// assert_eq!(params.rustdoc_url(), "/krate/latest/path_add/static.html"); +// assert_eq!(params.source_url(), "/crate/krate/latest/source/"); +// assert_eq!( +// params.target_redirect_url(), +// "/crate/krate/latest/target-redirect/path_add/static.html" +// ); + +// let params = params.with_page_kind(PageKind::Source); +// assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); +// // just path added, not static suffix +// assert_eq!(params.source_url(), "/crate/krate/latest/source/path_add"); +// assert_eq!( +// params.target_redirect_url(), +// "/crate/krate/latest/target-redirect/krate/" +// ); +// } + +// #[test] +// fn test_override_page_kind_with_target() { +// let params = RustdocParams::new(KRATE) +// .try_with_original_uri(format!("/krate/latest/{OTHER_TARGET}/path_add/static.html")) +// .unwrap() +// .with_inner_path("path_add") +// .with_static_route_suffix("static.html") +// .with_doc_target(OTHER_TARGET) +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// // without page kind, rustdoc path doesn' thave a path, and static suffix ignored +// assert_eq!( +// params.rustdoc_url(), +// format!("/krate/latest/{OTHER_TARGET}/krate/") +// ); +// assert_eq!(params.source_url(), "/crate/krate/latest/source/"); +// assert_eq!( +// params.target_redirect_url(), +// format!("/crate/krate/latest/target-redirect/{OTHER_TARGET}/krate/") +// ); + +// // same when the pagekind is "Source" +// let params = params.with_page_kind(PageKind::Source); +// assert_eq!( +// params.rustdoc_url(), +// format!("/krate/latest/{OTHER_TARGET}/krate/") +// ); +// assert_eq!(params.source_url(), "/crate/krate/latest/source/path_add"); +// assert_eq!( +// params.target_redirect_url(), +// format!("/crate/krate/latest/target-redirect/{OTHER_TARGET}/krate/") +// ); + +// // with page-kind "Rustdoc", we get the full path with static suffix +// let params = params.with_page_kind(PageKind::Rustdoc); +// dbg!(¶ms); +// assert_eq!( +// params.rustdoc_url(), +// format!("/krate/latest/{OTHER_TARGET}/path_add/static.html") +// ); +// assert_eq!(params.source_url(), format!("/crate/krate/latest/source/")); +// assert_eq!( +// params.target_redirect_url(), +// format!("/crate/krate/latest/target-redirect/{OTHER_TARGET}/path_add/static.html") +// ); +// } + +// #[test] +// fn test_debug_output() { +// let params = RustdocParams::new("dummy") +// .try_with_req_version("0.2.0") +// .unwrap() +// .with_inner_path("struct.Dummy.html") +// .with_doc_target("dummy") +// .with_page_kind(PageKind::Rustdoc) +// .try_with_original_uri("/dummy/0.2.0/dummy/struct.Dummy.html") +// .unwrap() +// .with_default_target(DEFAULT_TARGET) +// .with_target_name("dummy") +// .with_doc_targets(TARGETS.iter().cloned()); + +// let debug_output = format!("{:?}", params); + +// assert!(debug_output.contains("EscapedURI")); +// assert!(debug_output.contains("rustdoc_url()")); +// assert!(debug_output.contains("generate_fallback_url()")); +// } + +// #[test] +// fn test_override_doc_target_when_old_doc_target_was_path() { +// // params as if they would have come from a route like +// // `/{name}/{version}/{target}/{*path}`, +// // where in the `{target}` place we have part of the path. +// let params = RustdocParams::new(KRATE) +// .with_req_version(ReqVersion::Exact(VERSION)) +// .try_with_original_uri("/dummy/0.1.0/dummy/struct.Dummy.html") +// .unwrap() +// .with_doc_target("dummy") +// .with_inner_path("struct.Dummy.html"); + +// dbg!(¶ms); + +// // initial params, doc-target is "dummy", not validated +// assert_eq!(params.doc_target(), Some("dummy")); +// assert_eq!(params.inner_path(), "struct.Dummy.html"); + +// // after parsing, we recognize that the doc target is not a target, and attach +// // it to the inner_path. +// let params = params +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// dbg!(¶ms); + +// assert_eq!(params.doc_target(), None); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); + +// // now, in some cases, we now want to generate a variation of these params, +// // with an actual non-default doc target. +// // Then we expect the path to be intact still, and the target to be set, even +// // though the folder-part of the path was initially generated from the doc_target field. +// let params = params.with_doc_target(OTHER_TARGET); +// dbg!(¶ms); +// assert_eq!(params.doc_target(), Some(OTHER_TARGET)); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); +// } + +// #[test] +// fn test_if_order_matters_1() { +// let params = RustdocParams::new(KRATE) +// .with_req_version(ReqVersion::Exact(VERSION)) +// .try_with_original_uri("/dummy/0.1.0/dummy/struct.Dummy.html") +// .unwrap() +// .with_inner_path("dummy/struct.Dummy.html") +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// assert_eq!(params.doc_target(), None); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); + +// let params = params.with_doc_target(OTHER_TARGET); +// assert_eq!(params.doc_target(), Some(OTHER_TARGET)); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); +// } + +// #[test] +// fn test_if_order_matters_2() { +// let params = RustdocParams::new(KRATE) +// .with_req_version(ReqVersion::Exact(VERSION)) +// .try_with_original_uri(format!( +// "/dummy/0.1.0/{OTHER_TARGET}/dummy/struct.Dummy.html" +// )) +// .unwrap() +// .with_inner_path(format!("{OTHER_TARGET}/dummy/struct.Dummy.html")) +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// assert_eq!(params.doc_target(), Some(OTHER_TARGET)); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); + +// let params = params.with_doc_target(DEFAULT_TARGET); +// assert_eq!(params.doc_target(), Some(DEFAULT_TARGET)); +// assert_eq!(params.inner_path(), "dummy/struct.Dummy.html"); +// } + +// #[test] +// fn test_parse_something() { +// // test for https://github.com/rust-lang/docs.rs/issues/2989 +// let params = dbg!( +// RustdocParams::new(KRATE) +// .with_page_kind(PageKind::Rustdoc) +// .try_with_original_uri(format!("/{KRATE}/latest/{KRATE}")) +// .unwrap() +// .with_req_version(ReqVersion::Latest) +// .with_doc_target(KRATE) +// ); + +// assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); + +// let params = dbg!( +// params +// .with_target_name(KRATE) +// .with_default_target(DEFAULT_TARGET) +// .with_doc_targets(TARGETS.iter().cloned()) +// ); + +// assert_eq!(params.rustdoc_url(), "/krate/latest/krate/"); +// } + +// #[test_case("other_path.html", "/krate/latest/krate/other_path.html")] +// #[test_case("other_path", "/krate/latest/krate/other_path"; "without .html")] +// #[test_case("other_path.html", "/krate/latest/krate/other_path.html"; "with .html")] +// #[test_case("settings.html", "/krate/latest/settings.html"; "static routes")] +// #[test_case(KRATE, "/krate/latest/krate/"; "same as target name, without slash")] +// fn test_redirect_some_odd_paths_we_saw(inner_path: &str, expected_url: &str) { +// // test for https://github.com/rust-lang/docs.rs/issues/2989 +// let params = RustdocParams::new(KRATE) +// .with_page_kind(PageKind::Rustdoc) +// .try_with_original_uri(format!("/{KRATE}/latest/{inner_path}")) +// .unwrap() +// .with_req_version(ReqVersion::Latest) +// .with_maybe_doc_target(None::) +// .with_inner_path(inner_path) +// .with_default_target(DEFAULT_TARGET) +// .with_target_name(KRATE) +// .with_doc_targets(TARGETS.iter().cloned()); + +// dbg!(¶ms); + +// assert_eq!(params.rustdoc_url(), expected_url); +// } + +// #[test] +// fn test_item_with_semver_url() { +// // https://github.com/rust-lang/docs.rs/issues/3036 +// // This fixes an issue where we mistakenly attached a +// // trailing `/` to a rustdoc URL when redirecting +// // to the exact version, coming from a semver version. + +// let ver: Version = "0.14.0".parse().unwrap(); +// let params = RustdocParams::new(KRATE) +// .with_page_kind(PageKind::Rustdoc) +// .with_req_version(ReqVersion::Exact(ver)) +// .with_doc_target(KRATE) +// .with_inner_path("trait.Itertools.html"); + +// dbg!(¶ms); + +// assert_eq!( +// params.rustdoc_url(), +// format!("/{KRATE}/0.14.0/{KRATE}/trait.Itertools.html") +// ) +// } + +// #[test_case(None)] +// #[test_case(Some(CompressionAlgorithm::Gzip))] +// #[test_case(Some(CompressionAlgorithm::Zstd))] +// fn test_plain_json_url(wanted_compression: Option) { +// let mut params = RustdocParams::new(KRATE) +// .with_page_kind(PageKind::Rustdoc) +// .with_req_version(ReqVersion::Exact(V1)); + +// assert_eq!( +// params.json_download_url(wanted_compression, None), +// format!( +// "/crate/{KRATE}/{V1}/json{}", +// wanted_compression +// .map(|c| format!(".{}", c.file_extension())) +// .unwrap_or_default() +// ) +// ); + +// params = params.with_doc_target("some-target"); + +// assert_eq!( +// params.json_download_url(wanted_compression, None), +// format!( +// "/crate/{KRATE}/{V1}/some-target/json{}", +// wanted_compression +// .map(|c| format!(".{}", c.file_extension())) +// .unwrap_or_default() +// ) +// ); +// } + +// #[test_case(None)] +// #[test_case(Some(CompressionAlgorithm::Gzip))] +// #[test_case(Some(CompressionAlgorithm::Zstd))] +// fn test_plain_json_url_with_format(wanted_compression: Option) { +// let mut params = RustdocParams::new(KRATE) +// .with_page_kind(PageKind::Rustdoc) +// .with_req_version(ReqVersion::Exact(V1)); + +// assert_eq!( +// params.json_download_url(wanted_compression, Some("42")), +// format!( +// "/crate/{KRATE}/{V1}/json/42{}", +// wanted_compression +// .map(|c| format!(".{}", c.file_extension())) +// .unwrap_or_default() +// ) +// ); + +// params = params.with_doc_target("some-target"); + +// assert_eq!( +// params.json_download_url(wanted_compression, Some("42")), +// format!( +// "/crate/{KRATE}/{V1}/some-target/json/42{}", +// wanted_compression +// .map(|c| format!(".{}", c.file_extension())) +// .unwrap_or_default() +// ) +// ); +// } + +// #[test] +// fn test_zip_download_url() { +// let params = RustdocParams::new(KRATE).with_req_version(ReqVersion::Exact(V1)); +// assert_eq!( +// params.zip_download_url(), +// format!("/crate/{KRATE}/{V1}/download") +// ); +// } +// } diff --git a/crates/docs_rs_web/src/features.rs b/crates/docs_rs_web/src/features.rs index 0dd827b43..94a4a8b98 100644 --- a/crates/docs_rs_web/src/features.rs +++ b/crates/docs_rs_web/src/features.rs @@ -1,21 +1,18 @@ use crate::{ - db::types::Feature as DbFeature, - impl_axum_webpage, - web::{ - MetaData, ReqVersion, - cache::CachePolicy, - error::{AxumNope, AxumResult}, - extractors::{ - DbConnection, - rustdoc::{PageKind, RustdocParams}, - }, - filters, match_version, - page::templates::{RenderBrands, RenderRegular, RenderSolid}, + MetaData, ReqVersion, + cache::CachePolicy, + error::{AxumNope, AxumResult}, + extractors::{ + DbConnection, + rustdoc::{PageKind, RustdocParams}, }, + impl_axum_webpage, match_version, + page::templates::{RenderBrands, RenderRegular, RenderSolid}, }; use anyhow::anyhow; use askama::Template; use axum::response::IntoResponse; +use docs_rs_database::types::Feature as DbFeature; use docs_rs_headers::CanonicalUrl; use serde_json::Value; use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; @@ -260,281 +257,281 @@ fn get_sorted_features(raw_features: Vec) -> (Vec, HashSet>()) - .create() - .await?; - - let web = env.web_app().await; - - let page = kuchikiki::parse_html() - .one(web.get("/crate/foo/0.1.0/features").await?.text().await?); - let text = page.select_first("#main > p").unwrap().text_contents(); - // It should only contain one feature enabled by default since the others are either - // enabling a dependency (`dep:what`) or enabling a feature from a dependency - // (`whatever/wut`). - assert_eq!( - text, - "This version has 2 feature flags, 1 of them enabled by default." - ); - - Ok(()) - }); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; +// use kuchikiki::traits::TendrilSink; +// use reqwest::StatusCode; + +// #[test] +// fn test_parsing_raw_features() { +// let feature = SubFeature::parse("a-feature"); +// assert_eq!(feature, SubFeature::Feature("a-feature".into())); + +// let feature = SubFeature::parse("dep:a-dependency"); +// assert_eq!(feature, SubFeature::Dependency("a-dependency".into())); + +// let feature = SubFeature::parse("a-dependency/sub-feature"); +// assert_eq!( +// feature, +// SubFeature::DependencyFeature { +// dependency: "a-dependency".into(), +// optional: false, +// feature: "sub-feature".into() +// } +// ); + +// let feature = SubFeature::parse("a-dependency?/sub-feature"); +// assert_eq!( +// feature, +// SubFeature::DependencyFeature { +// dependency: "a-dependency".into(), +// optional: true, +// feature: "sub-feature".into() +// } +// ); +// } + +// #[test] +// fn test_feature_map_filters_private() { +// let private1 = DbFeature::new("_private1".into(), vec!["feature1".into()]); +// let feature2 = DbFeature::new("feature2".into(), Vec::new()); + +// let (sorted_features, _) = get_sorted_features(vec![private1, feature2]); + +// assert_eq!(sorted_features.len(), 1); +// assert_eq!(sorted_features[0].name, "feature2"); +// } + +// #[test] +// fn test_default_tree_structure_with_nested_default() { +// let default = DbFeature::new(DEFAULT_NAME.into(), vec!["feature1".into()]); +// let non_default = DbFeature::new("non-default".into(), Vec::new()); +// let feature1 = DbFeature::new( +// "feature1".into(), +// vec!["feature2".into(), "feature3".into()], +// ); +// let feature2 = DbFeature::new("feature2".into(), Vec::new()); +// let feature3 = DbFeature::new("feature3".into(), Vec::new()); + +// let (sorted_features, default_features) = +// get_sorted_features(vec![default, non_default, feature3, feature2, feature1]); + +// assert_eq!(sorted_features.len(), 5); +// assert_eq!(sorted_features[0].name, "default"); +// assert_eq!(sorted_features[1].name, "feature1"); +// assert_eq!(sorted_features[2].name, "feature2"); +// assert_eq!(sorted_features[3].name, "feature3"); +// assert_eq!(sorted_features[4].name, "non-default"); + +// assert!(default_features.contains("feature3")); +// assert!(!default_features.contains("non-default")); +// } + +// #[test] +// fn test_default_tree_structure_without_default() { +// let feature1 = DbFeature::new( +// "feature1".into(), +// vec!["feature2".into(), "feature3".into()], +// ); +// let feature2 = DbFeature::new("feature2".into(), Vec::new()); +// let feature3 = DbFeature::new("feature3".into(), Vec::new()); + +// let (sorted_features, default_features) = +// get_sorted_features(vec![feature3, feature2, feature1]); + +// assert_eq!(sorted_features.len(), 3); +// assert_eq!(sorted_features[0].name, "feature1"); +// assert_eq!(sorted_features[1].name, "feature2"); +// assert_eq!(sorted_features[2].name, "feature3"); + +// assert_eq!(default_features.len(), 0); +// } + +// #[test] +// fn test_default_tree_structure_single_default() { +// let default = DbFeature::new(DEFAULT_NAME.into(), Vec::new()); +// let non_default = DbFeature::new("non-default".into(), Vec::new()); + +// let (sorted_features, default_features) = get_sorted_features(vec![default, non_default]); + +// assert_eq!(sorted_features.len(), 2); +// assert_eq!(sorted_features[0].name, "default"); +// assert_eq!(sorted_features[1].name, "non-default"); + +// assert_eq!(default_features.len(), 1); +// assert!(default_features.contains("default")); +// } + +// #[test] +// fn test_order_features_and_get_len_without_default() { +// let feature1 = DbFeature::new( +// "feature1".into(), +// vec!["feature10".into(), "feature11".into()], +// ); +// let feature2 = DbFeature::new("feature2".into(), vec!["feature20".into()]); +// let feature3 = DbFeature::new("feature3".into(), Vec::new()); + +// let (sorted_features, default_features) = +// get_sorted_features(vec![feature3, feature2, feature1]); + +// assert_eq!(sorted_features.len(), 3); +// assert_eq!(sorted_features[0].name, "feature1"); +// assert_eq!(sorted_features[1].name, "feature2"); +// assert_eq!(sorted_features[2].name, "feature3"); + +// assert_eq!(default_features.len(), 0); +// } + +// #[test] +// fn semver_redirect() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.1") +// .features(HashMap::new()) +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect_cached( +// "/crate/foo/~0.2/features", +// "/crate/foo/0.2.1/features", +// CachePolicy::ForeverInCdn, +// env.config(), +// ) +// .await?; +// Ok(()) +// }); +// } + +// #[test] +// fn specific_version_correctly_cached() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.0") +// .features(HashMap::new()) +// .create() +// .await?; + +// let web = env.web_app().await; +// let resp = web.get("/crate/foo/0.2.0/features").await?; +// assert!(resp.status().is_success()); +// resp.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); +// Ok(()) +// }); +// } + +// #[test] +// fn latest_200() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .features(HashMap::new()) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.0") +// .features(HashMap::new()) +// .create() +// .await?; + +// let web = env.web_app().await; +// let resp = web.get("/crate/foo/latest/features").await?; +// assert!(resp.status().is_success()); +// resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// let body = resp.text().await?; +// assert!(body.contains(">()) +// .create() +// .await?; + +// let web = env.web_app().await; + +// let page = kuchikiki::parse_html() +// .one(web.get("/crate/foo/0.1.0/features").await?.text().await?); +// let text = page.select_first("#main > p").unwrap().text_contents(); +// // It should only contain one feature enabled by default since the others are either +// // enabling a dependency (`dep:what`) or enabling a feature from a dependency +// // (`whatever/wut`). +// assert_eq!( +// text, +// "This version has 2 feature flags, 1 of them enabled by default." +// ); + +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_web/src/file.rs b/crates/docs_rs_web/src/file.rs index c20762afd..18c2b03a0 100644 --- a/crates/docs_rs_web/src/file.rs +++ b/crates/docs_rs_web/src/file.rs @@ -1,7 +1,8 @@ //! Database based file handler use super::cache::CachePolicy; -use crate::{Config, error::Result}; +use crate::config::Config; +use anyhow::Result; use axum::{ body::Body, extract::Extension, @@ -85,165 +86,165 @@ impl StreamingFile { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::{storage::CompressionAlgorithm, test::TestEnvironment, web::headers::compute_etag}; - use axum_extra::headers::{ETag, HeaderMapExt as _}; - use chrono::Utc; - use http::header::{CACHE_CONTROL, ETAG, LAST_MODIFIED}; - use std::{io, rc::Rc}; - - fn streaming_blob( - content: impl Into>, - alg: Option, - ) -> StreamingBlob { - let content = content.into(); - StreamingBlob { - path: "some_path.db".into(), - mime: mime::APPLICATION_OCTET_STREAM, - date_updated: Utc::now(), - compression: alg, - etag: Some(compute_etag(&content)), - content_length: content.len(), - content: Box::new(io::Cursor::new(content)), - } - } - - #[tokio::test] - async fn test_stream_into_response() -> Result<()> { - const CONTENT: &[u8] = b"Hello, world!"; - let etag: ETag = { - // first request normal - let stream = StreamingFile(streaming_blob(CONTENT, None)); - let resp = stream.into_response(None); - assert!(resp.status().is_success()); - assert!(resp.headers().get(CACHE_CONTROL).is_none()); - let cache = resp - .extensions() - .get::() - .expect("missing cache response extension"); - assert!(matches!(cache, CachePolicy::ForeverInCdnAndBrowser)); - assert!(resp.headers().get(LAST_MODIFIED).is_some()); - - resp.headers().typed_get().unwrap() - }; - - let if_none_match = IfNoneMatch::from(etag); - - { - // cached request - let stream = StreamingFile(streaming_blob(CONTENT, None)); - let resp = stream.into_response(Some(&if_none_match)); - assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); - - // cache related headers are repeated on the not-modified response - assert!(resp.headers().get(CACHE_CONTROL).is_none()); - let cache = resp - .extensions() - .get::() - .expect("missing cache response extension"); - assert!(matches!(cache, CachePolicy::ForeverInCdnAndBrowser)); - assert!(resp.headers().get(LAST_MODIFIED).is_some()); - assert!(resp.headers().get(ETAG).is_some()); - } - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn file_roundtrip_axum() -> Result<()> { - let env = TestEnvironment::new().await?; - - let now = Utc::now(); - - env.fake_release().await.create().await?; - - let mut file = File::from_path( - env.async_storage(), - "rustdoc/fake-package/1.0.0/fake-package/index.html", - env.config(), - ) - .await?; - - file.0.date_updated = now; - - let resp = file.into_response(None); - assert!(resp.status().is_success()); - assert!(resp.headers().get(CACHE_CONTROL).is_none()); - let cache = resp - .extensions() - .get::() - .expect("missing cache response extension"); - assert!(matches!(cache, CachePolicy::ForeverInCdnAndBrowser)); - assert_eq!( - resp.headers().get(LAST_MODIFIED).unwrap(), - &now.format("%a, %d %b %Y %T GMT").to_string(), - ); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_max_size() -> Result<()> { - const MAX_SIZE: usize = 1024; - const MAX_HTML_SIZE: usize = 128; - - let env = Rc::new( - TestEnvironment::with_config( - TestEnvironment::base_config() - .max_file_size(MAX_SIZE) - .max_file_size_html(MAX_HTML_SIZE) - .build()?, - ) - .await?, - ); - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .rustdoc_file_with("small.html", &[b'A'; MAX_HTML_SIZE / 2] as &[u8]) - .rustdoc_file_with("exact.html", &[b'A'; MAX_HTML_SIZE] as &[u8]) - .rustdoc_file_with("big.html", &[b'A'; MAX_HTML_SIZE * 2] as &[u8]) - .rustdoc_file_with("small.js", &[b'A'; MAX_SIZE / 2] as &[u8]) - .rustdoc_file_with("exact.js", &[b'A'; MAX_SIZE] as &[u8]) - .rustdoc_file_with("big.js", &[b'A'; MAX_SIZE * 2] as &[u8]) - .create() - .await?; - - let file = |path| { - let env = env.clone(); - async move { - File::from_path( - env.async_storage(), - &format!("rustdoc/dummy/0.1.0/{path}"), - env.config(), - ) - .await - } - }; - let assert_len = |len, path| async move { - assert_eq!(len, file(path).await.unwrap().0.content.len()); - }; - let assert_too_big = |path| async move { - file(path) - .await - .unwrap_err() - .downcast_ref::() - .and_then(|io| io.get_ref()) - .and_then(|err| err.downcast_ref::()) - .is_some() - }; - - assert_len(MAX_HTML_SIZE / 2, "small.html").await; - assert_len(MAX_HTML_SIZE, "exact.html").await; - assert_len(MAX_SIZE / 2, "small.js").await; - assert_len(MAX_SIZE, "exact.js").await; - - assert_too_big("big.html").await; - assert_too_big("big.js").await; - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{storage::CompressionAlgorithm, test::TestEnvironment, web::headers::compute_etag}; +// use axum_extra::headers::{ETag, HeaderMapExt as _}; +// use chrono::Utc; +// use http::header::{CACHE_CONTROL, ETAG, LAST_MODIFIED}; +// use std::{io, rc::Rc}; + +// fn streaming_blob( +// content: impl Into>, +// alg: Option, +// ) -> StreamingBlob { +// let content = content.into(); +// StreamingBlob { +// path: "some_path.db".into(), +// mime: mime::APPLICATION_OCTET_STREAM, +// date_updated: Utc::now(), +// compression: alg, +// etag: Some(compute_etag(&content)), +// content_length: content.len(), +// content: Box::new(io::Cursor::new(content)), +// } +// } + +// #[tokio::test] +// async fn test_stream_into_response() -> Result<()> { +// const CONTENT: &[u8] = b"Hello, world!"; +// let etag: ETag = { +// // first request normal +// let stream = StreamingFile(streaming_blob(CONTENT, None)); +// let resp = stream.into_response(None); +// assert!(resp.status().is_success()); +// assert!(resp.headers().get(CACHE_CONTROL).is_none()); +// let cache = resp +// .extensions() +// .get::() +// .expect("missing cache response extension"); +// assert!(matches!(cache, CachePolicy::ForeverInCdnAndBrowser)); +// assert!(resp.headers().get(LAST_MODIFIED).is_some()); + +// resp.headers().typed_get().unwrap() +// }; + +// let if_none_match = IfNoneMatch::from(etag); + +// { +// // cached request +// let stream = StreamingFile(streaming_blob(CONTENT, None)); +// let resp = stream.into_response(Some(&if_none_match)); +// assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); + +// // cache related headers are repeated on the not-modified response +// assert!(resp.headers().get(CACHE_CONTROL).is_none()); +// let cache = resp +// .extensions() +// .get::() +// .expect("missing cache response extension"); +// assert!(matches!(cache, CachePolicy::ForeverInCdnAndBrowser)); +// assert!(resp.headers().get(LAST_MODIFIED).is_some()); +// assert!(resp.headers().get(ETAG).is_some()); +// } + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn file_roundtrip_axum() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// let now = Utc::now(); + +// env.fake_release().await.create().await?; + +// let mut file = File::from_path( +// env.async_storage(), +// "rustdoc/fake-package/1.0.0/fake-package/index.html", +// env.config(), +// ) +// .await?; + +// file.0.date_updated = now; + +// let resp = file.into_response(None); +// assert!(resp.status().is_success()); +// assert!(resp.headers().get(CACHE_CONTROL).is_none()); +// let cache = resp +// .extensions() +// .get::() +// .expect("missing cache response extension"); +// assert!(matches!(cache, CachePolicy::ForeverInCdnAndBrowser)); +// assert_eq!( +// resp.headers().get(LAST_MODIFIED).unwrap(), +// &now.format("%a, %d %b %Y %T GMT").to_string(), +// ); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_max_size() -> Result<()> { +// const MAX_SIZE: usize = 1024; +// const MAX_HTML_SIZE: usize = 128; + +// let env = Rc::new( +// TestEnvironment::with_config( +// TestEnvironment::base_config() +// .max_file_size(MAX_SIZE) +// .max_file_size_html(MAX_HTML_SIZE) +// .build()?, +// ) +// .await?, +// ); + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .rustdoc_file_with("small.html", &[b'A'; MAX_HTML_SIZE / 2] as &[u8]) +// .rustdoc_file_with("exact.html", &[b'A'; MAX_HTML_SIZE] as &[u8]) +// .rustdoc_file_with("big.html", &[b'A'; MAX_HTML_SIZE * 2] as &[u8]) +// .rustdoc_file_with("small.js", &[b'A'; MAX_SIZE / 2] as &[u8]) +// .rustdoc_file_with("exact.js", &[b'A'; MAX_SIZE] as &[u8]) +// .rustdoc_file_with("big.js", &[b'A'; MAX_SIZE * 2] as &[u8]) +// .create() +// .await?; + +// let file = |path| { +// let env = env.clone(); +// async move { +// File::from_path( +// env.async_storage(), +// &format!("rustdoc/dummy/0.1.0/{path}"), +// env.config(), +// ) +// .await +// } +// }; +// let assert_len = |len, path| async move { +// assert_eq!(len, file(path).await.unwrap().0.content.len()); +// }; +// let assert_too_big = |path| async move { +// file(path) +// .await +// .unwrap_err() +// .downcast_ref::() +// .and_then(|io| io.get_ref()) +// .and_then(|err| err.downcast_ref::()) +// .is_some() +// }; + +// assert_len(MAX_HTML_SIZE / 2, "small.html").await; +// assert_len(MAX_HTML_SIZE, "exact.html").await; +// assert_len(MAX_SIZE / 2, "small.js").await; +// assert_len(MAX_SIZE, "exact.js").await; + +// assert_too_big("big.html").await; +// assert_too_big("big.js").await; + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_web/src/highlight.rs b/crates/docs_rs_web/src/highlight.rs index 0c6bb9a10..f6e1fbfdd 100644 --- a/crates/docs_rs_web/src/highlight.rs +++ b/crates/docs_rs_web/src/highlight.rs @@ -1,10 +1,10 @@ -use crate::error::Result; use std::sync::LazyLock; use syntect::{ html::{ClassStyle, ClassedHTMLGenerator}, parsing::{SyntaxReference, SyntaxSet}, util::LinesWithEndings, }; +use tracing::{debug, error, info}; const TOTAL_CODE_BYTE_LENGTH_LIMIT: usize = 2 * 1024 * 1024; const PER_LINE_BYTE_LENGTH_LIMIT: usize = 512; @@ -23,7 +23,7 @@ static SYNTAXES: LazyLock = LazyLock::new(|| { .iter() .map(|s| &s.name) .collect::>(); - log::debug!("known syntaxes {names:?}"); + debug!("known syntaxes {names:?}"); syntaxes }); @@ -78,11 +78,11 @@ pub fn with_lang(lang: Option<&str>, code: &str, default: Option<&str>) -> Strin Ok(highlighted) => highlighted, Err(err) => { if err.is::() { - log::debug!("hit limit while highlighting code"); + debug!("hit limit while highlighting code"); } else { - log::error!("failed while highlighting code: {err:?}"); + error!("failed while highlighting code: {err:?}"); } - crate::web::page::templates::filters::escape_html(code, &()) + crate::page::templates::filters::escape_html(code, &()) .map(|s| s.to_string()) .unwrap_or_default() } diff --git a/crates/docs_rs_web/src/lib.rs b/crates/docs_rs_web/src/lib.rs index a44e5ecbf..2dd27a8fb 100644 --- a/crates/docs_rs_web/src/lib.rs +++ b/crates/docs_rs_web/src/lib.rs @@ -3,6 +3,9 @@ pub mod page; use docs_rs_database::types::BuildStatus; +pub use docs_rs_utils::{BUILD_VERSION, DEFAULT_MAX_TARGETS}; +pub use font_awesome_as_a_crate::icons; + // use crate::{ // utils::get_correct_docsrs_style_file, // web::{ @@ -21,6 +24,7 @@ use tracing::{info, instrument}; mod build_details; mod builds; pub(crate) mod cache; +pub(crate) mod config; pub(crate) mod crate_details; mod csp; pub(crate) mod error; @@ -38,8 +42,8 @@ mod sitemap; mod source; mod statics; mod status; +pub(crate) mod utils; -use crate::{Context, impl_axum_webpage}; use anyhow::Error; use axum::{ Router as AxumRouter, @@ -66,6 +70,18 @@ use tower::ServiceBuilder; use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::TraceLayer}; use self::crate_details::Release; +use page::GlobalAlert; + +// Warning message shown in the navigation bar of every page. Set to `None` to hide it. +pub(crate) static GLOBAL_ALERT: Option = None; +/* +pub(crate) static GLOBAL_ALERT: Option = Some(GlobalAlert { + url: "https://blog.rust-lang.org/2019/09/18/upcoming-docsrs-changes.html", + text: "Upcoming docs.rs breaking changes!", + css_class: "error", + fa_icon: "exclamation-triangle", +}); +*/ const DEFAULT_BIND: SocketAddr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 3000); @@ -650,7 +666,6 @@ pub(crate) struct MetaData { } impl MetaData { - #[fn_error_context::context("getting metadata for {name} {version}")] async fn from_crate( conn: &mut sqlx::PgConnection, name: &str, @@ -726,628 +741,628 @@ impl_axum_webpage! { } -#[cfg(test)] -mod test { - use super::*; - use crate::test::{ - AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestDatabase, TestEnvironment, - async_wrapper, - }; - use crate::{db::ReleaseId, docbuilder::DocCoverage}; - use kuchikiki::traits::TendrilSink; - use pretty_assertions::assert_eq; - use serde_json::json; - use test_case::test_case; - - async fn release(version: &str, env: &TestEnvironment) -> ReleaseId { - let version = Version::parse(version).unwrap(); - env.fake_release() - .await - .name("foo") - .version(version) - .create() - .await - .unwrap() - } - - async fn version(v: Option<&str>, db: &TestDatabase) -> Option { - let mut conn = db.async_conn().await; - let version = match_version( - &mut conn, - "foo", - &ReqVersion::from_str(v.unwrap_or_default()).unwrap(), - ) - .await - .ok()? - .assume_exact_name() - .ok()? - .into_version(); - Some(version) - } - - #[allow(clippy::unnecessary_wraps)] - fn semver(version: &'static str) -> Option { - version.parse().ok() - } - - #[allow(clippy::unnecessary_wraps)] - fn exact(version: &'static str) -> Option { - version.parse().ok() - } - - async fn clipboard_is_present_for_path(path: &str, web: &axum::Router) -> bool { - let data = web.get(path).await.unwrap().text().await.unwrap(); - let node = kuchikiki::parse_html().one(data); - node.select("#clipboard").unwrap().count() == 1 - } - - #[test] - fn test_index_returns_success() { - async_wrapper(|env| async move { - let web = env.web_app().await; - assert!(web.get("/").await?.status().is_success()); - Ok(()) - }); - } - - #[test] - fn test_doc_coverage_for_crate_pages() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.0.1") - .source_file("test.rs", &[]) - .doc_coverage(DocCoverage { - total_items: 10, - documented_items: 6, - total_items_needing_examples: 2, - items_with_examples: 1, - }) - .create() - .await?; - let web = env.web_app().await; - - let foo_crate = kuchikiki::parse_html() - .one(web.assert_success("/crate/foo/0.0.1").await?.text().await?); - - for (idx, value) in ["60%", "6", "10", "2", "1"].iter().enumerate() { - let mut menu_items = foo_crate.select(".pure-menu-item b").unwrap(); - assert!( - menu_items.any(|e| e.text_contents().contains(value)), - "({idx}, {value:?})" - ); - } - - let foo_doc = kuchikiki::parse_html() - .one(web.assert_success("/foo/0.0.1/foo/").await?.text().await?); - assert!( - foo_doc - .select(".pure-menu-link b") - .unwrap() - .any(|e| e.text_contents().contains("60%")) - ); - - Ok(()) - }); - } - - #[test] - fn test_show_clipboard_for_crate_pages() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake_crate") - .version("0.0.1") - .source_file("test.rs", &[]) - .create() - .await?; - let web = env.web_app().await; - assert!(clipboard_is_present_for_path("/crate/fake_crate/0.0.1", &web).await); - assert!(clipboard_is_present_for_path("/crate/fake_crate/0.0.1/source/", &web).await); - assert!(clipboard_is_present_for_path("/fake_crate/0.0.1/fake_crate/", &web).await); - Ok(()) - }); - } - - #[test] - fn test_hide_clipboard_for_non_crate_pages() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake_crate") - .version("0.0.1") - .create() - .await?; - let web = env.web_app().await; - assert!(!clipboard_is_present_for_path("/about", &web).await); - assert!(!clipboard_is_present_for_path("/releases", &web).await); - assert!(!clipboard_is_present_for_path("/", &web).await); - assert!(!clipboard_is_present_for_path("/not/a/real/path", &web).await); - Ok(()) - }); - } - - #[test] - fn standard_library_redirects() { - async fn assert_external_redirect_success( - web: &axum::Router, - path: &str, - expected_target: &str, - ) -> Result<()> { - let redirect_response = web.assert_redirect_unchecked(path, expected_target).await?; - - let external_target_url = redirect_response.redirect_target().unwrap(); - - let response = reqwest::get(external_target_url).await?; - let status = response.status(); - assert!( - status.is_success(), - "failed to GET {external_target_url}: {status}" - ); - Ok(()) - } - - async_wrapper(|env| async move { - let web = env.web_app().await; - for krate in &["std", "alloc", "core", "proc_macro", "test"] { - let target = format!("https://doc.rust-lang.org/stable/{krate}/"); - - // with or without slash - assert_external_redirect_success(&web, &format!("/{krate}"), &target).await?; - assert_external_redirect_success(&web, &format!("/{krate}/"), &target).await?; - } - - let target = "https://doc.rust-lang.org/stable/proc_macro/"; - // with or without slash - assert_external_redirect_success(&web, "/proc-macro", target).await?; - assert_external_redirect_success(&web, "/proc-macro/", target).await?; - - let target = "https://doc.rust-lang.org/nightly/nightly-rustc/"; - // with or without slash - assert_external_redirect_success(&web, "/rustc", target).await?; - assert_external_redirect_success(&web, "/rustc/", target).await?; - - let target = "https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/"; - // with or without slash - assert_external_redirect_success(&web, "/rustdoc", target).await?; - assert_external_redirect_success(&web, "/rustdoc/", target).await?; - - // queries are supported - assert_external_redirect_success( - &web, - "/std?search=foobar", - "https://doc.rust-lang.org/stable/std/?search=foobar", - ) - .await?; - - Ok(()) - }) - } - - #[test] - fn double_slash_does_redirect_to_latest_version() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("bat") - .version("0.2.0") - .create() - .await?; - let web = env.web_app().await; - web.assert_redirect("/bat//", "/bat/latest/bat/").await?; - Ok(()) - }) - } - - #[test] - fn binary_docs_redirect_to_crate() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("bat") - .version("0.2.0") - .binary(true) - .create() - .await?; - let web = env.web_app().await; - web.assert_redirect("/bat/0.2.0", "/crate/bat/0.2.0") - .await?; - web.assert_redirect("/bat/0.2.0/aarch64-unknown-linux-gnu", "/crate/bat/0.2.0") - .await?; - /* TODO: this should work (https://github.com/rust-lang/docs.rs/issues/603) - assert_redirect("/bat/0.2.0/aarch64-unknown-linux-gnu/bat", "/crate/bat/0.2.0", web)?; - assert_redirect("/bat/0.2.0/aarch64-unknown-linux-gnu/bat/", "/crate/bat/0.2.0/", web)?; - */ - Ok(()) - }) - } - - #[test] - fn can_view_source() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("regex") - .version("0.3.0") - .source_file("src/main.rs", br#"println!("definitely valid rust")"#) - .create() - .await?; - - let web = env.web_app().await; - web.assert_success("/crate/regex/0.3.0/source/src/main.rs") - .await?; - web.assert_success("/crate/regex/0.3.0/source/").await?; - web.assert_success("/crate/regex/0.3.0/source/src").await?; - web.assert_success("/regex/0.3.0/src/regex/main.rs.html") - .await?; - Ok(()) - }) - } - - #[test] - // https://github.com/rust-lang/docs.rs/issues/223 - fn prereleases_are_not_considered_for_semver() { - async_wrapper(|env| async move { - let db = env.async_db(); - let version = |v| version(v, db); - let release = |v| release(v, &env); - - release("0.3.1-pre").await; - for search in &["*", "newest", "latest"] { - assert_eq!(version(Some(search)).await, semver("0.3.1-pre")); - } - - release("0.3.1-alpha").await; - assert_eq!(version(Some("0.3.1-alpha")).await, exact("0.3.1-alpha")); - - release("0.3.0").await; - let three = semver("0.3.0"); - assert_eq!(version(None).await, three); - // same thing but with "*" - assert_eq!(version(Some("*")).await, three); - // make sure exact matches still work - assert_eq!(version(Some("0.3.0")).await, exact("0.3.0")); - - Ok(()) - }); - } - - #[test] - fn platform_dropdown_not_shown_with_no_targets() { - async_wrapper(|env| async move { - release("0.1.0", &env).await; - let web = env.web_app().await; - let text = web.get("/foo/0.1.0/foo").await?.text().await?; - let platform = kuchikiki::parse_html() - .one(text) - .select(r#"ul > li > a[aria-label="Platform"]"#) - .unwrap() - .count(); - assert_eq!(platform, 0); - - // sanity check the test is doing something - env.fake_release() - .await - .name("foo") - .version("0.2.0") - .add_platform("x86_64-unknown-linux-musl") - .create() - .await?; - let text = web.assert_success("/foo/0.2.0/foo/").await?.text().await?; - let platform = kuchikiki::parse_html() - .one(text) - .select(r#"ul > li > a[aria-label="Platform"]"#) - .unwrap() - .count(); - assert_eq!(platform, 1); - Ok(()) - }); - } - - #[test] - // https://github.com/rust-lang/docs.rs/issues/221 - fn yanked_crates_are_not_considered() { - async_wrapper(|env| async move { - let db = env.async_db(); - - let release_id = release("0.3.0", &env).await; - - sqlx::query!( - "UPDATE releases SET yanked = true WHERE id = $1 AND version = '0.3.0'", - release_id.0 - ) - .execute(&mut *db.async_conn().await) - .await?; - - assert_eq!(version(None, db).await, None); - assert_eq!(version(Some("0.3"), db).await, None); - - release("0.1.0+4.1", &env).await; - assert_eq!(version(Some("0.1.0+4.1"), db).await, exact("0.1.0+4.1")); - assert_eq!(version(None, db).await, semver("0.1.0+4.1")); - - Ok(()) - }); - } - - #[test] - fn in_progress_releases_are_ignored_when_others_match() { - async_wrapper(|env| async move { - let db = env.async_db(); - - // normal release - release("1.0.0", &env).await; - - // in progress release - env.fake_release() - .await - .name("foo") - .version("1.1.0") - .builds(vec![ - FakeBuild::default().build_status(BuildStatus::InProgress), - ]) - .create() - .await?; - - // STAR gives me the prod release - assert_eq!(version(Some("*"), db).await, exact("1.0.0")); - - // exact-match query gives me the in progress release - assert_eq!(version(Some("=1.1.0"), db).await, exact("1.1.0")); - - Ok(()) - }) - } - - #[test] - // https://github.com/rust-lang/docs.rs/issues/1682 - fn prereleases_are_considered_when_others_dont_match() { - async_wrapper(|env| async move { - let db = env.async_db(); - - // normal release - release("1.0.0", &env).await; - // prereleases - release("2.0.0-alpha.1", &env).await; - release("2.0.0-alpha.2", &env).await; - - // STAR gives me the prod release - assert_eq!(version(Some("*"), db).await, exact("1.0.0")); - - // prerelease query gives me the latest prerelease - assert_eq!( - version(Some(">=2.0.0-alpha"), db).await, - exact("2.0.0-alpha.2") - ); - - Ok(()) - }) - } - - #[test] - // vaguely related to https://github.com/rust-lang/docs.rs/issues/395 - fn metadata_has_no_effect() { - async_wrapper(|env| async move { - let db = env.async_db(); - - release("0.1.0+4.1", &env).await; - release("0.1.1", &env).await; - assert_eq!(version(None, db).await, semver("0.1.1")); - release("0.5.1+zstd.1.4.4", &env).await; - assert_eq!(version(None, db).await, semver("0.5.1+zstd.1.4.4")); - assert_eq!(version(Some("0.5"), db).await, semver("0.5.1+zstd.1.4.4")); - assert_eq!( - version(Some("0.5.1+zstd.1.4.4"), db).await, - exact("0.5.1+zstd.1.4.4") - ); - - Ok(()) - }); - } - - #[test] - fn serialize_metadata() { - let mut metadata = MetaData { - name: "serde".parse().unwrap(), - version: "1.0.0".parse().unwrap(), - req_version: ReqVersion::Latest, - description: Some("serde does stuff".to_string()), - target_name: None, - rustdoc_status: Some(true), - default_target: Some("x86_64-unknown-linux-gnu".to_string()), - doc_targets: Some(vec![ - "x86_64-unknown-linux-gnu".to_string(), - "arm64-unknown-linux-gnu".to_string(), - ]), - yanked: Some(false), - rustdoc_css_file: Some("rustdoc.css".to_string()), - }; - - let correct_json = json!({ - "name": "serde", - "version": "1.0.0", - "req_version": "latest", - "description": "serde does stuff", - "target_name": null, - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu", - "doc_targets": [ - "x86_64-unknown-linux-gnu", - "arm64-unknown-linux-gnu", - ], - "yanked": false, - "rustdoc_css_file": "rustdoc.css", - }); - - assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap()); - - metadata.target_name = Some("serde_lib_name".to_string()); - let correct_json = json!({ - "name": "serde", - "version": "1.0.0", - "req_version": "latest", - "description": "serde does stuff", - "target_name": "serde_lib_name", - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu", - "doc_targets": [ - "x86_64-unknown-linux-gnu", - "arm64-unknown-linux-gnu", - ], - "yanked": false, - "rustdoc_css_file": "rustdoc.css", - }); - - assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap()); - - metadata.description = None; - let correct_json = json!({ - "name": "serde", - "version": "1.0.0", - "req_version": "latest", - "description": null, - "target_name": "serde_lib_name", - "rustdoc_status": true, - "default_target": "x86_64-unknown-linux-gnu", - "doc_targets": [ - "x86_64-unknown-linux-gnu", - "arm64-unknown-linux-gnu", - ], - "yanked": false, - "rustdoc_css_file": "rustdoc.css", - }); - - assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap()); - } - - #[test] - fn metadata_from_crate() { - async_wrapper(|env| async move { - release("0.1.0", &env).await; - let mut conn = env.async_db().async_conn().await; - let metadata = MetaData::from_crate( - &mut conn, - "foo", - &"0.1.0".parse().unwrap(), - Some(ReqVersion::Latest), - ) - .await; - assert_eq!( - metadata.unwrap(), - MetaData { - name: "foo".parse().unwrap(), - version: "0.1.0".parse().unwrap(), - req_version: ReqVersion::Latest, - description: Some("Fake package".to_string()), - target_name: Some("foo".to_string()), - rustdoc_status: Some(true), - default_target: Some("x86_64-unknown-linux-gnu".to_string()), - doc_targets: Some(vec!["x86_64-unknown-linux-gnu".to_string()]), - yanked: Some(false), - rustdoc_css_file: Some("rustdoc.css".to_string()), - }, - ); - Ok(()) - }) - } - - #[test] - fn test_tabindex_is_present_on_topbar_crate_search_input() { - async_wrapper(|env| async move { - release("0.1.0", &env).await; - let web = env.web_app().await; - let text = web.assert_success("/foo/0.1.0/foo/").await?.text().await?; - let tabindex = kuchikiki::parse_html() - .one(text) - .select(r#"#nav-search[tabindex="-1"]"#) - .unwrap() - .count(); - assert_eq!(tabindex, 1); - Ok(()) - }); - } - - #[test] - fn test_axum_redirect() { - let response = axum_redirect("/something").unwrap().into_response(); - assert_eq!(response.status(), StatusCode::FOUND); - assert_eq!( - response.headers().get(http::header::LOCATION).unwrap(), - "/something" - ); - assert!( - response - .headers() - .get(http::header::CACHE_CONTROL) - .is_none() - ); - assert!(response.extensions().get::().is_none()); - } - - #[test] - fn test_axum_redirect_cached() { - let response = axum_cached_redirect("/something", cache::CachePolicy::NoCaching) - .unwrap() - .into_response(); - assert_eq!(response.status(), StatusCode::FOUND); - assert_eq!( - response.headers().get(http::header::LOCATION).unwrap(), - "/something" - ); - assert!(matches!( - response.extensions().get::().unwrap(), - cache::CachePolicy::NoCaching, - )) - } - - #[test_case("without_leading_slash")] - #[test_case("//with_double_leading_slash")] - fn test_axum_redirect_failure(path: &str) { - assert!(axum_redirect(path).is_err()); - assert!(axum_cached_redirect(path, cache::CachePolicy::NoCaching).is_err()); - } - - #[test] - fn test_parse_req_version_latest() { - let req_version: ReqVersion = "latest".parse().unwrap(); - assert_eq!(req_version, ReqVersion::Latest); - assert_eq!(req_version.to_string(), "latest"); - } - - #[test_case("1.2.3")] - fn test_parse_req_version_exact(input: &str) { - let req_version: ReqVersion = input.parse().unwrap(); - assert_eq!( - req_version, - ReqVersion::Exact(Version::parse(input).unwrap()) - ); - assert_eq!(req_version.to_string(), input); - } - - #[test_case("^1.2.3")] - #[test_case("*")] - fn test_parse_req_version_semver(input: &str) { - let req_version: ReqVersion = input.parse().unwrap(); - assert_eq!( - req_version, - ReqVersion::Semver(VersionReq::parse(input).unwrap()) - ); - assert_eq!(req_version.to_string(), input); - } - - #[test_case("")] - #[test_case("newest")] - fn test_parse_req_version_semver_latest(input: &str) { - let req_version: ReqVersion = input.parse().unwrap(); - assert_eq!(req_version, ReqVersion::Semver(VersionReq::STAR)); - assert_eq!(req_version.to_string(), "*") - } - - #[test_case("/something/", "/something/")] // already valid path - #[test_case("/something>", "/something%3E")] // something to encode - #[test_case("/something%3E", "/something%3E")] // re-running doesn't change anything - fn test_encode_url_path(input: &str, expected: &str) { - assert_eq!(encode_url_path(input), expected); - } -} +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::test::{ +// AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestDatabase, TestEnvironment, +// async_wrapper, +// }; +// use crate::{db::ReleaseId, docbuilder::DocCoverage}; +// use kuchikiki::traits::TendrilSink; +// use pretty_assertions::assert_eq; +// use serde_json::json; +// use test_case::test_case; + +// async fn release(version: &str, env: &TestEnvironment) -> ReleaseId { +// let version = Version::parse(version).unwrap(); +// env.fake_release() +// .await +// .name("foo") +// .version(version) +// .create() +// .await +// .unwrap() +// } + +// async fn version(v: Option<&str>, db: &TestDatabase) -> Option { +// let mut conn = db.async_conn().await; +// let version = match_version( +// &mut conn, +// "foo", +// &ReqVersion::from_str(v.unwrap_or_default()).unwrap(), +// ) +// .await +// .ok()? +// .assume_exact_name() +// .ok()? +// .into_version(); +// Some(version) +// } + +// #[allow(clippy::unnecessary_wraps)] +// fn semver(version: &'static str) -> Option { +// version.parse().ok() +// } + +// #[allow(clippy::unnecessary_wraps)] +// fn exact(version: &'static str) -> Option { +// version.parse().ok() +// } + +// async fn clipboard_is_present_for_path(path: &str, web: &axum::Router) -> bool { +// let data = web.get(path).await.unwrap().text().await.unwrap(); +// let node = kuchikiki::parse_html().one(data); +// node.select("#clipboard").unwrap().count() == 1 +// } + +// #[test] +// fn test_index_returns_success() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// assert!(web.get("/").await?.status().is_success()); +// Ok(()) +// }); +// } + +// #[test] +// fn test_doc_coverage_for_crate_pages() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.0.1") +// .source_file("test.rs", &[]) +// .doc_coverage(DocCoverage { +// total_items: 10, +// documented_items: 6, +// total_items_needing_examples: 2, +// items_with_examples: 1, +// }) +// .create() +// .await?; +// let web = env.web_app().await; + +// let foo_crate = kuchikiki::parse_html() +// .one(web.assert_success("/crate/foo/0.0.1").await?.text().await?); + +// for (idx, value) in ["60%", "6", "10", "2", "1"].iter().enumerate() { +// let mut menu_items = foo_crate.select(".pure-menu-item b").unwrap(); +// assert!( +// menu_items.any(|e| e.text_contents().contains(value)), +// "({idx}, {value:?})" +// ); +// } + +// let foo_doc = kuchikiki::parse_html() +// .one(web.assert_success("/foo/0.0.1/foo/").await?.text().await?); +// assert!( +// foo_doc +// .select(".pure-menu-link b") +// .unwrap() +// .any(|e| e.text_contents().contains("60%")) +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_show_clipboard_for_crate_pages() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake_crate") +// .version("0.0.1") +// .source_file("test.rs", &[]) +// .create() +// .await?; +// let web = env.web_app().await; +// assert!(clipboard_is_present_for_path("/crate/fake_crate/0.0.1", &web).await); +// assert!(clipboard_is_present_for_path("/crate/fake_crate/0.0.1/source/", &web).await); +// assert!(clipboard_is_present_for_path("/fake_crate/0.0.1/fake_crate/", &web).await); +// Ok(()) +// }); +// } + +// #[test] +// fn test_hide_clipboard_for_non_crate_pages() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake_crate") +// .version("0.0.1") +// .create() +// .await?; +// let web = env.web_app().await; +// assert!(!clipboard_is_present_for_path("/about", &web).await); +// assert!(!clipboard_is_present_for_path("/releases", &web).await); +// assert!(!clipboard_is_present_for_path("/", &web).await); +// assert!(!clipboard_is_present_for_path("/not/a/real/path", &web).await); +// Ok(()) +// }); +// } + +// #[test] +// fn standard_library_redirects() { +// async fn assert_external_redirect_success( +// web: &axum::Router, +// path: &str, +// expected_target: &str, +// ) -> Result<()> { +// let redirect_response = web.assert_redirect_unchecked(path, expected_target).await?; + +// let external_target_url = redirect_response.redirect_target().unwrap(); + +// let response = reqwest::get(external_target_url).await?; +// let status = response.status(); +// assert!( +// status.is_success(), +// "failed to GET {external_target_url}: {status}" +// ); +// Ok(()) +// } + +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// for krate in &["std", "alloc", "core", "proc_macro", "test"] { +// let target = format!("https://doc.rust-lang.org/stable/{krate}/"); + +// // with or without slash +// assert_external_redirect_success(&web, &format!("/{krate}"), &target).await?; +// assert_external_redirect_success(&web, &format!("/{krate}/"), &target).await?; +// } + +// let target = "https://doc.rust-lang.org/stable/proc_macro/"; +// // with or without slash +// assert_external_redirect_success(&web, "/proc-macro", target).await?; +// assert_external_redirect_success(&web, "/proc-macro/", target).await?; + +// let target = "https://doc.rust-lang.org/nightly/nightly-rustc/"; +// // with or without slash +// assert_external_redirect_success(&web, "/rustc", target).await?; +// assert_external_redirect_success(&web, "/rustc/", target).await?; + +// let target = "https://doc.rust-lang.org/nightly/nightly-rustc/rustdoc/"; +// // with or without slash +// assert_external_redirect_success(&web, "/rustdoc", target).await?; +// assert_external_redirect_success(&web, "/rustdoc/", target).await?; + +// // queries are supported +// assert_external_redirect_success( +// &web, +// "/std?search=foobar", +// "https://doc.rust-lang.org/stable/std/?search=foobar", +// ) +// .await?; + +// Ok(()) +// }) +// } + +// #[test] +// fn double_slash_does_redirect_to_latest_version() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("bat") +// .version("0.2.0") +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_redirect("/bat//", "/bat/latest/bat/").await?; +// Ok(()) +// }) +// } + +// #[test] +// fn binary_docs_redirect_to_crate() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("bat") +// .version("0.2.0") +// .binary(true) +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_redirect("/bat/0.2.0", "/crate/bat/0.2.0") +// .await?; +// web.assert_redirect("/bat/0.2.0/aarch64-unknown-linux-gnu", "/crate/bat/0.2.0") +// .await?; +// /* TODO: this should work (https://github.com/rust-lang/docs.rs/issues/603) +// assert_redirect("/bat/0.2.0/aarch64-unknown-linux-gnu/bat", "/crate/bat/0.2.0", web)?; +// assert_redirect("/bat/0.2.0/aarch64-unknown-linux-gnu/bat/", "/crate/bat/0.2.0/", web)?; +// */ +// Ok(()) +// }) +// } + +// #[test] +// fn can_view_source() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("regex") +// .version("0.3.0") +// .source_file("src/main.rs", br#"println!("definitely valid rust")"#) +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_success("/crate/regex/0.3.0/source/src/main.rs") +// .await?; +// web.assert_success("/crate/regex/0.3.0/source/").await?; +// web.assert_success("/crate/regex/0.3.0/source/src").await?; +// web.assert_success("/regex/0.3.0/src/regex/main.rs.html") +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// // https://github.com/rust-lang/docs.rs/issues/223 +// fn prereleases_are_not_considered_for_semver() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let version = |v| version(v, db); +// let release = |v| release(v, &env); + +// release("0.3.1-pre").await; +// for search in &["*", "newest", "latest"] { +// assert_eq!(version(Some(search)).await, semver("0.3.1-pre")); +// } + +// release("0.3.1-alpha").await; +// assert_eq!(version(Some("0.3.1-alpha")).await, exact("0.3.1-alpha")); + +// release("0.3.0").await; +// let three = semver("0.3.0"); +// assert_eq!(version(None).await, three); +// // same thing but with "*" +// assert_eq!(version(Some("*")).await, three); +// // make sure exact matches still work +// assert_eq!(version(Some("0.3.0")).await, exact("0.3.0")); + +// Ok(()) +// }); +// } + +// #[test] +// fn platform_dropdown_not_shown_with_no_targets() { +// async_wrapper(|env| async move { +// release("0.1.0", &env).await; +// let web = env.web_app().await; +// let text = web.get("/foo/0.1.0/foo").await?.text().await?; +// let platform = kuchikiki::parse_html() +// .one(text) +// .select(r#"ul > li > a[aria-label="Platform"]"#) +// .unwrap() +// .count(); +// assert_eq!(platform, 0); + +// // sanity check the test is doing something +// env.fake_release() +// .await +// .name("foo") +// .version("0.2.0") +// .add_platform("x86_64-unknown-linux-musl") +// .create() +// .await?; +// let text = web.assert_success("/foo/0.2.0/foo/").await?.text().await?; +// let platform = kuchikiki::parse_html() +// .one(text) +// .select(r#"ul > li > a[aria-label="Platform"]"#) +// .unwrap() +// .count(); +// assert_eq!(platform, 1); +// Ok(()) +// }); +// } + +// #[test] +// // https://github.com/rust-lang/docs.rs/issues/221 +// fn yanked_crates_are_not_considered() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// let release_id = release("0.3.0", &env).await; + +// sqlx::query!( +// "UPDATE releases SET yanked = true WHERE id = $1 AND version = '0.3.0'", +// release_id.0 +// ) +// .execute(&mut *db.async_conn().await) +// .await?; + +// assert_eq!(version(None, db).await, None); +// assert_eq!(version(Some("0.3"), db).await, None); + +// release("0.1.0+4.1", &env).await; +// assert_eq!(version(Some("0.1.0+4.1"), db).await, exact("0.1.0+4.1")); +// assert_eq!(version(None, db).await, semver("0.1.0+4.1")); + +// Ok(()) +// }); +// } + +// #[test] +// fn in_progress_releases_are_ignored_when_others_match() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// // normal release +// release("1.0.0", &env).await; + +// // in progress release +// env.fake_release() +// .await +// .name("foo") +// .version("1.1.0") +// .builds(vec![ +// FakeBuild::default().build_status(BuildStatus::InProgress), +// ]) +// .create() +// .await?; + +// // STAR gives me the prod release +// assert_eq!(version(Some("*"), db).await, exact("1.0.0")); + +// // exact-match query gives me the in progress release +// assert_eq!(version(Some("=1.1.0"), db).await, exact("1.1.0")); + +// Ok(()) +// }) +// } + +// #[test] +// // https://github.com/rust-lang/docs.rs/issues/1682 +// fn prereleases_are_considered_when_others_dont_match() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// // normal release +// release("1.0.0", &env).await; +// // prereleases +// release("2.0.0-alpha.1", &env).await; +// release("2.0.0-alpha.2", &env).await; + +// // STAR gives me the prod release +// assert_eq!(version(Some("*"), db).await, exact("1.0.0")); + +// // prerelease query gives me the latest prerelease +// assert_eq!( +// version(Some(">=2.0.0-alpha"), db).await, +// exact("2.0.0-alpha.2") +// ); + +// Ok(()) +// }) +// } + +// #[test] +// // vaguely related to https://github.com/rust-lang/docs.rs/issues/395 +// fn metadata_has_no_effect() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// release("0.1.0+4.1", &env).await; +// release("0.1.1", &env).await; +// assert_eq!(version(None, db).await, semver("0.1.1")); +// release("0.5.1+zstd.1.4.4", &env).await; +// assert_eq!(version(None, db).await, semver("0.5.1+zstd.1.4.4")); +// assert_eq!(version(Some("0.5"), db).await, semver("0.5.1+zstd.1.4.4")); +// assert_eq!( +// version(Some("0.5.1+zstd.1.4.4"), db).await, +// exact("0.5.1+zstd.1.4.4") +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn serialize_metadata() { +// let mut metadata = MetaData { +// name: "serde".parse().unwrap(), +// version: "1.0.0".parse().unwrap(), +// req_version: ReqVersion::Latest, +// description: Some("serde does stuff".to_string()), +// target_name: None, +// rustdoc_status: Some(true), +// default_target: Some("x86_64-unknown-linux-gnu".to_string()), +// doc_targets: Some(vec![ +// "x86_64-unknown-linux-gnu".to_string(), +// "arm64-unknown-linux-gnu".to_string(), +// ]), +// yanked: Some(false), +// rustdoc_css_file: Some("rustdoc.css".to_string()), +// }; + +// let correct_json = json!({ +// "name": "serde", +// "version": "1.0.0", +// "req_version": "latest", +// "description": "serde does stuff", +// "target_name": null, +// "rustdoc_status": true, +// "default_target": "x86_64-unknown-linux-gnu", +// "doc_targets": [ +// "x86_64-unknown-linux-gnu", +// "arm64-unknown-linux-gnu", +// ], +// "yanked": false, +// "rustdoc_css_file": "rustdoc.css", +// }); + +// assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap()); + +// metadata.target_name = Some("serde_lib_name".to_string()); +// let correct_json = json!({ +// "name": "serde", +// "version": "1.0.0", +// "req_version": "latest", +// "description": "serde does stuff", +// "target_name": "serde_lib_name", +// "rustdoc_status": true, +// "default_target": "x86_64-unknown-linux-gnu", +// "doc_targets": [ +// "x86_64-unknown-linux-gnu", +// "arm64-unknown-linux-gnu", +// ], +// "yanked": false, +// "rustdoc_css_file": "rustdoc.css", +// }); + +// assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap()); + +// metadata.description = None; +// let correct_json = json!({ +// "name": "serde", +// "version": "1.0.0", +// "req_version": "latest", +// "description": null, +// "target_name": "serde_lib_name", +// "rustdoc_status": true, +// "default_target": "x86_64-unknown-linux-gnu", +// "doc_targets": [ +// "x86_64-unknown-linux-gnu", +// "arm64-unknown-linux-gnu", +// ], +// "yanked": false, +// "rustdoc_css_file": "rustdoc.css", +// }); + +// assert_eq!(correct_json, serde_json::to_value(&metadata).unwrap()); +// } + +// #[test] +// fn metadata_from_crate() { +// async_wrapper(|env| async move { +// release("0.1.0", &env).await; +// let mut conn = env.async_db().async_conn().await; +// let metadata = MetaData::from_crate( +// &mut conn, +// "foo", +// &"0.1.0".parse().unwrap(), +// Some(ReqVersion::Latest), +// ) +// .await; +// assert_eq!( +// metadata.unwrap(), +// MetaData { +// name: "foo".parse().unwrap(), +// version: "0.1.0".parse().unwrap(), +// req_version: ReqVersion::Latest, +// description: Some("Fake package".to_string()), +// target_name: Some("foo".to_string()), +// rustdoc_status: Some(true), +// default_target: Some("x86_64-unknown-linux-gnu".to_string()), +// doc_targets: Some(vec!["x86_64-unknown-linux-gnu".to_string()]), +// yanked: Some(false), +// rustdoc_css_file: Some("rustdoc.css".to_string()), +// }, +// ); +// Ok(()) +// }) +// } + +// #[test] +// fn test_tabindex_is_present_on_topbar_crate_search_input() { +// async_wrapper(|env| async move { +// release("0.1.0", &env).await; +// let web = env.web_app().await; +// let text = web.assert_success("/foo/0.1.0/foo/").await?.text().await?; +// let tabindex = kuchikiki::parse_html() +// .one(text) +// .select(r#"#nav-search[tabindex="-1"]"#) +// .unwrap() +// .count(); +// assert_eq!(tabindex, 1); +// Ok(()) +// }); +// } + +// #[test] +// fn test_axum_redirect() { +// let response = axum_redirect("/something").unwrap().into_response(); +// assert_eq!(response.status(), StatusCode::FOUND); +// assert_eq!( +// response.headers().get(http::header::LOCATION).unwrap(), +// "/something" +// ); +// assert!( +// response +// .headers() +// .get(http::header::CACHE_CONTROL) +// .is_none() +// ); +// assert!(response.extensions().get::().is_none()); +// } + +// #[test] +// fn test_axum_redirect_cached() { +// let response = axum_cached_redirect("/something", cache::CachePolicy::NoCaching) +// .unwrap() +// .into_response(); +// assert_eq!(response.status(), StatusCode::FOUND); +// assert_eq!( +// response.headers().get(http::header::LOCATION).unwrap(), +// "/something" +// ); +// assert!(matches!( +// response.extensions().get::().unwrap(), +// cache::CachePolicy::NoCaching, +// )) +// } + +// #[test_case("without_leading_slash")] +// #[test_case("//with_double_leading_slash")] +// fn test_axum_redirect_failure(path: &str) { +// assert!(axum_redirect(path).is_err()); +// assert!(axum_cached_redirect(path, cache::CachePolicy::NoCaching).is_err()); +// } + +// #[test] +// fn test_parse_req_version_latest() { +// let req_version: ReqVersion = "latest".parse().unwrap(); +// assert_eq!(req_version, ReqVersion::Latest); +// assert_eq!(req_version.to_string(), "latest"); +// } + +// #[test_case("1.2.3")] +// fn test_parse_req_version_exact(input: &str) { +// let req_version: ReqVersion = input.parse().unwrap(); +// assert_eq!( +// req_version, +// ReqVersion::Exact(Version::parse(input).unwrap()) +// ); +// assert_eq!(req_version.to_string(), input); +// } + +// #[test_case("^1.2.3")] +// #[test_case("*")] +// fn test_parse_req_version_semver(input: &str) { +// let req_version: ReqVersion = input.parse().unwrap(); +// assert_eq!( +// req_version, +// ReqVersion::Semver(VersionReq::parse(input).unwrap()) +// ); +// assert_eq!(req_version.to_string(), input); +// } + +// #[test_case("")] +// #[test_case("newest")] +// fn test_parse_req_version_semver_latest(input: &str) { +// let req_version: ReqVersion = input.parse().unwrap(); +// assert_eq!(req_version, ReqVersion::Semver(VersionReq::STAR)); +// assert_eq!(req_version.to_string(), "*") +// } + +// #[test_case("/something/", "/something/")] // already valid path +// #[test_case("/something>", "/something%3E")] // something to encode +// #[test_case("/something%3E", "/something%3E")] // re-running doesn't change anything +// fn test_encode_url_path(input: &str, expected: &str) { +// assert_eq!(encode_url_path(input), expected); +// } +// } diff --git a/crates/docs_rs_web/src/markdown.rs b/crates/docs_rs_web/src/markdown.rs index 79998b6c4..240974e13 100644 --- a/crates/docs_rs_web/src/markdown.rs +++ b/crates/docs_rs_web/src/markdown.rs @@ -1,4 +1,4 @@ -use crate::web::highlight; +use crate::highlight; use comrak::{Options, adapters::SyntaxHighlighterAdapter, options}; use std::{borrow::Cow, collections::HashMap, fmt}; @@ -104,42 +104,42 @@ pub fn render_with_default(text: &str, default: &'static str) -> String { render_with_highlighter(text, Some(default), highlight::with_lang) } -#[cfg(test)] -mod test { - use super::render_with_highlighter; - use indoc::indoc; - use std::sync::Mutex; +// #[cfg(test)] +// mod test { +// use super::render_with_highlighter; +// use indoc::indoc; +// use std::sync::Mutex; - #[test] - fn ignore_info_string_attributes() { - let highlighted = Mutex::new(vec![]); +// #[test] +// fn ignore_info_string_attributes() { +// let highlighted = Mutex::new(vec![]); - let output = render_with_highlighter( - indoc! {" - ```rust,ignore - ignore::commas(); - ``` +// let output = render_with_highlighter( +// indoc! {" +// ```rust,ignore +// ignore::commas(); +// ``` - ```rust ignore - ignore::spaces(); - ``` - "}, - None, - |lang, code, _| { - let mut highlighted = highlighted.lock().unwrap(); - highlighted.push((lang.map(str::to_owned), code.to_owned())); - code.to_owned() - }, - ); +// ```rust ignore +// ignore::spaces(); +// ``` +// "}, +// None, +// |lang, code, _| { +// let mut highlighted = highlighted.lock().unwrap(); +// highlighted.push((lang.map(str::to_owned), code.to_owned())); +// code.to_owned() +// }, +// ); - assert!(output.matches(r#""#).count() == 2); - let highlighted = highlighted.lock().unwrap(); - assert_eq!( - highlighted.as_slice(), - [ - (Some("rust".into()), "ignore::commas();\n".into()), - (Some("rust".into()), "ignore::spaces();\n".into()) - ] - ); - } -} +// assert!(output.matches(r#""#).count() == 2); +// let highlighted = highlighted.lock().unwrap(); +// assert_eq!( +// highlighted.as_slice(), +// [ +// (Some("rust".into()), "ignore::commas();\n".into()), +// (Some("rust".into()), "ignore::spaces();\n".into()) +// ] +// ); +// } +// } diff --git a/crates/docs_rs_web/src/metrics.rs b/crates/docs_rs_web/src/metrics.rs index fdbd3953d..856a05f52 100644 --- a/crates/docs_rs_web/src/metrics.rs +++ b/crates/docs_rs_web/src/metrics.rs @@ -116,178 +116,178 @@ pub(crate) async fn request_recorder( result } -#[cfg(test)] -mod tests { - use crate::test::{AxumRouterTestExt, async_wrapper}; - use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; - use pretty_assertions::assert_eq; - use std::collections::HashMap; +// #[cfg(test)] +// mod tests { +// use crate::test::{AxumRouterTestExt, async_wrapper}; +// use opentelemetry_sdk::metrics::data::{AggregatedMetrics, MetricData}; +// use pretty_assertions::assert_eq; +// use std::collections::HashMap; - #[test] - fn test_response_times_count_being_collected() { - const ROUTES: &[(&str, &str)] = &[ - ("/", "/"), - ("/crate/hexponent/0.2.0", "/crate/{name}/{version}"), - ("/crate/rcc/0.0.0", "/crate/{name}/{version}"), - ( - "/crate/rcc/0.0.0/status.json", - "/crate/{name}/{version}/status.json", - ), - ("/-/static/index.js", "static resource"), - ("/-/static/menu.js", "static resource"), - ("/-/static/keyboard.js", "static resource"), - ("/-/static/source.js", "static resource"), - ("/-/static/opensearch.xml", "static resource"), - ("/releases", "/releases"), - ("/releases/feed", "/releases/feed"), - ("/releases/queue", "/releases/queue"), - ("/releases/recent-failures", "/releases/recent-failures"), - ( - "/releases/recent-failures/1", - "/releases/recent-failures/{page}", - ), - ("/releases/recent/1", "/releases/recent/{page}"), - ("/-/static/robots.txt", "static resource"), - ("/sitemap.xml", "/sitemap.xml"), - ( - "/-/sitemap/a/sitemap.xml", - "/-/sitemap/{letter}/sitemap.xml", - ), - ("/-/static/style.css", "static resource"), - ("/-/static/vendored.css", "static resource"), - ("/rustdoc/rcc/0.0.0/rcc/index.html", "rustdoc page"), - ("/rustdoc/gcc/0.0.0/gcc/index.html", "rustdoc page"), - ]; +// #[test] +// fn test_response_times_count_being_collected() { +// const ROUTES: &[(&str, &str)] = &[ +// ("/", "/"), +// ("/crate/hexponent/0.2.0", "/crate/{name}/{version}"), +// ("/crate/rcc/0.0.0", "/crate/{name}/{version}"), +// ( +// "/crate/rcc/0.0.0/status.json", +// "/crate/{name}/{version}/status.json", +// ), +// ("/-/static/index.js", "static resource"), +// ("/-/static/menu.js", "static resource"), +// ("/-/static/keyboard.js", "static resource"), +// ("/-/static/source.js", "static resource"), +// ("/-/static/opensearch.xml", "static resource"), +// ("/releases", "/releases"), +// ("/releases/feed", "/releases/feed"), +// ("/releases/queue", "/releases/queue"), +// ("/releases/recent-failures", "/releases/recent-failures"), +// ( +// "/releases/recent-failures/1", +// "/releases/recent-failures/{page}", +// ), +// ("/releases/recent/1", "/releases/recent/{page}"), +// ("/-/static/robots.txt", "static resource"), +// ("/sitemap.xml", "/sitemap.xml"), +// ( +// "/-/sitemap/a/sitemap.xml", +// "/-/sitemap/{letter}/sitemap.xml", +// ), +// ("/-/static/style.css", "static resource"), +// ("/-/static/vendored.css", "static resource"), +// ("/rustdoc/rcc/0.0.0/rcc/index.html", "rustdoc page"), +// ("/rustdoc/gcc/0.0.0/gcc/index.html", "rustdoc page"), +// ]; - async_wrapper(|env| async move { - env.fake_release() - .await - .name("rcc") - .version("0.0.0") - .repo("https://github.com/jyn514/rcc") - .create() - .await?; - env.fake_release() - .await - .name("rcc") - .version("1.0.0") - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("hexponent") - .version("0.2.0") - .create() - .await?; +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("rcc") +// .version("0.0.0") +// .repo("https://github.com/jyn514/rcc") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("rcc") +// .version("1.0.0") +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.2.0") +// .create() +// .await?; - let frontend = env.web_app().await; +// let frontend = env.web_app().await; - for (route, _) in ROUTES.iter() { - frontend.get(route).await?; - frontend.get(route).await?; - } +// for (route, _) in ROUTES.iter() { +// frontend.get(route).await?; +// frontend.get(route).await?; +// } - let mut expected = HashMap::new(); - for (_, correct) in ROUTES.iter() { - let entry = expected.entry(*correct).or_insert(0); - *entry += 2; - } +// let mut expected = HashMap::new(); +// for (_, correct) in ROUTES.iter() { +// let entry = expected.entry(*correct).or_insert(0); +// *entry += 2; +// } - let collected = dbg!(env.collected_metrics()); - let AggregatedMetrics::U64(MetricData::Sum(routes_visited)) = collected - .get_metric("web", "docsrs.web.routes_visited")? - .data() - else { - panic!("Expected Sum metric data"); - }; +// let collected = dbg!(env.collected_metrics()); +// let AggregatedMetrics::U64(MetricData::Sum(routes_visited)) = collected +// .get_metric("web", "docsrs.web.routes_visited")? +// .data() +// else { +// panic!("Expected Sum metric data"); +// }; - dbg!(&routes_visited); +// dbg!(&routes_visited); - let routes_visited: HashMap = routes_visited - .data_points() - .map(|dp| { - let route = dp - .attributes() - .find(|kv| kv.key.as_str() == "route") - .unwrap() - .clone() - .value; +// let routes_visited: HashMap = routes_visited +// .data_points() +// .map(|dp| { +// let route = dp +// .attributes() +// .find(|kv| kv.key.as_str() == "route") +// .unwrap() +// .clone() +// .value; - (route.to_string(), dp.value()) - }) - .collect(); +// (route.to_string(), dp.value()) +// }) +// .collect(); - assert_eq!( - routes_visited, - HashMap::from_iter( - vec![ - ("/", 2), - ("/-/sitemap/{letter}/sitemap.xml", 2), - ("/crate/{name}/{version}", 4), - ("/crate/{name}/{version}/status.json", 2), - ("/releases", 2), - ("/releases/feed", 2), - ("/releases/queue", 2), - ("/releases/recent-failures", 2), - ("/releases/recent-failures/{page}", 2), - ("/releases/recent/{page}", 2), - ("/sitemap.xml", 2), - ("rustdoc page", 4), - ("static resource", 16), - ] - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - ) - ); +// assert_eq!( +// routes_visited, +// HashMap::from_iter( +// vec![ +// ("/", 2), +// ("/-/sitemap/{letter}/sitemap.xml", 2), +// ("/crate/{name}/{version}", 4), +// ("/crate/{name}/{version}/status.json", 2), +// ("/releases", 2), +// ("/releases/feed", 2), +// ("/releases/queue", 2), +// ("/releases/recent-failures", 2), +// ("/releases/recent-failures/{page}", 2), +// ("/releases/recent/{page}", 2), +// ("/sitemap.xml", 2), +// ("rustdoc page", 4), +// ("static resource", 16), +// ] +// .into_iter() +// .map(|(k, v)| (k.to_string(), v)) +// ) +// ); - let AggregatedMetrics::F64(MetricData::Histogram(response_time)) = collected - .get_metric("web", "docsrs.web.response_time")? - .data() - else { - panic!("Expected Histogram metric data"); - }; +// let AggregatedMetrics::F64(MetricData::Histogram(response_time)) = collected +// .get_metric("web", "docsrs.web.response_time")? +// .data() +// else { +// panic!("Expected Histogram metric data"); +// }; - dbg!(&response_time); +// dbg!(&response_time); - let response_time_sample_counts: HashMap = response_time - .data_points() - .map(|dp| { - let route = dp - .attributes() - .find(|kv| kv.key.as_str() == "route") - .unwrap() - .clone() - .value; +// let response_time_sample_counts: HashMap = response_time +// .data_points() +// .map(|dp| { +// let route = dp +// .attributes() +// .find(|kv| kv.key.as_str() == "route") +// .unwrap() +// .clone() +// .value; - (route.to_string(), dp.count()) - }) - .collect(); +// (route.to_string(), dp.count()) +// }) +// .collect(); - assert_eq!( - response_time_sample_counts, - HashMap::from_iter( - vec![ - ("/", 2), - ("/-/sitemap/{letter}/sitemap.xml", 2), - ("/crate/{name}/{version}", 4), - ("/crate/{name}/{version}/status.json", 2), - ("/releases", 2), - ("/releases/feed", 2), - ("/releases/queue", 2), - ("/releases/recent-failures", 2), - ("/releases/recent-failures/{page}", 2), - ("/releases/recent/{page}", 2), - ("/sitemap.xml", 2), - ("rustdoc page", 4), - ("static resource", 16), - ] - .into_iter() - .map(|(k, v)| (k.to_string(), v)) - ) - ); +// assert_eq!( +// response_time_sample_counts, +// HashMap::from_iter( +// vec![ +// ("/", 2), +// ("/-/sitemap/{letter}/sitemap.xml", 2), +// ("/crate/{name}/{version}", 4), +// ("/crate/{name}/{version}/status.json", 2), +// ("/releases", 2), +// ("/releases/feed", 2), +// ("/releases/queue", 2), +// ("/releases/recent-failures", 2), +// ("/releases/recent-failures/{page}", 2), +// ("/releases/recent/{page}", 2), +// ("/sitemap.xml", 2), +// ("rustdoc page", 4), +// ("static resource", 16), +// ] +// .into_iter() +// .map(|(k, v)| (k.to_string(), v)) +// ) +// ); - Ok(()) - }) - } -} +// Ok(()) +// }) +// } +// } diff --git a/crates/docs_rs_web/src/page/templates.rs b/crates/docs_rs_web/src/page/templates.rs index 6b1e98f07..a44fa0fc5 100644 --- a/crates/docs_rs_web/src/page/templates.rs +++ b/crates/docs_rs_web/src/page/templates.rs @@ -138,7 +138,7 @@ pub mod filters { /// Prettily format a timestamp // TODO: This can be replaced by chrono pub fn timeformat(value: &DateTime, _: &dyn Values) -> askama::Result { - Ok(crate::web::duration_to_str(*value)) + Ok(crate::duration_to_str(*value)) } pub fn format_secs(mut value: f32, _: &dyn Values) -> askama::Result { @@ -208,8 +208,7 @@ pub mod filters { _: &dyn Values, lang: &str, ) -> askama::Result> { - let highlighted_code = - crate::web::highlight::with_lang(Some(lang), &code.to_string(), None); + let highlighted_code = crate::highlight::with_lang(Some(lang), &code.to_string(), None); Ok(Safe(format!("
{highlighted_code}
"))) } diff --git a/crates/docs_rs_web/src/page/web_page.rs b/crates/docs_rs_web/src/page/web_page.rs index ef60fc6f4..1d76bca2a 100644 --- a/crates/docs_rs_web/src/page/web_page.rs +++ b/crates/docs_rs_web/src/page/web_page.rs @@ -13,6 +13,7 @@ pub(crate) trait AddCspNonce: IntoResponse { fn render_with_csp_nonce(&mut self, csp_nonce: String) -> askama::Result; } +#[macro_export] macro_rules! impl_axum_webpage { ( $page:ty @@ -23,7 +24,7 @@ macro_rules! impl_axum_webpage { $(, cpu_intensive_rendering = $cpu_intensive_rendering:expr)? $(,)? ) => { - impl $crate::web::page::web_page::AddCspNonce for $page { + impl $crate::page::web_page::AddCspNonce for $page { fn render_with_csp_nonce(&mut self, csp_nonce: String) -> askama::Result { let values: (&str, &dyn std::any::Any) = ("csp_nonce", &csp_nonce); self.render_with_values(&values) @@ -61,7 +62,7 @@ macro_rules! impl_axum_webpage { $( response.extensions_mut().insert({ - let cache_policy: fn(&$page) -> $crate::web::cache::CachePolicy = $cache_policy; + let cache_policy: fn(&$page) -> $crate::cache::CachePolicy = $cache_policy; (cache_policy)(&self) }); )? @@ -79,7 +80,7 @@ macro_rules! impl_axum_webpage { )? - response.extensions_mut().insert($crate::web::page::web_page::DelayedTemplateRender { + response.extensions_mut().insert($crate::page::web_page::DelayedTemplateRender { template: std::sync::Arc::new(Box::new(self)), cpu_intensive_rendering, }); diff --git a/crates/docs_rs_web/src/releases.rs b/crates/docs_rs_web/src/releases.rs index 96a1c678c..131e8351c 100644 --- a/crates/docs_rs_web/src/releases.rs +++ b/crates/docs_rs_web/src/releases.rs @@ -2,17 +2,14 @@ use super::cache::CachePolicy; use crate::{ - Config, RegistryApi, impl_axum_webpage, - utils::report_error, - web::{ - ReqVersion, axum_redirect, - error::{AxumNope, AxumResult}, - extractors::{DbConnection, Path, rustdoc::RustdocParams}, - match_version, - metrics::WebMetrics, - page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, - rustdoc::OfficialCrateDescription, - }, + ReqVersion, axum_redirect, + config::Config, + error::{AxumNope, AxumResult}, + extractors::{DbConnection, Path, rustdoc::RustdocParams}, + impl_axum_webpage, match_version, + metrics::WebMetrics, + page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, + rustdoc::OfficialCrateDescription, }; use anyhow::{Context as _, Result, anyhow}; use askama::Template; @@ -24,6 +21,7 @@ use base64::{Engine, engine::general_purpose::STANDARD as b64}; use chrono::{DateTime, Utc}; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS, QueuedCrate}; use docs_rs_database::types::version::Version; +use docs_rs_registry_api::RegistryApi; use docs_rs_web_utils::encode_url_path; use futures_util::stream::TryStreamExt; use itertools::Itertools; @@ -159,7 +157,7 @@ async fn get_search_results( query_params: &str, query: &str, ) -> Result { - let crate::registry_api::Search { crates, meta } = registry.search(query_params).await?; + let docs_rs_registry_api::Search { crates, meta } = registry.search(query_params).await?; let names = Arc::new( crates @@ -779,1501 +777,1501 @@ pub(crate) async fn build_queue_handler( }) } -#[cfg(test)] -mod tests { - use super::*; - use crate::db::types::BuildStatus; - use crate::db::{finish_build, initialize_build, initialize_crate, initialize_release}; - use crate::registry_api::{CrateOwner, OwnerKind}; - use crate::test::{ - AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, V0_1, V1, V2, V3, - async_wrapper, fake_release_that_failed_before_build, - }; - use anyhow::Error; - use chrono::{Duration, TimeZone}; - use kuchikiki::traits::TendrilSink; - use mockito::Matcher; - use reqwest::StatusCode; - use serde_json::json; - use std::collections::HashSet; - use test_case::test_case; - - #[test] - fn test_release_list_with_incomplete_release_and_successful_build() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let crate_id = initialize_crate(&mut conn, "foo").await?; - let release_id = initialize_release(&mut conn, crate_id, &V1).await?; - let build_id = initialize_build(&mut conn, release_id).await?; - - finish_build( - &mut conn, - build_id, - "rustc-version", - "docs.rs 4.0.0", - BuildStatus::Success, - None, - None, - ) - .await?; - - let releases = get_releases(&mut conn, 1, 10, Order::ReleaseTime, false).await?; - - assert_eq!( - vec!["foo"], - releases - .iter() - .map(|release| release.name.as_str()) - .collect::>(), - ); - - Ok(()) - }) - } - - #[test] - fn get_releases_by_stars() { - async_wrapper(|env| async move { - let db = env.async_db(); - - env.fake_release() - .await - .name("foo") - .version(V1) - .github_stats("ghost/foo", 10, 10, 10) - .create() - .await?; - env.fake_release() - .await - .name("bar") - .version(V1) - .github_stats("ghost/bar", 20, 20, 20) - .create() - .await?; - env.fake_release() - .await - .name("bar") - .version(V1) - .github_stats("ghost/bar", 20, 20, 20) - .create() - .await?; - // release without stars will not be shown - env.fake_release() - .await - .name("baz") - .version(V1) - .create() - .await?; - - // release with only in-progress build (= in progress release) will not be shown - env.fake_release() - .await - .name("in_progress") - .version(V0_1) - .builds(vec![ - FakeBuild::default() - .build_status(BuildStatus::InProgress) - .rustc_version("rustc (blabla 2022-01-01)") - .docsrs_version("docs.rs 4.0.0"), - ]) - .create() - .await?; - - let releases = - get_releases(&mut *db.async_conn().await, 1, 10, Order::GithubStars, true) - .await - .unwrap(); - assert_eq!( - vec![ - "bar", // 20 stars - "foo", // 10 stars - ], - releases - .iter() - .map(|release| release.name.as_str()) - .collect::>(), - ); - - Ok(()) - }) - } - - #[test] - fn search_im_feeling_lucky_with_query_redirect_to_crate_page() { - async_wrapper(|env| async move { - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .version(V1) - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("some_other_crate") - .version(V1) - .create() - .await?; - - web.assert_redirect( - "/releases/search?query=some_random_crate&i-am-feeling-lucky=1", - "/crate/some_random_crate/latest", - ) - .await?; - Ok(()) - }) - } - - #[test] - fn search_im_feeling_lucky_with_query_redirect_to_docs() { - async_wrapper(|env| async move { - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .version(V1) - .create() - .await?; - env.fake_release() - .await - .name("some_other_crate") - .version(V1) - .create() - .await?; - - web.assert_redirect( - "/releases/search?query=some_random_crate&i-am-feeling-lucky=1", - "/some_random_crate/latest/some_random_crate/", - ) - .await?; - Ok(()) - }) - } - - #[test] - fn im_feeling_lucky_with_stars() { - async_wrapper(|env| async move { - // The normal test-setup will offset all primary sequences by 10k - // to prevent errors with foreign key relations. - // Random-crate-search relies on the sequence for the crates-table - // to find a maximum possible ID. This combined with only one actual - // crate in the db breaks this test. - // That's why we reset the id-sequence to zero for this test. - - let mut conn = env.async_db().async_conn().await; - sqlx::query!(r#"ALTER SEQUENCE crates_id_seq RESTART WITH 1"#) - .execute(&mut *conn) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .github_stats("some/repo", 333, 22, 11) - .name("some_random_crate") - .version(V1) - .create() - .await?; - web.assert_redirect( - "/releases/search?query=&i-am-feeling-lucky=1", - &format!("/some_random_crate/{V1}/some_random_crate/"), - ) - .await?; - Ok(()) - }) - } - - #[test] - fn search_coloncolon_path_redirects_to_crate_docs() { - async_wrapper(|env| async move { - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - env.fake_release() - .await - .name("some_other_crate") - .create() - .await?; - - web.assert_redirect( - "/releases/search?query=some_random_crate::somepath", - "/some_random_crate/latest/some_random_crate/?search=somepath", - ) - .await?; - web.assert_redirect( - "/releases/search?query=some_random_crate::some::path", - "/some_random_crate/latest/some_random_crate/?search=some%3A%3Apath", - ) - .await?; - Ok(()) - }) - } - - #[test] - fn search_coloncolon_path_redirects_to_crate_docs_and_keeps_query() { - async_wrapper(|env| async move { - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - - web.assert_redirect( - "/releases/search?query=some_random_crate::somepath&go_to_first=true", - "/some_random_crate/latest/some_random_crate/?go_to_first=true&search=somepath", - ) - .await?; - Ok(()) - }) - } - - #[tokio::test(flavor = "multi_thread")] - async fn search_result_can_retrieve_sort_by_from_pagination() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "some_random_crate".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - Matcher::UrlEncoded("page".into(), "2".into()), - Matcher::UrlEncoded("sort".into(), "recent-updates".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "crates": [ - { "name": "some_random_crate" }, - ], - "meta": { - "next_page": "?q=some_random_crate&sort=recent-updates&per_page=30&page=2", - "prev_page": "?q=some_random_crate&sort=recent-updates&per_page=30&page=1", - } - }) - .to_string(), - ) - .create_async() - .await; - - // click the "Next Page" Button, the "Sort by" SelectBox should keep the same option. - let next_page_url = format!( - "/releases/search?paginate={}", - b64.encode("?q=some_random_crate&sort=recent-updates&per_page=30&page=2"), - ); - let response = web.get(&next_page_url).await?; - assert!(response.status().is_success()); - - let page = kuchikiki::parse_html().one(response.text().await?); - let is_target_option_selected = page - .select("#nav-sort > option") - .expect("missing option") - .any(|el| { - let attributes = el.attributes.borrow(); - attributes.get("selected").is_some() - && attributes.get("value").unwrap() == "recent-updates" - }); - assert!(is_target_option_selected); - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn search_result_passes_cratesio_pagination_links() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "some_random_crate".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "crates": [ - { "name": "some_random_crate" }, - ], - "meta": { - "next_page": "?some=parameters&that=cratesio&might=return", - "prev_page": "?and=the¶meters=for&the=previouspage", - } - }) - .to_string(), - ) - .create_async() - .await; - - let response = web.get("/releases/search?query=some_random_crate").await?; - assert!(response.status().is_success()); - - let page = kuchikiki::parse_html().one(response.text().await?); - - let other_search_links: Vec<_> = page - .select("a") - .expect("missing link") - .map(|el| { - let attributes = el.attributes.borrow(); - attributes.get("href").unwrap().to_string() - }) - .filter(|url| url.starts_with("/releases/search?")) - .collect(); - - assert_eq!(other_search_links.len(), 2); - assert_eq!( - other_search_links[0], - format!( - "/releases/search?paginate={}", - b64.encode("?and=the¶meters=for&the=previouspage"), - ) - ); - assert_eq!( - other_search_links[1], - format!( - "/releases/search?paginate={}", - b64.encode("?some=parameters&that=cratesio&might=return") - ) - ); - - Ok(()) - } - - #[test] - fn search_invalid_paginate_doesnt_request_cratesio() { - async_wrapper(|env| async move { - let response = env - .web_app() - .await - .get(&format!( - "/releases/search?paginate={}", - b64.encode("something_that_doesnt_start_with_?") - )) - .await?; - assert_eq!(response.status(), StatusCode::NOT_FOUND); - Ok(()) - }) - } - - #[tokio::test(flavor = "multi_thread")] - async fn crates_io_errors_as_status_code_200() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .crates_io_api_call_retries(0) - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "doesnt_matter_here".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "errors": [ - { "detail": "error name 1" }, - { "detail": "error name 2" }, - ] - }) - .to_string(), - ) - .create_async() - .await; - - let response = env - .web_app() - .await - .get("/releases/search?query=doesnt_matter_here") - .await?; - assert_eq!(response.status(), 500); - - assert!( - response - .text() - .await? - .contains("error name 1\nerror name 2") - ); - Ok(()) - } - - #[test_case(StatusCode::NOT_FOUND)] - #[test_case(StatusCode::INTERNAL_SERVER_ERROR)] - #[test_case(StatusCode::BAD_GATEWAY)] - #[tokio::test(flavor = "multi_thread")] - async fn crates_io_errors_are_correctly_returned_and_we_dont_try_parsing( - status: StatusCode, - ) -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .crates_io_api_call_retries(0) - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "doesnt_matter_here".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - ])) - .with_status(status.as_u16() as usize) - .create_async() - .await; - - let response = env - .web_app() - .await - .get("/releases/search?query=doesnt_matter_here") - .await?; - assert_eq!(response.status(), 500); - - assert!(response.text().await?.contains(&format!("{status}"))); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn search_encoded_pagination_passed_to_cratesio() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("some".into(), "dummy".into()), - Matcher::UrlEncoded("pagination".into(), "parameters".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "crates": [ - { "name": "some_random_crate" }, - ], - "meta": { - "next_page": null, - "prev_page": null, - } - }) - .to_string(), - ) - .create_async() - .await; - - let links = get_release_links( - &format!( - "/releases/search?paginate={}", - b64.encode("?some=dummy&pagination=parameters") - ), - &web, - ) - .await?; - - assert_eq!(links.len(), 1); - assert_eq!(links[0], "/some_random_crate/latest/some_random_crate/",); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn search_lucky_with_unknown_crate() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "some_random_".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "crates": [ - { "name": "some_random_crate" }, - { "name": "some_other_crate" }, - ], - "meta": { - "next_page": null, - "prev_page": null, - } - }) - .to_string(), - ) - .create_async() - .await; - - // when clicking "I'm feeling lucky" and the query doesn't match any crate, - // just fallback to the normal search results. - let links = get_release_links( - "/releases/search?query=some_random_&i-am-feeling-lucky=1", - &web, - ) - .await?; - - assert_eq!(links.len(), 1); - assert_eq!(links[0], "/some_random_crate/latest/some_random_crate/"); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn search() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .version("2.0.0") - .create() - .await?; - env.fake_release() - .await - .name("some_random_crate") - .version("1.0.0") - .create() - .await?; - - env.fake_release() - .await - .name("and_another_one") - .version("0.0.1") - .create() - .await?; - - env.fake_release() - .await - .name("yet_another_crate") - .version("0.1.0") - .yanked(true) - .create() - .await?; - - // release with only in-progress build (= in progress release) will not be shown - env.fake_release() - .await - .name("in_progress") - .version("0.1.0") - .builds(vec![ - FakeBuild::default() - .build_status(BuildStatus::InProgress) - .rustc_version("rustc (blabla 2022-01-01)") - .docsrs_version("docs.rs 4.0.0"), - ]) - .create() - .await?; - - // release that failed in the fetch-step, will miss some details - let mut conn = env.async_db().async_conn().await; - fake_release_that_failed_before_build( - &mut conn, - "failed_hard", - "0.1.0", - "some random error", - ) - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "some_random_crate".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "crates": [ - { "name": "some_random_crate" }, - { "name": "some_other_crate" }, - { "name": "and_another_one" }, - { "name": "yet_another_crate" }, - { "name": "in_progress" }, - { "name": "failed_hard" } - ], - "meta": { - "next_page": null, - "prev_page": null, - } - }) - .to_string(), - ) - .create_async() - .await; - - let links = get_release_links("/releases/search?query=some_random_crate", &web).await?; - - // `some_other_crate` won't be shown since we don't have it yet - assert_eq!(links.len(), 4); - // * `max_version` from the crates.io search result will be ignored since we - // might not have it yet, or the doc-build might be in progress. - // * ranking/order from crates.io result is preserved - // * version used is the highest semver following our own "latest version" logic - assert_eq!(links[0], "/some_random_crate/latest/some_random_crate/"); - assert_eq!(links[1], "/and_another_one/latest/and_another_one/"); - assert_eq!(links[2], "/yet_another_crate/0.1.0/yet_another_crate/"); - assert_eq!(links[3], "/crate/failed_hard/0.1.0"); - Ok(()) - } - - async fn get_release_links(path: &str, web: &axum::Router) -> Result, Error> { - let response = web.get(path).await?; - assert!(response.status().is_success()); - - let page = kuchikiki::parse_html().one(response.text().await?); - - Ok(page - .select("a.release") - .expect("missing heading") - .map(|el| { - let attributes = el.attributes.borrow(); - attributes.get("href").unwrap().to_string() - }) - .collect()) - } - - #[test] - fn releases_by_stars() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.1.0") - .github_stats("some/repo", 66, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) - .create() - .await?; - - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.2.0") - .github_stats("some/repo", 66, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 20, 4, 33, 50).unwrap()) - .create() - .await?; - - env.fake_release() - .await - .name("crate_that_succeeded_without_github") - .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) - .version("0.2.0") - .create() - .await?; - - env.fake_release() - .await - .name("crate_that_failed_with_github") - .version("0.1.0") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) - .build_result_failed() - .create() - .await?; - - let links = get_release_links("/releases/stars", &env.web_app().await).await?; - - // output is sorted by stars, not release-time - assert_eq!(links.len(), 2); - assert_eq!( - links[0], - "/crate_that_succeeded_with_github/0.2.0/crate_that_succeeded_with_github/" - ); - assert_eq!(links[1], "/crate/crate_that_failed_with_github/0.1.0"); - - Ok(()) - }) - } - - #[test] - fn failures_by_stars() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.1.0") - .github_stats("some/repo", 66, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) - .create() - .await?; - - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.2.0") - .github_stats("some/repo", 66, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 20, 4, 33, 50).unwrap()) - .create() - .await?; - - env.fake_release() - .await - .name("crate_that_succeeded_without_github") - .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) - .version("0.2.0") - .create() - .await?; - - env.fake_release() - .await - .name("crate_that_failed_with_github") - .version("0.1.0") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) - .build_result_failed() - .create() - .await?; - - let links = get_release_links("/releases/failures", &env.web_app().await).await?; - - // output is sorted by stars, not release-time - assert_eq!(links.len(), 1); - assert_eq!(links[0], "/crate/crate_that_failed_with_github/0.1.0"); - - Ok(()) - }) - } - - #[test] - fn releases_failed_by_time() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.1.0") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) - .create() - .await?; - // make sure that crates get at most one release shown, so they don't crowd the page - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) - .version("0.2.0") - .create() - .await?; - env.fake_release() - .await - .name("crate_that_failed") - .version("0.1.0") - .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) - .build_result_failed() - .create() - .await?; - - let links = - get_release_links("/releases/recent-failures", &env.web_app().await).await?; - - assert_eq!(links.len(), 1); - assert_eq!(links[0], "/crate/crate_that_failed/0.1.0"); - - Ok(()) - }) - } - - #[test] - fn releases_homepage_and_recent() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.1.0") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) - .create() - .await?; - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .version("0.2.0-rc") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 8, 33, 50).unwrap()) - .build_result_failed() - .create() - .await?; - env.fake_release() - .await - .name("crate_that_succeeded_with_github") - .github_stats("some/repo", 33, 22, 11) - .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) - .version("0.2.0") - .create() - .await?; - env.fake_release() - .await - .name("crate_that_failed") - .version("0.1.0") - .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) - .build_result_failed() - .create() - .await?; - - // make sure that crates get at most one release shown, so they don't crowd the homepage - assert_eq!( - get_release_links("/", &env.web_app().await).await?, - [ - "/crate/crate_that_failed/0.1.0", - "/crate_that_succeeded_with_github/0.2.0/crate_that_succeeded_with_github/", - ] - ); - - // but on the main release list they all show, including prerelease - assert_eq!( - get_release_links("/releases", &env.web_app().await).await?, - [ - "/crate/crate_that_failed/0.1.0", - "/crate_that_succeeded_with_github/0.2.0/crate_that_succeeded_with_github/", - "/crate/crate_that_succeeded_with_github/0.2.0-rc", - "/crate_that_succeeded_with_github/0.1.0/crate_that_succeeded_with_github/", - ] - ); - - Ok(()) - }) - } - - #[test] - fn release_activity() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - let empty_data = format!("data: [{}]", vec!["0"; 30].join(", ")); - - // no data / only zeros without releases - let response = web.get("/releases/activity").await?; - assert!(response.status().is_success()); - let text = response.text().await?; - assert_eq!(text.matches(&empty_data).count(), 2); - - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - env.fake_release() - .await - .name("some_random_crate_that_failed") - .build_result_failed() - .create() - .await?; - - // same when the release is on the current day, since we ignore today. - let response = web.get("/releases/activity").await?; - assert!(response.status().is_success()); - assert_eq!(response.text().await?.matches(&empty_data).count(), 2); - - env.fake_release() - .await - .name("some_random_crate_yesterday") - .release_time(Utc::now() - Duration::try_days(1).unwrap()) - .create() - .await?; - env.fake_release() - .await - .name("some_random_crate_that_failed_yesterday") - .build_result_failed() - .release_time(Utc::now() - Duration::try_days(1).unwrap()) - .create() - .await?; - - // with releases yesterday we get the data we want - let response = web.get("/releases/activity").await?; - assert!(response.status().is_success()); - let text = response.text().await?; - // counts contain both releases - assert!(text.contains(&format!("data: [{}, 2]", vec!["0"; 29].join(", ")))); - // failures only one - assert!(text.contains(&format!("data: [{}, 1]", vec!["0"; 29].join(", ")))); - - Ok(()) - }) - } - - #[test] - fn release_feed() { - async_wrapper(|env| async move { - let web = env.web_app().await; - web.assert_success("/releases/feed").await?; - - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - env.fake_release() - .await - .name("some_random_crate_that_failed") - .build_result_failed() - .create() - .await?; - web.assert_success("/releases/feed").await?; - Ok(()) - }) - } - - #[test] - fn test_releases_queue() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - let empty = - kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); - assert!( - empty - .select(".queue-list > strong") - .expect("missing heading") - .any(|el| el.text_contents().contains("nothing")) - ); - - assert!( - !empty - .select(".release > strong") - .expect("missing heading") - .any(|el| el.text_contents().contains("active CDN deployments")) - ); - - let queue = env.async_build_queue(); - queue.add_crate("foo", &V1, 0, None).await?; - queue.add_crate("bar", &V2, -10, None).await?; - queue.add_crate("baz", &V3, 10, None).await?; - - let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); - let items = full - .select(".queue-list > li") - .expect("missing list items") - .collect::>(); - - assert_eq!(items.len(), 3); - let expected = [ - ("bar", V2, Some(10)), - ("foo", V1, None), - ("baz", V3, Some(-10)), - ]; - for (li, expected) in items.iter().zip(&expected) { - let a = li.as_node().select_first("a").expect("missing link"); - assert!(a.text_contents().contains(expected.0)); - assert!(a.text_contents().contains(&expected.1.to_string())); - - if let Some(priority) = expected.2 { - assert!( - li.text_contents() - .contains(&format!("priority: {priority}")) - ); - } - } - - Ok(()) - }); - } - - #[test] - fn test_releases_queue_in_progress() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - // we have two queued releases, where the build for one is already in progress - let queue = env.async_build_queue(); - queue.add_crate("foo", &V1, 0, None).await?; - queue.add_crate("bar", &V2, 0, None).await?; - - env.fake_release() - .await - .name("foo") - .version(V1) - .builds(vec![ - FakeBuild::default() - .build_status(BuildStatus::InProgress) - .rustc_version("rustc (blabla 2022-01-01)") - .docsrs_version("docs.rs 4.0.0"), - ]) - .create() - .await?; - - let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); - - let lists = full - .select(".queue-list") - .expect("missing queues") - .collect::>(); - assert_eq!(lists.len(), 2); - - let in_progress_items: Vec<_> = lists[0] - .as_node() - .select("li > a") - .expect("missing in progress list items") - .map(|node| node.text_contents().trim().to_string()) - .collect(); - assert_eq!(in_progress_items, vec![format!("foo {V1}")]); - - let queued_items: Vec<_> = lists[1] - .as_node() - .select("li > a") - .expect("missing queued list items") - .map(|node| node.text_contents().trim().to_string()) - .collect(); - assert_eq!(queued_items, vec![format!("bar {V2}")]); - - Ok(()) - }); - } - - #[test] - fn test_releases_rebuild_queue_empty() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - let empty = - kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); - - assert!( - empty - .select(".about > p") - .expect("missing heading") - .any(|el| el.text_contents().contains("We continuously rebuild")) - ); - - assert!( - empty - .select(".about > p") - .expect("missing heading") - .any(|el| el.text_contents().contains("crates in the rebuild queue")) - ); - - Ok(()) - }); - } - - #[test] - fn test_releases_rebuild_queue_with_crates() { - async_wrapper(|env| async move { - let web = env.web_app().await; - let queue = env.async_build_queue(); - queue - .add_crate("foo", &V1, PRIORITY_CONTINUOUS, None) - .await?; - queue - .add_crate("bar", &V2, PRIORITY_CONTINUOUS + 1, None) - .await?; - queue - .add_crate("baz", &V3, PRIORITY_CONTINUOUS - 1, None) - .await?; - - let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); - let items = full - .select(".rebuild-queue-list > li") - .expect("missing list items") - .collect::>(); - - // empty because expand_rebuild_queue is not set - assert_eq!(items.len(), 0); - assert!( - full.select(".about > p") - .expect("missing heading") - .any(|el| el - .text_contents() - .contains("There are currently 2 crates in the rebuild queue")) - ); - - let full = kuchikiki::parse_html() - .one(web.get("/releases/queue?expand=1").await?.text().await?); - let build_queue_list = full - .select(".queue-list > li") - .expect("missing list items") - .collect::>(); - let rebuild_queue_list = full - .select(".rebuild-queue-list > li") - .expect("missing list items") - .collect::>(); - - assert_eq!(build_queue_list.len(), 1); - assert_eq!(rebuild_queue_list.len(), 2); - assert!( - rebuild_queue_list - .iter() - .any(|li| li.text_contents().contains("foo")) - ); - assert!( - rebuild_queue_list - .iter() - .any(|li| li.text_contents().contains("bar")) - ); - assert!( - build_queue_list - .iter() - .any(|li| li.text_contents().contains("baz")) - ); - assert!( - !rebuild_queue_list - .iter() - .any(|li| li.text_contents().contains("baz")) - ); - - Ok(()) - }); - } - - #[test] - fn home_page_links() { - async_wrapper(|env| async move { - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .add_owner(CrateOwner { - login: "foobar".into(), - avatar: "https://example.org/foobar".into(), - kind: OwnerKind::User, - }) - .create() - .await?; - - let mut urls = vec![]; - let mut seen = HashSet::new(); - seen.insert("".to_owned()); - - let resp = web.get("/").await?; - resp.assert_cache_control(CachePolicy::ShortInCdnAndBrowser, env.config()); - - assert!(resp.status().is_success()); - - let html = kuchikiki::parse_html().one(resp.text().await?); - for link in html.select("a").unwrap() { - let link = link.as_node().as_element().unwrap(); - - urls.push(link.attributes.borrow().get("href").unwrap().to_owned()); - } - - while let Some(url) = urls.pop() { - // Skip urls we've already checked - if !seen.insert(url.clone()) { - continue; - } - - let resp = - if url.starts_with("http://") || url.starts_with("https://") || url == "#" { - // Skip external links - continue; - } else { - web.get(&url).await? - }; - let status = resp.status(); - assert!( - status.is_success(), - "failed to GET {url}: {status}, {:?}", - resp.headers().get("Location"), - ); - } - - Ok(()) - }); - } - - #[test] - fn check_releases_page_content() { - // NOTE: this is a little fragile and may have to be updated if the HTML layout changes - let sel = ".pure-menu-horizontal>.pure-menu-list>.pure-menu-item>.pure-menu-link>.title"; - async_wrapper(|env| async move { - for url in &[ - "/releases", - "/releases/stars", - "/releases/recent-failures", - "/releases/failures", - "/releases/activity", - "/releases/queue", - ] { - let page = kuchikiki::parse_html() - .one(env.web_app().await.get(url).await.unwrap().text().await?); - assert_eq!(page.select("#crate-title").unwrap().count(), 1); - let not_matching = page - .select(sel) - .unwrap() - .map(|node| node.text_contents()) - .zip( - [ - "Recent", - "Stars", - "Recent Failures", - "Failures By Stars", - "Activity", - "Queue", - ] - .iter(), - ) - .filter(|(a, b)| a.as_str() != **b) - .collect::>(); - if !not_matching.is_empty() { - let not_found = not_matching.iter().map(|(_, b)| b).collect::>(); - let found = not_matching.iter().map(|(a, _)| a).collect::>(); - assert!( - not_matching.is_empty(), - "Titles did not match for URL `{url}`: not found: {not_found:?}, found: {found:?}", - ); - } - } - - Ok(()) - }); - } - - #[test] - fn check_owner_releases_redirect() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - web.assert_redirect_unchecked("/releases/someone", "https://crates.io/users/someone") - .await?; - Ok(()) - }); - } - - #[tokio::test(flavor = "multi_thread")] - async fn crates_not_on_docsrs() -> Result<()> { - let mut crates_io = mockito::Server::new_async().await; - - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .registry_api_host(crates_io.url().parse().unwrap()) - .build()?, - ) - .await?; - - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - - let _m = crates_io - .mock("GET", "/api/v1/crates") - .match_query(Matcher::AllOf(vec![ - Matcher::UrlEncoded("q".into(), "some_random_crate".into()), - Matcher::UrlEncoded("per_page".into(), "30".into()), - ])) - .with_status(200) - .with_header("content-type", "application/json") - .with_body( - json!({ - "crates": [ - { "name": "some_random_crate" }, - { "name": "some_random_crate2" }, - { "name": "some_random_crate3" }, - ], - "meta": { - "next_page": "null", - "prev_page": "null", - } - }) - .to_string(), - ) - .create_async() - .await; - - let response = web.get("/releases/search?query=some_random_crate").await?; - assert!(response.status().is_success()); - - let page = kuchikiki::parse_html().one(response.text().await?); - - assert_eq!(page.select("div.name.not-available").unwrap().count(), 2); - assert_eq!( - page.select("div.name:not(.not-available)").unwrap().count(), - 1 - ); - - Ok(()) - } - - #[test] - fn recent_failures_correct_pagination_links() { - async_wrapper(|env| async move { - for i in 0..RELEASES_IN_RELEASES + 1 { - env.fake_release() - .await - .name("failed") - .version(format!("0.0.{i}")) - .build_result_failed() - .create() - .await?; - } - - let web = env.web_app().await; - - let response = web.get("/releases/recent-failures").await?; - assert!(response.status().is_success()); - - let page = kuchikiki::parse_html().one(response.text().await?); - assert_eq!( - page.select("div.description") - .unwrap() - .next() - .unwrap() - .text_contents(), - "Recent crates failed to build" - ); - - let next_page_link = page.select("div.pagination > a").unwrap().next().unwrap(); - assert_eq!(next_page_link.text_contents().trim(), "Next Page"); - - let next_page_url = next_page_link - .attributes - .borrow() - .get("href") - .unwrap() - .to_owned(); - assert_eq!(next_page_url, "/releases/recent-failures/2"); - - let response = web.get(&next_page_url).await?; - assert!(response.status().is_success()); - - let page = kuchikiki::parse_html().one(response.text().await?); - assert_eq!( - page.select("div.description") - .unwrap() - .next() - .unwrap() - .text_contents(), - "Recent crates failed to build" - ); - assert_eq!( - page.select(".recent-releases-container > ul > li .name") - .unwrap() - .next() - .unwrap() - .text_contents() - .trim(), - "failed-0.0.0" - ); - - Ok(()) - }); - } - - #[test] - fn test_search_std() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - async fn inner(web: &axum::Router, krate: &str) -> Result<(), anyhow::Error> { - let full = kuchikiki::parse_html().one( - web.get(&format!("/releases/search?query={krate}")) - .await? - .text() - .await?, - ); - let items = full - .select("ul a.release") - .expect("missing list items") - .collect::>(); - - // empty because expand_rebuild_queue is not set - let item_element = items.first().unwrap(); - let item = item_element.as_node(); - assert_eq!( - item.select(".name") - .unwrap() - .next() - .unwrap() - .text_contents(), - "std" - ); - assert_eq!( - item.select(".description") - .unwrap() - .next() - .unwrap() - .text_contents(), - "Rust standard library", - ); - assert_eq!( - item_element.attributes.borrow().get("href").unwrap(), - "https://doc.rust-lang.org/stable/std/" - ); - - Ok(()) - } - - inner(&web, "std").await?; - inner(&web, "libstd").await?; - - Ok(()) - }); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::db::types::BuildStatus; +// use crate::db::{finish_build, initialize_build, initialize_crate, initialize_release}; +// use crate::registry_api::{CrateOwner, OwnerKind}; +// use crate::test::{ +// AxumResponseTestExt, AxumRouterTestExt, FakeBuild, TestEnvironment, V0_1, V1, V2, V3, +// async_wrapper, fake_release_that_failed_before_build, +// }; +// use anyhow::Error; +// use chrono::{Duration, TimeZone}; +// use kuchikiki::traits::TendrilSink; +// use mockito::Matcher; +// use reqwest::StatusCode; +// use serde_json::json; +// use std::collections::HashSet; +// use test_case::test_case; + +// #[test] +// fn test_release_list_with_incomplete_release_and_successful_build() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let crate_id = initialize_crate(&mut conn, "foo").await?; +// let release_id = initialize_release(&mut conn, crate_id, &V1).await?; +// let build_id = initialize_build(&mut conn, release_id).await?; + +// finish_build( +// &mut conn, +// build_id, +// "rustc-version", +// "docs.rs 4.0.0", +// BuildStatus::Success, +// None, +// None, +// ) +// .await?; + +// let releases = get_releases(&mut conn, 1, 10, Order::ReleaseTime, false).await?; + +// assert_eq!( +// vec!["foo"], +// releases +// .iter() +// .map(|release| release.name.as_str()) +// .collect::>(), +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn get_releases_by_stars() { +// async_wrapper(|env| async move { +// let db = env.async_db(); + +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .github_stats("ghost/foo", 10, 10, 10) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("bar") +// .version(V1) +// .github_stats("ghost/bar", 20, 20, 20) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("bar") +// .version(V1) +// .github_stats("ghost/bar", 20, 20, 20) +// .create() +// .await?; +// // release without stars will not be shown +// env.fake_release() +// .await +// .name("baz") +// .version(V1) +// .create() +// .await?; + +// // release with only in-progress build (= in progress release) will not be shown +// env.fake_release() +// .await +// .name("in_progress") +// .version(V0_1) +// .builds(vec![ +// FakeBuild::default() +// .build_status(BuildStatus::InProgress) +// .rustc_version("rustc (blabla 2022-01-01)") +// .docsrs_version("docs.rs 4.0.0"), +// ]) +// .create() +// .await?; + +// let releases = +// get_releases(&mut *db.async_conn().await, 1, 10, Order::GithubStars, true) +// .await +// .unwrap(); +// assert_eq!( +// vec![ +// "bar", // 20 stars +// "foo", // 10 stars +// ], +// releases +// .iter() +// .map(|release| release.name.as_str()) +// .collect::>(), +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn search_im_feeling_lucky_with_query_redirect_to_crate_page() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .version(V1) +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_other_crate") +// .version(V1) +// .create() +// .await?; + +// web.assert_redirect( +// "/releases/search?query=some_random_crate&i-am-feeling-lucky=1", +// "/crate/some_random_crate/latest", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn search_im_feeling_lucky_with_query_redirect_to_docs() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .version(V1) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_other_crate") +// .version(V1) +// .create() +// .await?; + +// web.assert_redirect( +// "/releases/search?query=some_random_crate&i-am-feeling-lucky=1", +// "/some_random_crate/latest/some_random_crate/", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn im_feeling_lucky_with_stars() { +// async_wrapper(|env| async move { +// // The normal test-setup will offset all primary sequences by 10k +// // to prevent errors with foreign key relations. +// // Random-crate-search relies on the sequence for the crates-table +// // to find a maximum possible ID. This combined with only one actual +// // crate in the db breaks this test. +// // That's why we reset the id-sequence to zero for this test. + +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!(r#"ALTER SEQUENCE crates_id_seq RESTART WITH 1"#) +// .execute(&mut *conn) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .github_stats("some/repo", 333, 22, 11) +// .name("some_random_crate") +// .version(V1) +// .create() +// .await?; +// web.assert_redirect( +// "/releases/search?query=&i-am-feeling-lucky=1", +// &format!("/some_random_crate/{V1}/some_random_crate/"), +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn search_coloncolon_path_redirects_to_crate_docs() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_other_crate") +// .create() +// .await?; + +// web.assert_redirect( +// "/releases/search?query=some_random_crate::somepath", +// "/some_random_crate/latest/some_random_crate/?search=somepath", +// ) +// .await?; +// web.assert_redirect( +// "/releases/search?query=some_random_crate::some::path", +// "/some_random_crate/latest/some_random_crate/?search=some%3A%3Apath", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn search_coloncolon_path_redirects_to_crate_docs_and_keeps_query() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; + +// web.assert_redirect( +// "/releases/search?query=some_random_crate::somepath&go_to_first=true", +// "/some_random_crate/latest/some_random_crate/?go_to_first=true&search=somepath", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn search_result_can_retrieve_sort_by_from_pagination() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "some_random_crate".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// Matcher::UrlEncoded("page".into(), "2".into()), +// Matcher::UrlEncoded("sort".into(), "recent-updates".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "crates": [ +// { "name": "some_random_crate" }, +// ], +// "meta": { +// "next_page": "?q=some_random_crate&sort=recent-updates&per_page=30&page=2", +// "prev_page": "?q=some_random_crate&sort=recent-updates&per_page=30&page=1", +// } +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// // click the "Next Page" Button, the "Sort by" SelectBox should keep the same option. +// let next_page_url = format!( +// "/releases/search?paginate={}", +// b64.encode("?q=some_random_crate&sort=recent-updates&per_page=30&page=2"), +// ); +// let response = web.get(&next_page_url).await?; +// assert!(response.status().is_success()); + +// let page = kuchikiki::parse_html().one(response.text().await?); +// let is_target_option_selected = page +// .select("#nav-sort > option") +// .expect("missing option") +// .any(|el| { +// let attributes = el.attributes.borrow(); +// attributes.get("selected").is_some() +// && attributes.get("value").unwrap() == "recent-updates" +// }); +// assert!(is_target_option_selected); + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn search_result_passes_cratesio_pagination_links() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "some_random_crate".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "crates": [ +// { "name": "some_random_crate" }, +// ], +// "meta": { +// "next_page": "?some=parameters&that=cratesio&might=return", +// "prev_page": "?and=the¶meters=for&the=previouspage", +// } +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// let response = web.get("/releases/search?query=some_random_crate").await?; +// assert!(response.status().is_success()); + +// let page = kuchikiki::parse_html().one(response.text().await?); + +// let other_search_links: Vec<_> = page +// .select("a") +// .expect("missing link") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// attributes.get("href").unwrap().to_string() +// }) +// .filter(|url| url.starts_with("/releases/search?")) +// .collect(); + +// assert_eq!(other_search_links.len(), 2); +// assert_eq!( +// other_search_links[0], +// format!( +// "/releases/search?paginate={}", +// b64.encode("?and=the¶meters=for&the=previouspage"), +// ) +// ); +// assert_eq!( +// other_search_links[1], +// format!( +// "/releases/search?paginate={}", +// b64.encode("?some=parameters&that=cratesio&might=return") +// ) +// ); + +// Ok(()) +// } + +// #[test] +// fn search_invalid_paginate_doesnt_request_cratesio() { +// async_wrapper(|env| async move { +// let response = env +// .web_app() +// .await +// .get(&format!( +// "/releases/search?paginate={}", +// b64.encode("something_that_doesnt_start_with_?") +// )) +// .await?; +// assert_eq!(response.status(), StatusCode::NOT_FOUND); +// Ok(()) +// }) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn crates_io_errors_as_status_code_200() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .crates_io_api_call_retries(0) +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "doesnt_matter_here".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "errors": [ +// { "detail": "error name 1" }, +// { "detail": "error name 2" }, +// ] +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// let response = env +// .web_app() +// .await +// .get("/releases/search?query=doesnt_matter_here") +// .await?; +// assert_eq!(response.status(), 500); + +// assert!( +// response +// .text() +// .await? +// .contains("error name 1\nerror name 2") +// ); +// Ok(()) +// } + +// #[test_case(StatusCode::NOT_FOUND)] +// #[test_case(StatusCode::INTERNAL_SERVER_ERROR)] +// #[test_case(StatusCode::BAD_GATEWAY)] +// #[tokio::test(flavor = "multi_thread")] +// async fn crates_io_errors_are_correctly_returned_and_we_dont_try_parsing( +// status: StatusCode, +// ) -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .crates_io_api_call_retries(0) +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "doesnt_matter_here".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// ])) +// .with_status(status.as_u16() as usize) +// .create_async() +// .await; + +// let response = env +// .web_app() +// .await +// .get("/releases/search?query=doesnt_matter_here") +// .await?; +// assert_eq!(response.status(), 500); + +// assert!(response.text().await?.contains(&format!("{status}"))); +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn search_encoded_pagination_passed_to_cratesio() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("some".into(), "dummy".into()), +// Matcher::UrlEncoded("pagination".into(), "parameters".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "crates": [ +// { "name": "some_random_crate" }, +// ], +// "meta": { +// "next_page": null, +// "prev_page": null, +// } +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// let links = get_release_links( +// &format!( +// "/releases/search?paginate={}", +// b64.encode("?some=dummy&pagination=parameters") +// ), +// &web, +// ) +// .await?; + +// assert_eq!(links.len(), 1); +// assert_eq!(links[0], "/some_random_crate/latest/some_random_crate/",); +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn search_lucky_with_unknown_crate() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "some_random_".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "crates": [ +// { "name": "some_random_crate" }, +// { "name": "some_other_crate" }, +// ], +// "meta": { +// "next_page": null, +// "prev_page": null, +// } +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// // when clicking "I'm feeling lucky" and the query doesn't match any crate, +// // just fallback to the normal search results. +// let links = get_release_links( +// "/releases/search?query=some_random_&i-am-feeling-lucky=1", +// &web, +// ) +// .await?; + +// assert_eq!(links.len(), 1); +// assert_eq!(links[0], "/some_random_crate/latest/some_random_crate/"); +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn search() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .version("2.0.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_random_crate") +// .version("1.0.0") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("and_another_one") +// .version("0.0.1") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("yet_another_crate") +// .version("0.1.0") +// .yanked(true) +// .create() +// .await?; + +// // release with only in-progress build (= in progress release) will not be shown +// env.fake_release() +// .await +// .name("in_progress") +// .version("0.1.0") +// .builds(vec![ +// FakeBuild::default() +// .build_status(BuildStatus::InProgress) +// .rustc_version("rustc (blabla 2022-01-01)") +// .docsrs_version("docs.rs 4.0.0"), +// ]) +// .create() +// .await?; + +// // release that failed in the fetch-step, will miss some details +// let mut conn = env.async_db().async_conn().await; +// fake_release_that_failed_before_build( +// &mut conn, +// "failed_hard", +// "0.1.0", +// "some random error", +// ) +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "some_random_crate".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "crates": [ +// { "name": "some_random_crate" }, +// { "name": "some_other_crate" }, +// { "name": "and_another_one" }, +// { "name": "yet_another_crate" }, +// { "name": "in_progress" }, +// { "name": "failed_hard" } +// ], +// "meta": { +// "next_page": null, +// "prev_page": null, +// } +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// let links = get_release_links("/releases/search?query=some_random_crate", &web).await?; + +// // `some_other_crate` won't be shown since we don't have it yet +// assert_eq!(links.len(), 4); +// // * `max_version` from the crates.io search result will be ignored since we +// // might not have it yet, or the doc-build might be in progress. +// // * ranking/order from crates.io result is preserved +// // * version used is the highest semver following our own "latest version" logic +// assert_eq!(links[0], "/some_random_crate/latest/some_random_crate/"); +// assert_eq!(links[1], "/and_another_one/latest/and_another_one/"); +// assert_eq!(links[2], "/yet_another_crate/0.1.0/yet_another_crate/"); +// assert_eq!(links[3], "/crate/failed_hard/0.1.0"); +// Ok(()) +// } + +// async fn get_release_links(path: &str, web: &axum::Router) -> Result, Error> { +// let response = web.get(path).await?; +// assert!(response.status().is_success()); + +// let page = kuchikiki::parse_html().one(response.text().await?); + +// Ok(page +// .select("a.release") +// .expect("missing heading") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// attributes.get("href").unwrap().to_string() +// }) +// .collect()) +// } + +// #[test] +// fn releases_by_stars() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.1.0") +// .github_stats("some/repo", 66, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.2.0") +// .github_stats("some/repo", 66, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 20, 4, 33, 50).unwrap()) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("crate_that_succeeded_without_github") +// .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) +// .version("0.2.0") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("crate_that_failed_with_github") +// .version("0.1.0") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) +// .build_result_failed() +// .create() +// .await?; + +// let links = get_release_links("/releases/stars", &env.web_app().await).await?; + +// // output is sorted by stars, not release-time +// assert_eq!(links.len(), 2); +// assert_eq!( +// links[0], +// "/crate_that_succeeded_with_github/0.2.0/crate_that_succeeded_with_github/" +// ); +// assert_eq!(links[1], "/crate/crate_that_failed_with_github/0.1.0"); + +// Ok(()) +// }) +// } + +// #[test] +// fn failures_by_stars() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.1.0") +// .github_stats("some/repo", 66, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.2.0") +// .github_stats("some/repo", 66, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 20, 4, 33, 50).unwrap()) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("crate_that_succeeded_without_github") +// .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) +// .version("0.2.0") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("crate_that_failed_with_github") +// .version("0.1.0") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) +// .build_result_failed() +// .create() +// .await?; + +// let links = get_release_links("/releases/failures", &env.web_app().await).await?; + +// // output is sorted by stars, not release-time +// assert_eq!(links.len(), 1); +// assert_eq!(links[0], "/crate/crate_that_failed_with_github/0.1.0"); + +// Ok(()) +// }) +// } + +// #[test] +// fn releases_failed_by_time() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.1.0") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) +// .create() +// .await?; +// // make sure that crates get at most one release shown, so they don't crowd the page +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) +// .version("0.2.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("crate_that_failed") +// .version("0.1.0") +// .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) +// .build_result_failed() +// .create() +// .await?; + +// let links = +// get_release_links("/releases/recent-failures", &env.web_app().await).await?; + +// assert_eq!(links.len(), 1); +// assert_eq!(links[0], "/crate/crate_that_failed/0.1.0"); + +// Ok(()) +// }) +// } + +// #[test] +// fn releases_homepage_and_recent() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.1.0") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 4, 33, 50).unwrap()) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .version("0.2.0-rc") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 4, 16, 8, 33, 50).unwrap()) +// .build_result_failed() +// .create() +// .await?; +// env.fake_release() +// .await +// .name("crate_that_succeeded_with_github") +// .github_stats("some/repo", 33, 22, 11) +// .release_time(Utc.with_ymd_and_hms(2020, 5, 16, 4, 33, 50).unwrap()) +// .version("0.2.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("crate_that_failed") +// .version("0.1.0") +// .release_time(Utc.with_ymd_and_hms(2020, 6, 16, 4, 33, 50).unwrap()) +// .build_result_failed() +// .create() +// .await?; + +// // make sure that crates get at most one release shown, so they don't crowd the homepage +// assert_eq!( +// get_release_links("/", &env.web_app().await).await?, +// [ +// "/crate/crate_that_failed/0.1.0", +// "/crate_that_succeeded_with_github/0.2.0/crate_that_succeeded_with_github/", +// ] +// ); + +// // but on the main release list they all show, including prerelease +// assert_eq!( +// get_release_links("/releases", &env.web_app().await).await?, +// [ +// "/crate/crate_that_failed/0.1.0", +// "/crate_that_succeeded_with_github/0.2.0/crate_that_succeeded_with_github/", +// "/crate/crate_that_succeeded_with_github/0.2.0-rc", +// "/crate_that_succeeded_with_github/0.1.0/crate_that_succeeded_with_github/", +// ] +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn release_activity() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// let empty_data = format!("data: [{}]", vec!["0"; 30].join(", ")); + +// // no data / only zeros without releases +// let response = web.get("/releases/activity").await?; +// assert!(response.status().is_success()); +// let text = response.text().await?; +// assert_eq!(text.matches(&empty_data).count(), 2); + +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_random_crate_that_failed") +// .build_result_failed() +// .create() +// .await?; + +// // same when the release is on the current day, since we ignore today. +// let response = web.get("/releases/activity").await?; +// assert!(response.status().is_success()); +// assert_eq!(response.text().await?.matches(&empty_data).count(), 2); + +// env.fake_release() +// .await +// .name("some_random_crate_yesterday") +// .release_time(Utc::now() - Duration::try_days(1).unwrap()) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_random_crate_that_failed_yesterday") +// .build_result_failed() +// .release_time(Utc::now() - Duration::try_days(1).unwrap()) +// .create() +// .await?; + +// // with releases yesterday we get the data we want +// let response = web.get("/releases/activity").await?; +// assert!(response.status().is_success()); +// let text = response.text().await?; +// // counts contain both releases +// assert!(text.contains(&format!("data: [{}, 2]", vec!["0"; 29].join(", ")))); +// // failures only one +// assert!(text.contains(&format!("data: [{}, 1]", vec!["0"; 29].join(", ")))); + +// Ok(()) +// }) +// } + +// #[test] +// fn release_feed() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// web.assert_success("/releases/feed").await?; + +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_random_crate_that_failed") +// .build_result_failed() +// .create() +// .await?; +// web.assert_success("/releases/feed").await?; +// Ok(()) +// }) +// } + +// #[test] +// fn test_releases_queue() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// let empty = +// kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); +// assert!( +// empty +// .select(".queue-list > strong") +// .expect("missing heading") +// .any(|el| el.text_contents().contains("nothing")) +// ); + +// assert!( +// !empty +// .select(".release > strong") +// .expect("missing heading") +// .any(|el| el.text_contents().contains("active CDN deployments")) +// ); + +// let queue = env.async_build_queue(); +// queue.add_crate("foo", &V1, 0, None).await?; +// queue.add_crate("bar", &V2, -10, None).await?; +// queue.add_crate("baz", &V3, 10, None).await?; + +// let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); +// let items = full +// .select(".queue-list > li") +// .expect("missing list items") +// .collect::>(); + +// assert_eq!(items.len(), 3); +// let expected = [ +// ("bar", V2, Some(10)), +// ("foo", V1, None), +// ("baz", V3, Some(-10)), +// ]; +// for (li, expected) in items.iter().zip(&expected) { +// let a = li.as_node().select_first("a").expect("missing link"); +// assert!(a.text_contents().contains(expected.0)); +// assert!(a.text_contents().contains(&expected.1.to_string())); + +// if let Some(priority) = expected.2 { +// assert!( +// li.text_contents() +// .contains(&format!("priority: {priority}")) +// ); +// } +// } + +// Ok(()) +// }); +// } + +// #[test] +// fn test_releases_queue_in_progress() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// // we have two queued releases, where the build for one is already in progress +// let queue = env.async_build_queue(); +// queue.add_crate("foo", &V1, 0, None).await?; +// queue.add_crate("bar", &V2, 0, None).await?; + +// env.fake_release() +// .await +// .name("foo") +// .version(V1) +// .builds(vec![ +// FakeBuild::default() +// .build_status(BuildStatus::InProgress) +// .rustc_version("rustc (blabla 2022-01-01)") +// .docsrs_version("docs.rs 4.0.0"), +// ]) +// .create() +// .await?; + +// let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); + +// let lists = full +// .select(".queue-list") +// .expect("missing queues") +// .collect::>(); +// assert_eq!(lists.len(), 2); + +// let in_progress_items: Vec<_> = lists[0] +// .as_node() +// .select("li > a") +// .expect("missing in progress list items") +// .map(|node| node.text_contents().trim().to_string()) +// .collect(); +// assert_eq!(in_progress_items, vec![format!("foo {V1}")]); + +// let queued_items: Vec<_> = lists[1] +// .as_node() +// .select("li > a") +// .expect("missing queued list items") +// .map(|node| node.text_contents().trim().to_string()) +// .collect(); +// assert_eq!(queued_items, vec![format!("bar {V2}")]); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_releases_rebuild_queue_empty() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// let empty = +// kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); + +// assert!( +// empty +// .select(".about > p") +// .expect("missing heading") +// .any(|el| el.text_contents().contains("We continuously rebuild")) +// ); + +// assert!( +// empty +// .select(".about > p") +// .expect("missing heading") +// .any(|el| el.text_contents().contains("crates in the rebuild queue")) +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_releases_rebuild_queue_with_crates() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// let queue = env.async_build_queue(); +// queue +// .add_crate("foo", &V1, PRIORITY_CONTINUOUS, None) +// .await?; +// queue +// .add_crate("bar", &V2, PRIORITY_CONTINUOUS + 1, None) +// .await?; +// queue +// .add_crate("baz", &V3, PRIORITY_CONTINUOUS - 1, None) +// .await?; + +// let full = kuchikiki::parse_html().one(web.get("/releases/queue").await?.text().await?); +// let items = full +// .select(".rebuild-queue-list > li") +// .expect("missing list items") +// .collect::>(); + +// // empty because expand_rebuild_queue is not set +// assert_eq!(items.len(), 0); +// assert!( +// full.select(".about > p") +// .expect("missing heading") +// .any(|el| el +// .text_contents() +// .contains("There are currently 2 crates in the rebuild queue")) +// ); + +// let full = kuchikiki::parse_html() +// .one(web.get("/releases/queue?expand=1").await?.text().await?); +// let build_queue_list = full +// .select(".queue-list > li") +// .expect("missing list items") +// .collect::>(); +// let rebuild_queue_list = full +// .select(".rebuild-queue-list > li") +// .expect("missing list items") +// .collect::>(); + +// assert_eq!(build_queue_list.len(), 1); +// assert_eq!(rebuild_queue_list.len(), 2); +// assert!( +// rebuild_queue_list +// .iter() +// .any(|li| li.text_contents().contains("foo")) +// ); +// assert!( +// rebuild_queue_list +// .iter() +// .any(|li| li.text_contents().contains("bar")) +// ); +// assert!( +// build_queue_list +// .iter() +// .any(|li| li.text_contents().contains("baz")) +// ); +// assert!( +// !rebuild_queue_list +// .iter() +// .any(|li| li.text_contents().contains("baz")) +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn home_page_links() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .add_owner(CrateOwner { +// login: "foobar".into(), +// avatar: "https://example.org/foobar".into(), +// kind: OwnerKind::User, +// }) +// .create() +// .await?; + +// let mut urls = vec![]; +// let mut seen = HashSet::new(); +// seen.insert("".to_owned()); + +// let resp = web.get("/").await?; +// resp.assert_cache_control(CachePolicy::ShortInCdnAndBrowser, env.config()); + +// assert!(resp.status().is_success()); + +// let html = kuchikiki::parse_html().one(resp.text().await?); +// for link in html.select("a").unwrap() { +// let link = link.as_node().as_element().unwrap(); + +// urls.push(link.attributes.borrow().get("href").unwrap().to_owned()); +// } + +// while let Some(url) = urls.pop() { +// // Skip urls we've already checked +// if !seen.insert(url.clone()) { +// continue; +// } + +// let resp = +// if url.starts_with("http://") || url.starts_with("https://") || url == "#" { +// // Skip external links +// continue; +// } else { +// web.get(&url).await? +// }; +// let status = resp.status(); +// assert!( +// status.is_success(), +// "failed to GET {url}: {status}, {:?}", +// resp.headers().get("Location"), +// ); +// } + +// Ok(()) +// }); +// } + +// #[test] +// fn check_releases_page_content() { +// // NOTE: this is a little fragile and may have to be updated if the HTML layout changes +// let sel = ".pure-menu-horizontal>.pure-menu-list>.pure-menu-item>.pure-menu-link>.title"; +// async_wrapper(|env| async move { +// for url in &[ +// "/releases", +// "/releases/stars", +// "/releases/recent-failures", +// "/releases/failures", +// "/releases/activity", +// "/releases/queue", +// ] { +// let page = kuchikiki::parse_html() +// .one(env.web_app().await.get(url).await.unwrap().text().await?); +// assert_eq!(page.select("#crate-title").unwrap().count(), 1); +// let not_matching = page +// .select(sel) +// .unwrap() +// .map(|node| node.text_contents()) +// .zip( +// [ +// "Recent", +// "Stars", +// "Recent Failures", +// "Failures By Stars", +// "Activity", +// "Queue", +// ] +// .iter(), +// ) +// .filter(|(a, b)| a.as_str() != **b) +// .collect::>(); +// if !not_matching.is_empty() { +// let not_found = not_matching.iter().map(|(_, b)| b).collect::>(); +// let found = not_matching.iter().map(|(a, _)| a).collect::>(); +// assert!( +// not_matching.is_empty(), +// "Titles did not match for URL `{url}`: not found: {not_found:?}, found: {found:?}", +// ); +// } +// } + +// Ok(()) +// }); +// } + +// #[test] +// fn check_owner_releases_redirect() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// web.assert_redirect_unchecked("/releases/someone", "https://crates.io/users/someone") +// .await?; +// Ok(()) +// }); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn crates_not_on_docsrs() -> Result<()> { +// let mut crates_io = mockito::Server::new_async().await; + +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .registry_api_host(crates_io.url().parse().unwrap()) +// .build()?, +// ) +// .await?; + +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; + +// let _m = crates_io +// .mock("GET", "/api/v1/crates") +// .match_query(Matcher::AllOf(vec![ +// Matcher::UrlEncoded("q".into(), "some_random_crate".into()), +// Matcher::UrlEncoded("per_page".into(), "30".into()), +// ])) +// .with_status(200) +// .with_header("content-type", "application/json") +// .with_body( +// json!({ +// "crates": [ +// { "name": "some_random_crate" }, +// { "name": "some_random_crate2" }, +// { "name": "some_random_crate3" }, +// ], +// "meta": { +// "next_page": "null", +// "prev_page": "null", +// } +// }) +// .to_string(), +// ) +// .create_async() +// .await; + +// let response = web.get("/releases/search?query=some_random_crate").await?; +// assert!(response.status().is_success()); + +// let page = kuchikiki::parse_html().one(response.text().await?); + +// assert_eq!(page.select("div.name.not-available").unwrap().count(), 2); +// assert_eq!( +// page.select("div.name:not(.not-available)").unwrap().count(), +// 1 +// ); + +// Ok(()) +// } + +// #[test] +// fn recent_failures_correct_pagination_links() { +// async_wrapper(|env| async move { +// for i in 0..RELEASES_IN_RELEASES + 1 { +// env.fake_release() +// .await +// .name("failed") +// .version(format!("0.0.{i}")) +// .build_result_failed() +// .create() +// .await?; +// } + +// let web = env.web_app().await; + +// let response = web.get("/releases/recent-failures").await?; +// assert!(response.status().is_success()); + +// let page = kuchikiki::parse_html().one(response.text().await?); +// assert_eq!( +// page.select("div.description") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "Recent crates failed to build" +// ); + +// let next_page_link = page.select("div.pagination > a").unwrap().next().unwrap(); +// assert_eq!(next_page_link.text_contents().trim(), "Next Page"); + +// let next_page_url = next_page_link +// .attributes +// .borrow() +// .get("href") +// .unwrap() +// .to_owned(); +// assert_eq!(next_page_url, "/releases/recent-failures/2"); + +// let response = web.get(&next_page_url).await?; +// assert!(response.status().is_success()); + +// let page = kuchikiki::parse_html().one(response.text().await?); +// assert_eq!( +// page.select("div.description") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "Recent crates failed to build" +// ); +// assert_eq!( +// page.select(".recent-releases-container > ul > li .name") +// .unwrap() +// .next() +// .unwrap() +// .text_contents() +// .trim(), +// "failed-0.0.0" +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn test_search_std() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// async fn inner(web: &axum::Router, krate: &str) -> Result<(), anyhow::Error> { +// let full = kuchikiki::parse_html().one( +// web.get(&format!("/releases/search?query={krate}")) +// .await? +// .text() +// .await?, +// ); +// let items = full +// .select("ul a.release") +// .expect("missing list items") +// .collect::>(); + +// // empty because expand_rebuild_queue is not set +// let item_element = items.first().unwrap(); +// let item = item_element.as_node(); +// assert_eq!( +// item.select(".name") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "std" +// ); +// assert_eq!( +// item.select(".description") +// .unwrap() +// .next() +// .unwrap() +// .text_contents(), +// "Rust standard library", +// ); +// assert_eq!( +// item_element.attributes.borrow().get("href").unwrap(), +// "https://doc.rust-lang.org/stable/std/" +// ); + +// Ok(()) +// } + +// inner(&web, "std").await?; +// inner(&web, "libstd").await?; + +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_web/src/routes.rs b/crates/docs_rs_web/src/routes.rs index 0c0e4cc73..fd9783535 100644 --- a/crates/docs_rs_web/src/routes.rs +++ b/crates/docs_rs_web/src/routes.rs @@ -1,4 +1,4 @@ -use crate::web::{ +use crate::{ cache::CachePolicy, error::AxumNope, metrics::request_recorder, statics::build_static_router, }; use askama::Template; @@ -373,87 +373,87 @@ async fn fallback() -> impl IntoResponse { AxumNope::ResourceNotFound } -#[cfg(test)] -mod tests { - use crate::test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; - use crate::web::cache::CachePolicy; - use reqwest::StatusCode; +// #[cfg(test)] +// mod tests { +// use crate::test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}; +// use crate::web::cache::CachePolicy; +// use reqwest::StatusCode; - #[test] - fn test_root_redirects() { - async_wrapper(|env| async move { - let web = env.web_app().await; - let config = env.config(); - // These are "well-known" resources that will be requested from the root, but support - // redirection - web.assert_redirect_cached( - "/favicon.ico", - "/-/static/favicon.ico", - CachePolicy::ForeverInCdnAndBrowser, - config, - ) - .await?; - web.assert_redirect_cached( - "/robots.txt", - "/-/static/robots.txt", - CachePolicy::ForeverInCdnAndBrowser, - config, - ) - .await?; +// #[test] +// fn test_root_redirects() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// let config = env.config(); +// // These are "well-known" resources that will be requested from the root, but support +// // redirection +// web.assert_redirect_cached( +// "/favicon.ico", +// "/-/static/favicon.ico", +// CachePolicy::ForeverInCdnAndBrowser, +// config, +// ) +// .await?; +// web.assert_redirect_cached( +// "/robots.txt", +// "/-/static/robots.txt", +// CachePolicy::ForeverInCdnAndBrowser, +// config, +// ) +// .await?; - // This has previously been served with a url pointing to the root, it may be - // plausible to remove the redirects in the future, but for now we need to keep serving - // it. - web.assert_redirect_cached( - "/opensearch.xml", - "/-/static/opensearch.xml", - CachePolicy::ForeverInCdnAndBrowser, - config, - ) - .await?; +// // This has previously been served with a url pointing to the root, it may be +// // plausible to remove the redirects in the future, but for now we need to keep serving +// // it. +// web.assert_redirect_cached( +// "/opensearch.xml", +// "/-/static/opensearch.xml", +// CachePolicy::ForeverInCdnAndBrowser, +// config, +// ) +// .await?; - Ok(()) - }); - } +// Ok(()) +// }); +// } - #[test] - fn serve_rustdoc_content_not_found() { - async_wrapper(|env| async move { - let response = env - .web_app() - .await - .get("/-/rustdoc.static/style.css") - .await?; - assert_eq!(response.status(), StatusCode::NOT_FOUND); - response.assert_cache_control(CachePolicy::NoCaching, env.config()); - Ok(()) - }) - } +// #[test] +// fn serve_rustdoc_content_not_found() { +// async_wrapper(|env| async move { +// let response = env +// .web_app() +// .await +// .get("/-/rustdoc.static/style.css") +// .await?; +// assert_eq!(response.status(), StatusCode::NOT_FOUND); +// response.assert_cache_control(CachePolicy::NoCaching, env.config()); +// Ok(()) +// }) +// } - #[test] - fn serve_rustdoc_content() { - async_wrapper(|env| async move { - let web = env.web_app().await; - let storage = env.async_storage(); - storage - .store_one("/rustdoc-static/style.css", "content".as_bytes()) - .await?; - storage - .store_one("/will_not/be_found.css", "something".as_bytes()) - .await?; +// #[test] +// fn serve_rustdoc_content() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// let storage = env.async_storage(); +// storage +// .store_one("/rustdoc-static/style.css", "content".as_bytes()) +// .await?; +// storage +// .store_one("/will_not/be_found.css", "something".as_bytes()) +// .await?; - let response = web.get("/-/rustdoc.static/style.css").await?; - assert!(response.status().is_success()); - response.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); - assert_eq!(response.text().await?, "content"); +// let response = web.get("/-/rustdoc.static/style.css").await?; +// assert!(response.status().is_success()); +// response.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); +// assert_eq!(response.text().await?, "content"); - assert_eq!( - web.get("/-/rustdoc.static/will_not/be_found.css") - .await? - .status(), - StatusCode::NOT_FOUND - ); - Ok(()) - }) - } -} +// assert_eq!( +// web.get("/-/rustdoc.static/will_not/be_found.css") +// .await? +// .status(), +// StatusCode::NOT_FOUND +// ); +// Ok(()) +// }) +// } +// } diff --git a/crates/docs_rs_web/src/rustdoc.rs b/crates/docs_rs_web/src/rustdoc.rs index 4b5529a21..5e5d4ac7a 100644 --- a/crates/docs_rs_web/src/rustdoc.rs +++ b/crates/docs_rs_web/src/rustdoc.rs @@ -1,27 +1,24 @@ //! rustdoc handlerr use crate::{ - Config, RUSTDOC_STATIC_STORAGE_PREFIX, - registry_api::OwnerKind, - utils, - web::{ - MetaData, ReqVersion, axum_cached_redirect, - cache::CachePolicy, - crate_details::CrateDetails, - csp::Csp, - error::{AxumNope, AxumResult}, - extractors::{ - DbConnection, Path, WantedCompression, - rustdoc::{PageKind, RustdocParams}, - }, - file::StreamingFile, - licenses, match_version, - metrics::WebMetrics, - page::{ - TemplateData, - templates::{RenderBrands, RenderRegular, RenderSolid, filters}, - }, + MetaData, ReqVersion, axum_cached_redirect, + cache::CachePolicy, + config::Config, + crate_details::CrateDetails, + csp::Csp, + error::{AxumNope, AxumResult}, + extractors::{ + DbConnection, Path, WantedCompression, + rustdoc::{PageKind, RustdocParams}, + }, + file::StreamingFile, + licenses, match_version, + metrics::WebMetrics, + page::{ + TemplateData, + templates::{RenderBrands, RenderRegular, RenderSolid, filters}, }, + utils, }; use anyhow::{Context as _, anyhow}; use askama::Template; @@ -40,11 +37,13 @@ use docs_rs_cargo_metadata::Dependency; use docs_rs_database::types::version::Version; use docs_rs_headers::etag::ETagComputer; use docs_rs_headers::{IfNoneMatch, X_ROBOTS_TAG}; +use docs_rs_registry_api::OwnerKind; use docs_rs_storage::{ AsyncStorage, CompressionAlgorithm, RustdocJsonFormatVersion, StreamingBlob, errors::PathNotFoundError, rustdoc_archive_path, rustdoc_json_path, }; use docs_rs_utils::BUILD_VERSION; +use docs_rs_utils::RUSTDOC_STATIC_STORAGE_PREFIX; use docs_rs_web_utils::escaped_uri::EscapedURI; use http::{HeaderMap, HeaderValue, Uri, header::CONTENT_DISPOSITION, uri::Authority}; use serde::Deserialize; @@ -1077,2619 +1076,2619 @@ pub(crate) async fn static_asset_handler( .into_response(if_none_match.as_deref())) } -#[cfg(test)] -mod test { - use super::*; - use crate::{ - Config, - db::types::version::Version, - docbuilder::{RUSTDOC_JSON_COMPRESSION_ALGORITHMS, read_format_version_from_rustdoc_json}, - registry_api::{CrateOwner, OwnerKind}, - storage::decompress, - test::*, - utils::Dependency, - web::{cache::CachePolicy, encode_url_path}, - }; - use anyhow::{Context, Result}; - use chrono::{NaiveDate, Utc}; - use kuchikiki::traits::TendrilSink; - use pretty_assertions::assert_eq; - use reqwest::StatusCode; - use std::{collections::BTreeMap, io}; - use test_case::test_case; - use tracing::info; - - /// try decompressing the zip & read the content - fn check_archive_consistency(compressed_body: &[u8]) -> anyhow::Result<()> { - let mut zip = zip::ZipArchive::new(io::Cursor::new(compressed_body))?; - for i in 0..zip.len() { - let mut file = zip.by_index(i)?; - - let mut buf = Vec::new(); - io::copy(&mut file, &mut buf)?; - } - - Ok(()) - } - - async fn try_latest_version_redirect( - path: &str, - web: &axum::Router, - config: &Config, - ) -> Result, anyhow::Error> { - web.assert_success(path).await?; - let response = web.get(path).await?; - response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, config); - let data = response.text().await?; - info!( - "fetched path {} and got content {}\nhelp: if this is missing the header, remember to add ", - path, data - ); - let dom = kuchikiki::parse_html().one(data); - - if let Some(elem) = dom - .select("form > ul > li > a.warn") - .expect("invalid selector") - .next() - { - let link = elem.attributes.borrow().get("href").unwrap().to_string(); - let response = web.get(&link).await?; - response.assert_cache_control(CachePolicy::ForeverInCdn, config); - assert!(response.status().is_success() || response.status().is_redirection()); - Ok(Some(link)) - } else { - Ok(None) - } - } - - async fn latest_version_redirect( - path: &str, - web: &axum::Router, - config: &Config, - ) -> Result { - try_latest_version_redirect(path, web, config) - .await? - .with_context(|| anyhow::anyhow!("no redirect found for {}", path)) - } - - #[test_case(true)] - #[test_case(false)] - // https://github.com/rust-lang/docs.rs/issues/2313 - fn help_html(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("krate") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("help.html") - .create() - .await?; - let web = env.web_app().await; - web.assert_success_cached( - "/krate/0.1.0/help.html", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - - web.assert_success_and_conditional_get("/krate/0.1.0/help.html") - .await?; - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - // regression test for https://github.com/rust-lang/docs.rs/issues/552 - fn settings_html(archive_storage: bool) { - async_wrapper(|env| async move { - // first release works, second fails - env.fake_release() - .await - .name("buggy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("settings.html") - .rustdoc_file("scrape-examples-help.html") - .rustdoc_file("directory_1/index.html") - .rustdoc_file("directory_2.html/index.html") - .rustdoc_file("all.html") - .rustdoc_file("directory_3/.gitignore") - .rustdoc_file("directory_4/empty_file_no_ext") - .create() - .await?; - env.fake_release() - .await - .name("buggy") - .version("0.2.0") - .archive_storage(archive_storage) - .build_result_failed() - .create() - .await?; - let web = env.web_app().await; - web.assert_success_cached("/", CachePolicy::ShortInCdnAndBrowser, env.config()) - .await?; - web.assert_success_cached( - "/crate/buggy/0.1.0", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/directory_1/index.html", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/directory_2.html/index.html", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/directory_3/.gitignore", - CachePolicy::ForeverInCdnAndBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/settings.html", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/scrape-examples-help.html", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/all.html", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_success_cached( - "/buggy/0.1.0/directory_4/empty_file_no_ext", - CachePolicy::ForeverInCdnAndBrowser, - env.config(), - ) - .await?; - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn default_target_redirects_to_base(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .create() - .await?; - - let web = env.web_app().await; - // no explicit default-target - let base = "/dummy/0.1.0/dummy/"; - web.assert_success_cached( - base, - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - web.assert_redirect_cached( - "/dummy/0.1.0/x86_64-unknown-linux-gnu/dummy/", - base, - CachePolicy::ForeverInCdn, - env.config(), - ) - .await?; - - web.assert_success_and_conditional_get("/dummy/latest/dummy/") - .await?; - - // set an explicit target that requires cross-compile - let target = "x86_64-pc-windows-msvc"; - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .default_target(target) - .create() - .await?; - let base = "/dummy/0.2.0/dummy/"; - web.assert_success_and_conditional_get(base).await?; - web.assert_redirect("/dummy/0.2.0/x86_64-pc-windows-msvc/dummy/", base) - .await?; - - // set an explicit target without cross-compile - // also check that /:crate/:version/:platform/all.html doesn't panic - let target = "x86_64-unknown-linux-gnu"; - env.fake_release() - .await - .name("dummy") - .version("0.3.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .rustdoc_file("all.html") - .default_target(target) - .create() - .await?; - let base = "/dummy/0.3.0/dummy/"; - web.assert_success(base).await?; - web.assert_redirect("/dummy/0.3.0/x86_64-unknown-linux-gnu/dummy/", base) - .await?; - web.assert_redirect( - "/dummy/0.3.0/x86_64-unknown-linux-gnu/all.html", - "/dummy/0.3.0/all.html", - ) - .await?; - web.assert_redirect("/dummy/0.3.0/", base).await?; - web.assert_redirect("/dummy/0.3.0/index.html", base).await?; - Ok(()) - }); - } - - #[test] - fn latest_url() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .rustdoc_file("dummy/index.html") - .create() - .await?; - - let resp = env - .web_app() - .await - .get("/dummy/latest/dummy/") - .await? - .error_for_status()?; - - resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - let body = resp.text().await?; - assert!( - body.contains("
Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .cache_control_stale_while_revalidate(Some(2592000)) - .build()?, - ) - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .rustdoc_file("dummy/index.html") - .create() - .await?; - - let web = env.web_app().await; - - { - let resp = web.get("/dummy/latest/dummy/").await?; - resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - web.assert_conditional_get("/dummy/latest/dummy/", &resp) - .await?; - } - - { - let resp = web.get("/dummy/0.1.0/dummy/").await?; - resp.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); - web.assert_conditional_get("/dummy/0.1.0/dummy/", &resp) - .await?; - } - Ok(()) - } - - #[test_case(true)] - #[test_case(false)] - fn go_to_latest_version(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/blah/index.html") - .rustdoc_file("dummy/blah/blah.html") - .rustdoc_file("dummy/struct.will-be-deleted.html") - .create() - .await?; - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/blah/index.html") - .rustdoc_file("dummy/blah/blah.html") - .create() - .await?; - - let web = env.web_app().await; - - // check it works at all - let redirect = - latest_version_redirect("/dummy/0.1.0/dummy/", &web, env.config()).await?; - assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/"); - - let redirect = - latest_version_redirect("/dummy/0.1.0/dummy/blah/", &web, env.config()).await?; - assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/blah/"); - - // check it keeps the subpage - let redirect = - latest_version_redirect("/dummy/0.1.0/dummy/blah/blah.html", &web, env.config()) - .await?; - assert_eq!( - redirect, - "/crate/dummy/latest/target-redirect/dummy/blah/blah.html" - ); - - // check it also works for deleted pages - let redirect = latest_version_redirect( - "/dummy/0.1.0/dummy/struct.will-be-deleted.html", - &web, - env.config(), - ) - .await?; - assert_eq!( - redirect, - "/crate/dummy/latest/target-redirect/dummy/struct.will-be-deleted.html" - ); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn go_to_latest_version_keeps_platform(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .add_platform("x86_64-pc-windows-msvc") - .rustdoc_file("dummy/struct.Blah.html") - .create() - .await?; - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .add_platform("x86_64-pc-windows-msvc") - .create() - .await?; - - let web = env.web_app().await; - - let redirect = latest_version_redirect( - "/dummy/0.1.0/x86_64-pc-windows-msvc/dummy/index.html", - &web, - env.config(), - ) - .await?; - assert_eq!( - redirect, - "/crate/dummy/latest/target-redirect/x86_64-pc-windows-msvc/dummy/" - ); - - let redirect = latest_version_redirect( - "/dummy/0.1.0/x86_64-pc-windows-msvc/dummy/", - &web, - env.config(), - ) - .await?; - assert_eq!( - redirect, - "/crate/dummy/latest/target-redirect/x86_64-pc-windows-msvc/dummy/" - ); - - let redirect = latest_version_redirect( - "/dummy/0.1.0/x86_64-pc-windows-msvc/dummy/struct.Blah.html", - &web, - env.config(), - ) - .await?; - assert_eq!( - redirect, - "/crate/dummy/latest/target-redirect/x86_64-pc-windows-msvc/dummy/struct.Blah.html" - ); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn redirect_latest_goes_to_crate_if_build_failed(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .create() - .await?; - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .build_result_failed() - .create() - .await?; - - let web = env.web_app().await; - let redirect = - latest_version_redirect("/dummy/0.1.0/dummy/", &web, env.config()).await?; - assert_eq!(redirect, "/crate/dummy/latest"); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn redirect_latest_does_not_go_to_yanked_versions(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .create() - .await?; - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .create() - .await?; - env.fake_release() - .await - .name("dummy") - .version("0.2.1") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .yanked(true) - .create() - .await?; - - let web = env.web_app().await; - let redirect = - latest_version_redirect("/dummy/0.1.0/dummy/", &web, env.config()).await?; - assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/"); - - let redirect = - latest_version_redirect("/dummy/0.2.1/dummy/", &web, env.config()).await?; - assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/"); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn yanked_release_shows_warning_in_nav(archive_storage: bool) { - async fn has_yanked_warning(path: &str, web: &axum::Router) -> Result { - web.assert_success(path).await?; - let data = web.get(path).await?.text().await?; - Ok(kuchikiki::parse_html() - .one(data) - .select("form > ul > li > .warn") - .expect("invalid selector") - .any(|el| el.text_contents().contains("yanked"))) - } - - async_wrapper(|env| async move { - let web = env.web_app().await; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .yanked(true) - .create() - .await?; - - assert!(has_yanked_warning("/dummy/0.1.0/dummy/", &web).await?); - - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .yanked(true) - .create() - .await?; - - assert!(has_yanked_warning("/dummy/0.1.0/dummy/", &web).await?); - - Ok(()) - }) - } - - #[test] - fn badges_are_urlencoded() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("zstd") - .version("0.5.1+zstd.1.4.4") - .create() - .await?; - - let frontend = env.web_app().await; - let response = frontend - .assert_redirect_cached_unchecked( - "/zstd/badge.svg", - "https://img.shields.io/docsrs/zstd/latest", - CachePolicy::ForeverInCdnAndBrowser, - env.config(), - ) - .await?; - assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn crate_name_percent_decoded_redirect(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake-crate") - .version("0.0.1") - .archive_storage(archive_storage) - .rustdoc_file("fake_crate/index.html") - .create() - .await?; - - let web = env.web_app().await; - web.assert_redirect("/fake%2Dcrate", "/fake-crate/latest/fake_crate/") - .await?; - - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn base_redirect_handles_mismatched_separators(archive_storage: bool) { - async_wrapper(|env| async move { - let rels = [ - ("dummy-dash", "0.1.0"), - ("dummy-dash", "0.2.0"), - ("dummy_underscore", "0.1.0"), - ("dummy_underscore", "0.2.0"), - ("dummy_mixed-separators", "0.1.0"), - ("dummy_mixed-separators", "0.2.0"), - ]; - - for (name, version) in rels { - env.fake_release() - .await - .name(name) - .version(version) - .archive_storage(archive_storage) - .rustdoc_file(&(name.replace('-', "_") + "/index.html")) - .create() - .await?; - } - - let web = env.web_app().await; - - web.assert_redirect("/dummy_dash", "/dummy-dash/latest/dummy_dash/") - .await?; - web.assert_redirect("/dummy_dash/*", "/dummy-dash/latest/dummy_dash/") - .await?; - web.assert_redirect("/dummy_dash/0.1.0", "/dummy-dash/0.1.0/dummy_dash/") - .await?; - web.assert_redirect( - "/dummy-underscore", - "/dummy_underscore/latest/dummy_underscore/", - ) - .await?; - web.assert_redirect( - "/dummy-underscore/*", - "/dummy_underscore/latest/dummy_underscore/", - ) - .await?; - web.assert_redirect( - "/dummy-underscore/0.1.0", - "/dummy_underscore/0.1.0/dummy_underscore/", - ) - .await?; - web.assert_redirect( - "/dummy-mixed_separators", - "/dummy_mixed-separators/latest/dummy_mixed_separators/", - ) - .await?; - web.assert_redirect( - "/dummy_mixed_separators/*", - "/dummy_mixed-separators/latest/dummy_mixed_separators/", - ) - .await?; - web.assert_redirect( - "/dummy-mixed-separators/0.1.0", - "/dummy_mixed-separators/0.1.0/dummy_mixed_separators/", - ) - .await?; - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn specific_pages_do_not_handle_mismatched_separators(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy-dash") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy_dash/index.html") - .create() - .await?; - - env.fake_release() - .await - .name("dummy_mixed-separators") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy_mixed_separators/index.html") - .create() - .await?; - - let web = env.web_app().await; - - web.assert_success("/dummy-dash/0.1.0/dummy_dash/index.html") - .await?; - web.assert_redirect_unchecked( - "/crate/dummy_mixed-separators", - "/crate/dummy_mixed-separators/latest", - ) - .await?; - - web.assert_redirect( - "/dummy_dash/0.1.0/dummy_dash/index.html", - "/dummy-dash/0.1.0/dummy_dash/", - ) - .await?; - - assert_eq!( - web.get("/crate/dummy_mixed_separators/latest") - .await? - .status(), - StatusCode::NOT_FOUND - ); - - Ok(()) - }) - } - - #[test] - fn nonexistent_crate_404s() { - async_wrapper(|env| async move { - assert_eq!( - env.web_app().await.get("/dummy").await?.status(), - StatusCode::NOT_FOUND - ); - - Ok(()) - }) - } - - #[test] - fn no_target_target_redirect_404s() { - async_wrapper(|env| async move { - assert_eq!( - env.web_app() - .await - .get("/crate/dummy/0.1.0/target-redirect") - .await? - .status(), - StatusCode::NOT_FOUND - ); - - assert_eq!( - env.web_app() - .await - .get("/crate/dummy/0.1.0/target-redirect/") - .await? - .status(), - StatusCode::NOT_FOUND - ); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn platform_links_go_to_current_path(archive_storage: bool) { - async fn get_platform_links( - path: &str, - web: &axum::Router, - ) -> Result, anyhow::Error> { - web.assert_success(path).await?; - let data = web.get(path).await?.text().await?; - let dom = kuchikiki::parse_html().one(data); - Ok(dom - .select(r#"a[aria-label="Platform"] + ul li a"#) - .expect("invalid selector") - .map(|el| { - let attributes = el.attributes.borrow(); - let url = attributes.get("href").expect("href").to_string(); - let rel = attributes.get("rel").unwrap_or("").to_string(); - let name = el.text_contents(); - (name, url, rel) - }) - .collect()) - } - async fn assert_platform_links( - web: &axum::Router, - path: &str, - links: &[(&str, &str)], - ) -> Result<(), anyhow::Error> { - let mut links: BTreeMap<_, _> = links.iter().copied().collect(); - - for (platform, link, rel) in dbg!(get_platform_links(path, web).await?) { - assert_eq!(rel, "nofollow"); - web.assert_redirect(&link, links.remove(platform.as_str()).unwrap()) - .await?; - } - - assert!(links.is_empty()); - - Ok(()) - } - - async_wrapper(|env| async move { - let web = env.web_app().await; - - // no explicit default-target - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .rustdoc_file("dummy/struct.Dummy.html") - .add_target("x86_64-unknown-linux-gnu") - .create() - .await?; - - assert_platform_links( - &web, - "/dummy/0.1.0/dummy/", - &[("x86_64-unknown-linux-gnu", "/dummy/0.1.0/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.1.0/dummy/", - &[("x86_64-unknown-linux-gnu", "/dummy/0.1.0/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.1.0/dummy/struct.Dummy.html", - &[( - "x86_64-unknown-linux-gnu", - "/dummy/0.1.0/dummy/struct.Dummy.html", - )], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/", - &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/index.html", - &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/struct.Dummy.html", - &[( - "x86_64-unknown-linux-gnu", - "/dummy/latest/dummy/struct.Dummy.html", - )], - ) - .await?; - - // set an explicit target that requires cross-compile - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .rustdoc_file("dummy/struct.Dummy.html") - .default_target("x86_64-pc-windows-msvc") - .create() - .await?; - - assert_platform_links( - &web, - "/dummy/0.2.0/dummy/", - &[("x86_64-pc-windows-msvc", "/dummy/0.2.0/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.2.0/dummy/index.html", - &[("x86_64-pc-windows-msvc", "/dummy/0.2.0/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.2.0/dummy/struct.Dummy.html", - &[( - "x86_64-pc-windows-msvc", - "/dummy/0.2.0/dummy/struct.Dummy.html", - )], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/", - &[("x86_64-pc-windows-msvc", "/dummy/latest/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/index.html", - &[("x86_64-pc-windows-msvc", "/dummy/latest/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/struct.Dummy.html", - &[( - "x86_64-pc-windows-msvc", - "/dummy/latest/dummy/struct.Dummy.html", - )], - ) - .await?; - - // set an explicit target without cross-compile - env.fake_release() - .await - .name("dummy") - .version("0.3.0") - .archive_storage(archive_storage) - .rustdoc_file("dummy/index.html") - .rustdoc_file("dummy/struct.Dummy.html") - .default_target("x86_64-unknown-linux-gnu") - .create() - .await?; - - assert_platform_links( - &web, - "/dummy/0.3.0/dummy/", - &[("x86_64-unknown-linux-gnu", "/dummy/0.3.0/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.3.0/dummy/index.html", - &[("x86_64-unknown-linux-gnu", "/dummy/0.3.0/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.3.0/dummy/struct.Dummy.html", - &[( - "x86_64-unknown-linux-gnu", - "/dummy/0.3.0/dummy/struct.Dummy.html", - )], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/", - &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/index.html", - &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/dummy/struct.Dummy.html", - &[( - "x86_64-unknown-linux-gnu", - "/dummy/latest/dummy/struct.Dummy.html", - )], - ) - .await?; - - // multiple targets - env.fake_release() - .await - .name("dummy") - .version("0.4.0") - .archive_storage(archive_storage) - .rustdoc_file("settings.html") - .rustdoc_file("dummy/index.html") - .rustdoc_file("dummy/struct.Dummy.html") - .rustdoc_file("dummy/struct.DefaultOnly.html") - .rustdoc_file("x86_64-pc-windows-msvc/settings.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/struct.Dummy.html") - .rustdoc_file("x86_64-pc-windows-msvc/dummy/struct.WindowsOnly.html") - .default_target("x86_64-unknown-linux-gnu") - .add_target("x86_64-pc-windows-msvc") - .create() - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/settings.html", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/settings.html", - ), - ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/settings.html"), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/latest/settings.html", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/latest/x86_64-pc-windows-msvc/settings.html", - ), - ("x86_64-unknown-linux-gnu", "/dummy/latest/settings.html"), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/dummy/", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", - ), - ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/dummy/"), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", - ), - ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/dummy/"), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/dummy/", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", - ), - ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/dummy/"), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/dummy/struct.DefaultOnly.html", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/?search=DefaultOnly", - ), - ( - "x86_64-unknown-linux-gnu", - "/dummy/0.4.0/dummy/struct.DefaultOnly.html", - ), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/dummy/struct.Dummy.html", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.Dummy.html", - ), - ( - "x86_64-unknown-linux-gnu", - "/dummy/0.4.0/dummy/struct.Dummy.html", - ), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.Dummy.html", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.Dummy.html", - ), - ( - "x86_64-unknown-linux-gnu", - "/dummy/0.4.0/dummy/struct.Dummy.html", - ), - ], - ) - .await?; - - assert_platform_links( - &web, - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.WindowsOnly.html", - &[ - ( - "x86_64-pc-windows-msvc", - "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.WindowsOnly.html", - ), - ( - "x86_64-unknown-linux-gnu", - "/dummy/0.4.0/dummy/?search=WindowsOnly", - ), - ], - ) - .await?; - - Ok(()) - }); - } - - #[test] - fn test_target_redirect_with_corrected_name() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo_ab") - .version("0.0.1") - .archive_storage(true) - .create() - .await?; - - let web = env.web_app().await; - web.assert_redirect_unchecked( - "/crate/foo-ab/0.0.1/target-redirect/x86_64-unknown-linux-gnu", - "/foo-ab/0.0.1/foo_ab/", - ) - .await?; - // `-` becomes `_` but we keep the query arguments. - web.assert_redirect_unchecked( - "/foo-ab/0.0.1/foo_ab/?search=a", - "/foo_ab/0.0.1/foo_ab/?search=a", - ) - .await?; - Ok(()) - }) - } - - #[test] - fn test_target_redirect_not_found() { - async_wrapper(|env| async move { - let web = env.web_app().await; - assert_eq!( - web.get("/crate/fdsafdsafdsafdsa/0.1.0/target-redirect/aarch64-apple-darwin/") - .await? - .status(), - StatusCode::NOT_FOUND, - ); - Ok(()) - }) - } - - #[test] - fn test_redirect_to_latest_302() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .create() - .await?; - let web = env.web_app().await; - web.assert_redirect_cached( - "/dummy", - "/dummy/latest/dummy/", - CachePolicy::ForeverInCdn, - env.config(), - ) - .await?; - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_fully_yanked_crate_404s(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .archive_storage(archive_storage) - .yanked(true) - .create() - .await?; - - assert_eq!( - env.web_app() - .await - .get("/crate/dummy/latest") - .await? - .status(), - StatusCode::NOT_FOUND - ); - - assert_eq!( - env.web_app().await.get("/dummy/").await?.status(), - StatusCode::NOT_FOUND - ); - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_no_trailing_target_slash(archive_storage: bool) { - // regression test for https://github.com/rust-lang/docs.rs/issues/856 - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(archive_storage) - .create() - .await?; - let web = env.web_app().await; - web.assert_redirect( - "/crate/dummy/0.1.0/target-redirect/aarch64-apple-darwin", - "/dummy/0.1.0/dummy/", - ) - .await?; - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(archive_storage) - .add_platform("aarch64-apple-darwin") - .create() - .await?; - web.assert_redirect( - "/crate/dummy/0.2.0/target-redirect/aarch64-apple-darwin", - "/dummy/0.2.0/aarch64-apple-darwin/dummy/", - ) - .await?; - web.assert_redirect( - "/crate/dummy/0.2.0/target-redirect/platform-that-does-not-exist", - "/dummy/0.2.0/dummy/", - ) - .await?; - Ok(()) - }) - } - - #[test] - fn test_redirect_crate_coloncolon_path() { - async_wrapper(|env| async move { - let web = env.web_app().await; - env.fake_release() - .await - .name("some_random_crate") - .create() - .await?; - env.fake_release() - .await - .name("some_other_crate") - .create() - .await?; - - web.assert_redirect( - "/some_random_crate::somepath", - "/some_random_crate/latest/some_random_crate/?search=somepath", - ) - .await?; - web.assert_redirect( - "/some_random_crate::some::path", - "/some_random_crate/latest/some_random_crate/?search=some%3A%3Apath", - ) - .await?; - web.assert_redirect( - "/some_random_crate::some::path?go_to_first=true", - "/some_random_crate/latest/some_random_crate/?go_to_first=true&search=some%3A%3Apath", - ).await?; - - web.assert_redirect_unchecked( - "/std::some::path", - "https://doc.rust-lang.org/stable/std/?search=some%3A%3Apath", - ) - .await?; - - Ok(()) - }) - } - - #[test] - // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655147643 - fn test_no_panic_on_missing_kind() { - async_wrapper(|env| async move { - let id = env - .fake_release() - .await - .name("strum") - .version("0.13.0") - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - // https://stackoverflow.com/questions/18209625/how-do-i-modify-fields-inside-the-new-postgresql-json-datatype - sqlx::query!( - r#"UPDATE releases SET dependencies = dependencies::jsonb #- '{0,2}' WHERE id = $1"#, id.0 - ).execute(&mut *conn).await?; - - let web = env.web_app().await; - web.assert_success("/strum/0.13.0/strum/").await?; - web.assert_success("/crate/strum/0.13.0").await?; - Ok(()) - }) - } - - #[test] - // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655154405 - fn test_readme_rendered_as_html() { - async_wrapper(|env| async move { - let readme = "# Overview"; - env.fake_release() - .await - .name("strum") - .version("0.18.0") - .readme(readme) - .create() - .await?; - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/strum/0.18.0") - .await? - .text() - .await?, - ); - let rendered = page.select_first("#main").expect("missing readme"); - println!("{}", rendered.text_contents()); - rendered - .as_node() - .select_first("h1") - .expect("`# Overview` was not rendered as HTML"); - Ok(()) - }) - } - - #[test] - // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655149288 - fn test_build_status_is_accurate() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("hexponent") - .version("0.3.0") - .create() - .await?; - env.fake_release() - .await - .name("hexponent") - .version("0.2.0") - .build_result_failed() - .create() - .await?; - let web = env.web_app().await; - - let status = |version| { - let web = web.clone(); - async move { - let page = kuchikiki::parse_html() - .one(web.get("/crate/hexponent/0.3.0").await?.text().await?); - let selector = format!(r#"ul > li a[href="/crate/hexponent/{version}"]"#); - let anchor = page - .select(&selector) - .unwrap() - .find(|a| a.text_contents().trim().split(" ").next().unwrap() == version) - .unwrap(); - let attributes = anchor.as_node().as_element().unwrap().attributes.borrow(); - let classes = attributes.get("class").unwrap(); - Ok::<_, anyhow::Error>(classes.split(' ').all(|c| c != "warn")) - } - }; - - assert!(status("0.3.0").await?); - assert!(!status("0.2.0").await?); - Ok(()) - }) - } - - #[test] - fn test_crate_release_version_and_date() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("hexponent") - .version("0.3.0") - .release_time( - NaiveDate::from_ymd_opt(2021, 1, 12) - .unwrap() - .and_hms_milli_opt(0, 0, 0, 0) - .unwrap() - .and_local_timezone(Utc) - .unwrap(), - ) - .create() - .await?; - env.fake_release() - .await - .name("hexponent") - .version("0.2.0") - .release_time( - NaiveDate::from_ymd_opt(2020, 12, 1) - .unwrap() - .and_hms_milli_opt(0, 0, 0, 0) - .unwrap() - .and_local_timezone(Utc) - .unwrap(), - ) - .create() - .await?; - let web = env.web_app().await; - - let status = |version, date| { - let web = web.clone(); - async move { - let page = kuchikiki::parse_html() - .one(web.get("/crate/hexponent/0.3.0").await?.text().await?); - let selector = format!(r#"ul > li a[href="/crate/hexponent/{version}"]"#); - let full = format!("{version} ({date})"); - Result::::Ok(page.select(&selector).unwrap().any(|a| { - eprintln!("++++++> {:?}", a.text_contents()); - a.text_contents().trim() == full - })) - } - }; - - assert!(status("0.3.0", "2021-01-12").await?); - assert!(status("0.2.0", "2020-12-01").await?); - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_no_trailing_rustdoc_slash(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("tokio") - .version("0.2.21") - .archive_storage(archive_storage) - .rustdoc_file("tokio/time/index.html") - .create() - .await?; - - env.web_app() - .await - .assert_redirect("/tokio/0.2.21/tokio/time", "/tokio/0.2.21/tokio/time/") - .await?; - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_non_ascii(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("const_unit_poc") - .version("1.0.0") - .archive_storage(archive_storage) - .rustdoc_file("const_unit_poc/units/constant.Ω.html") - .create() - .await?; - env.web_app() - .await - .assert_success(&encode_url_path( - "/const_unit_poc/1.0.0/const_unit_poc/units/constant.Ω.html", - )) - .await?; - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_latest_version_keeps_query(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("tungstenite") - .version("0.10.0") - .archive_storage(archive_storage) - .rustdoc_file("tungstenite/index.html") - .create() - .await?; - env.fake_release() - .await - .name("tungstenite") - .version("0.11.0") - .archive_storage(archive_storage) - .rustdoc_file("tungstenite/index.html") - .create() - .await?; - assert_eq!( - latest_version_redirect( - "/tungstenite/0.10.0/tungstenite/?search=String+-%3E+Message", - &env.web_app().await, - env.config() - ) - .await?, - "/crate/tungstenite/latest/target-redirect/tungstenite/?search=String+-%3E+Message", - ); - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn latest_version_works_when_source_deleted(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("pyo3") - .version("0.2.7") - .archive_storage(archive_storage) - .source_file("src/objects/exc.rs", b"//! some docs") - .create() - .await?; - env.fake_release() - .await - .name("pyo3") - .version("0.13.2") - .create() - .await?; - let target_redirect = "/crate/pyo3/latest/target-redirect/src/pyo3/objects/exc.rs.html"; - let web = env.web_app().await; - assert_eq!( - latest_version_redirect( - "/pyo3/0.2.7/src/pyo3/objects/exc.rs.html", - &web, - env.config(), - ) - .await?, - target_redirect - ); - - web.assert_redirect(target_redirect, "/pyo3/latest/pyo3/?search=exc") - .await?; - Ok(()) - }) - } - - fn parse_release_links_from_menu(body: &str) -> Vec { - kuchikiki::parse_html() - .one(body) - .select(r#"ul > li > a"#) - .expect("invalid selector") - .map(|elem| elem.attributes.borrow().get("href").unwrap().to_string()) - .collect() - } - - #[test_case(true)] - #[test_case(false)] - fn test_version_link_goes_to_docs(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("hexponent") - .version("0.3.0") - .archive_storage(archive_storage) - .rustdoc_file("hexponent/index.html") - .add_target("x86_64-unknown-linux-gnu") - .default_target("x86_64-pc-windows-msvc") - .create() - .await?; - env.fake_release() - .await - .name("hexponent") - .version("0.3.1") - .archive_storage(archive_storage) - .rustdoc_file("hexponent/index.html") - .rustdoc_file("hexponent/something.html") - .add_target("x86_64-unknown-linux-gnu") - .default_target("x86_64-pc-windows-msvc") - .create() - .await?; - - // test rustdoc pages stay on the documentation - let releases_response = env - .web_app() - .await - .get("/crate/hexponent/0.3.1/menus/releases/x86_64-unknown-linux-gnu/hexponent/index.html") - .await?; - assert!(releases_response.status().is_success()); - releases_response.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - assert_eq!( - parse_release_links_from_menu(&releases_response.text().await?), - vec![ - "/crate/hexponent/0.3.1/target-redirect/x86_64-unknown-linux-gnu/hexponent/" - .to_owned(), - "/crate/hexponent/0.3.0/target-redirect/x86_64-unknown-linux-gnu/hexponent/" - .to_owned(), - ] - ); - - // test if target-redirect includes path - let releases_response = env - .web_app() - .await - .get("/crate/hexponent/0.3.1/menus/releases/hexponent/something.html") - .await?; - assert!(releases_response.status().is_success()); - releases_response.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - assert_eq!( - parse_release_links_from_menu(&releases_response.text().await?), - vec![ - "/crate/hexponent/0.3.1/target-redirect/hexponent/something.html".to_owned(), - "/crate/hexponent/0.3.0/target-redirect/hexponent/something.html".to_owned(), - ] - ); - - // test /crate pages stay on /crate - let page = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/hexponent/0.3.0") - .await? - .text() - .await?, - ); - let selector = r#"ul > li a[href="/crate/hexponent/0.3.1"]"#.to_string(); - assert_eq!( - page.select(&selector).unwrap().count(), - 1, - "link to /crate not found" - ); - - Ok(()) - }) - } - - #[test] - fn test_repository_link_in_topbar_dropdown() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("testing") - .repo("https://git.example.com") - .version("0.1.0") - .rustdoc_file("testing/index.html") - .create() - .await?; - - let dom = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/testing/0.1.0/testing/") - .await? - .text() - .await?, - ); - - assert_eq!( - dom.select(r#"ul > li a[href="https://git.example.com"]"#) - .unwrap() - .count(), - 1, - ); - - Ok(()) - }) - } - - #[test] - fn test_repository_link_in_topbar_dropdown_github() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("testing") - .version("0.1.0") - .rustdoc_file("testing/index.html") - .github_stats("https://git.example.com", 123, 321, 333) - .create() - .await?; - - let dom = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/testing/0.1.0/testing/") - .await? - .text() - .await?, - ); - - assert_eq!( - dom.select(r#"ul > li a[href="https://git.example.com"]"#) - .unwrap() - .count(), - 1, - ); - - Ok(()) - }) - } - - #[test] - fn test_owner_links_with_team() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("testing") - .version("0.1.0") - .add_owner(CrateOwner { - login: "some-user".into(), - kind: OwnerKind::User, - avatar: "".into(), - }) - .add_owner(CrateOwner { - login: "some-team".into(), - kind: OwnerKind::Team, - avatar: "".into(), - }) - .create() - .await?; - - let dom = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/testing/0.1.0/testing/") - .await? - .text() - .await?, - ); - - let owner_links: Vec<_> = dom - .select(r#"#topbar-owners > li > a"#) - .expect("invalid selector") - .map(|el| { - let attributes = el.attributes.borrow(); - let url = attributes.get("href").expect("href").trim().to_string(); - let name = el.text_contents().trim().to_string(); - (name, url) - }) - .collect(); - - assert_eq!( - owner_links, - vec![ - ( - "some-user".into(), - "https://crates.io/users/some-user".into() - ), - ( - "some-team".into(), - "https://crates.io/teams/some-team".into() - ), - ] - ); - - Ok(()) - }) - } - - #[test] - fn test_dependency_optional_suffix() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("testing") - .version("0.1.0") - .rustdoc_file("testing/index.html") - .add_dependency( - Dependency::new("optional-dep".to_string(), "1.2.3".parse().unwrap()) - .set_optional(true), - ) - .create() - .await?; - - let dom = kuchikiki::parse_html().one(dbg!( - env.web_app() - .await - .get("/testing/0.1.0/testing/") - .await? - .error_for_status()? - .text() - .await? - )); - assert!( - dom.select( - r#"a[href="/optional-dep/^1.2.3/"] > i[class="dependencies normal"] + i"# - ) - .expect("should have optional dependency") - .any(|el| { el.text_contents().contains("optional") }) - ); - let dom = kuchikiki::parse_html().one( - env.web_app() - .await - .get("/crate/testing/0.1.0") - .await? - .text() - .await?, - ); - assert!( - dom.select( - r#"a[href="/crate/optional-dep/^1.2.3"] > i[class="dependencies normal"] + i"# - ) - .expect("should have optional dependency") - .any(|el| { el.text_contents().contains("optional") }) - ); - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_missing_target_redirects_to_search(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("winapi") - .version("0.3.9") - .archive_storage(archive_storage) - .rustdoc_file("winapi/macro.ENUM.html") - .create() - .await?; - - let web = env.web_app().await; - web.assert_redirect( - "/winapi/0.3.9/x86_64-unknown-linux-gnu/winapi/macro.ENUM.html", - "/winapi/0.3.9/winapi/macro.ENUM.html", - ) - .await?; - - web.assert_not_found("/winapi/0.3.9/winapi/struct.not_here.html") - .await?; - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_redirect_source_not_rust(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("winapi") - .version("0.3.8") - .archive_storage(archive_storage) - .source_file("src/docs.md", b"created by Peter Rabbit") - .create() - .await?; - - env.fake_release() - .await - .name("winapi") - .version("0.3.9") - .archive_storage(archive_storage) - .create() - .await?; - - let web = env.web_app().await; - web.assert_success("/winapi/0.3.8/src/winapi/docs.md.html") - .await?; - // people can end up here from clicking "go to latest" while in source view - web.assert_redirect( - "/crate/winapi/0.3.9/target-redirect/src/winapi/docs.md.html", - "/winapi/0.3.9/winapi/", - ) - .await?; - Ok(()) - }) - } - - #[test] - fn noindex_nonlatest() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .rustdoc_file("dummy/index.html") - .create() - .await?; - - let web = env.web_app().await; - - assert!( - web.get("/dummy/0.1.0/dummy/") - .await? - .headers() - .get("x-robots-tag") - .unwrap() - .to_str() - .unwrap() - .contains("noindex") - ); - - assert!( - web.get("/dummy/latest/dummy/") - .await? - .headers() - .get("x-robots-tag") - .is_none() - ); - Ok(()) - }) - } - - #[test] - fn download_unknown_version_404() { - async_wrapper(|env| async move { - let web = env.web_app().await; - web.assert_not_found("/crate/dummy/0.1.0/download").await?; - - Ok(()) - }); - } - - #[test] - fn download_old_storage_version_404() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(false) - .create() - .await?; - - let web = env.web_app().await; - web.assert_not_found("/crate/dummy/0.1.0/download").await?; - - Ok(()) - }); - } - - #[tokio::test(flavor = "multi_thread")] - async fn download_semver() -> Result<()> { - let env = TestEnvironment::with_config(TestEnvironment::base_config().build()?).await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .create() - .await?; - - let web = env.web_app().await; - - web.assert_redirect_cached( - "/crate/dummy/0.1/download", - "/crate/dummy/0.1.0/download", - CachePolicy::ForeverInCdn, - env.config(), - ) - .await?; - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn download_specfic_version() -> Result<()> { - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .create() - .await?; - - let web = env.web_app().await; - let path = "/crate/dummy/0.1.0/download"; - - let resp = web - .assert_success_cached(path, CachePolicy::ForeverInCdn, env.config()) - .await?; - assert_eq!( - resp.headers().get(CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"rustdoc-dummy-0.1.0.zip\"" - ); - web.assert_conditional_get(path, &resp).await?; - - check_archive_consistency(&web.assert_success(path).await?.bytes().await?)?; - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn download_latest_version() -> Result<()> { - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(true) - .create() - .await?; - - let web = env.web_app().await; - let path = "/crate/dummy/latest/download"; - - let resp = web - .assert_success_cached(path, CachePolicy::ForeverInCdn, env.config()) - .await?; - assert_eq!( - resp.headers().get(CONTENT_DISPOSITION).unwrap(), - "attachment; filename=\"rustdoc-dummy-0.2.0.zip\"" - ); - web.assert_conditional_get(path, &resp).await?; - - check_archive_consistency(&web.assert_success(path).await?.bytes().await?)?; - - Ok(()) - } - - #[test_case("something.js")] - #[test_case("something.css")] - fn serve_release_specific_static_assets(name: &str) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .rustdoc_file_with(name, b"content") - .create() - .await?; - - let web = env.web_app().await; - - assert_eq!( - web.assert_success(&format!("/dummy/0.1.0/{name}")) - .await? - .text() - .await?, - "content" - ); - - web.assert_success_and_conditional_get(&format!("/dummy/0.1.0/{name}")) - .await?; - - Ok(()) - }) - } - - #[tokio::test(flavor = "multi_thread")] - #[test_case("folder/file.js")] - #[test_case("root.css")] - async fn test_static_asset_handler(path: &str) -> Result<()> { - let env = TestEnvironment::new().await?; - - let storage = env.async_storage(); - storage - .store_one( - format!("{RUSTDOC_STATIC_STORAGE_PREFIX}{path}"), - b"static content", - ) - .await?; - - let web = env.web_app().await; - - assert_eq!( - web.assert_success(&format!("/-/rustdoc.static/{path}"),) - .await? - .text() - .await?, - "static content" - ); - - web.assert_success_and_conditional_get(&format!("/-/rustdoc.static/{path}")) - .await?; - - Ok(()) - } - - #[test_case("search-1234.js")] - #[test_case("settings-1234.js")] - fn fallback_to_root_storage_for_some_js_assets(path: &str) { - // tests for two separate things needed to serve old rustdoc content - // 1. `/{crate}/{version}/asset.js`, where we try to find the assets in the rustdoc archive - // 2. `/asset.js` where we try to find it in RUSTDOC_STATIC_STORAGE_PREFIX - // - // For 2), new builds use the assets from RUSTDOC_STATIC_STORAGE_PREFIX via - // `/-/rustdoc.static/asset.js`. - // - // For 1) I'm actually not sure, new builds don't seem to have these assets. - // ( the logic is special-cased to `search-` and `settings-` prefixes.) - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .create() - .await?; - - const ROOT_ASSET: &str = "normalize-20200403-1.44.0-nightly-74bd074ee.css"; - - let storage = env.async_storage(); - storage.store_one(ROOT_ASSET, *b"content").await?; - storage.store_one(path, *b"more_content").await?; - - let web = env.web_app().await; - - let response = web.get(&format!("/dummy/0.1.0/{ROOT_ASSET}")).await?; - assert_eq!( - response.status(), - StatusCode::NOT_FOUND, - "{:?}", - response.headers().get("Location"), - ); - - for (path, expected_content) in [ - (format!("/{ROOT_ASSET}"), "content"), - (format!("/dummy/0.1.0/{path}"), "more_content"), - ] { - let resp = web.assert_success(&path).await?; - web.assert_conditional_get(&path, &resp).await?; - assert_eq!(resp.text().await?, expected_content); - } - - Ok(()) - }) - } - - #[test] - fn redirect_with_encoded_chars_in_path() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("clap") - .version("2.24.0") - .add_platform("i686-pc-windows-gnu") - .archive_storage(true) - .create() - .await?; - let web = env.web_app().await; - - web.assert_redirect_cached_unchecked( - "/clap/2.24.0/i686-pc-windows-gnu/clap/which%20is%20a%20part%20of%20%5B%60Display%60%5D", - "/crate/clap/2.24.0/target-redirect/i686-pc-windows-gnu/clap/which%20is%20a%20part%20of%20[%60Display%60]", - CachePolicy::ForeverInCdn, - env.config(), - ).await?; - - Ok(()) - }) - } - - #[test] - fn search_with_encoded_chars_in_path() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("clap") - .version("2.24.0") - .archive_storage(true) - .create() - .await?; - let web = env.web_app().await; - - web.assert_redirect_cached_unchecked( - "/clap/latest/clapproc%20macro%20%60Parser%60%20not%20expanded:%20Cannot%20create%20expander%20for", - "/clap/latest/clap/clapproc%20macro%20%60Parser%60%20not%20expanded:%20Cannot%20create%20expander%20for", - CachePolicy::ForeverInCdn, - env.config(), - ).await?; - - Ok(()) - }) - } - - #[test_case("/something/1.2.3/some_path/", "/crate/something/1.2.3")] - #[test_case("/something/latest/some_path/", "/crate/something/latest")] - fn rustdoc_page_from_failed_build_redirects_to_crate(path: &str, expected: &str) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("something") - .version("1.2.3") - .archive_storage(true) - .build_result_failed() - .create() - .await?; - let web = env.web_app().await; - - web.assert_redirect_cached(path, expected, CachePolicy::ForeverInCdn, env.config()) - .await?; - - Ok(()) - }) - } - - #[test_case(true)] - #[test_case(false)] - fn test_redirect_with_query_args(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake") - .version("0.0.1") - .archive_storage(archive_storage) - .rustdoc_file("fake/index.html") - .binary(true) // binary => rustdoc_status = false - .create() - .await?; - - let web = env.web_app().await; - web.assert_redirect("/fake?a=b", "/crate/fake/latest?a=b") - .await?; - - Ok(()) - }); - } - - #[test_case("/crate/dummy/0.1/json", "/crate/dummy/0.1.0/json")] - #[tokio::test(flavor = "multi_thread")] - async fn json_download_semver_redirect(path: &str, expected_redirect: &str) -> Result<()> { - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .default_target("x86_64-unknown-linux-gnu") - .add_target("i686-pc-windows-msvc") - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(true) - .default_target("x86_64-unknown-linux-gnu") - .add_target("i686-pc-windows-msvc") - .create() - .await?; - - let web = env.web_app().await; - - web.assert_redirect_cached( - path, - expected_redirect, - CachePolicy::ForeverInCdn, - env.config(), - ) - .await?; - Ok(()) - } - - #[test_case( - "latest/json", - CompressionAlgorithm::Zstd, - "x86_64-unknown-linux-gnu", - "latest", - "0.2.0" - )] - #[test_case( - "latest/json.gz", - CompressionAlgorithm::Gzip, - "x86_64-unknown-linux-gnu", - "latest", - "0.2.0" - )] - #[test_case( - "0.1.0/json", - CompressionAlgorithm::Zstd, - "x86_64-unknown-linux-gnu", - "latest", - "0.1.0" - )] - #[test_case( - "latest/json/latest", - CompressionAlgorithm::Zstd, - "x86_64-unknown-linux-gnu", - "latest", - "0.2.0" - )] - #[test_case( - "latest/json/latest.gz", - CompressionAlgorithm::Gzip, - "x86_64-unknown-linux-gnu", - "latest", - "0.2.0" - )] - #[test_case( - "latest/json/42", - CompressionAlgorithm::Zstd, - "x86_64-unknown-linux-gnu", - "42", - "0.2.0" - )] - #[test_case( - "latest/i686-pc-windows-msvc/json", - CompressionAlgorithm::Zstd, - "i686-pc-windows-msvc", - "latest", - "0.2.0" - )] - #[test_case( - "latest/i686-pc-windows-msvc/json.gz", - CompressionAlgorithm::Gzip, - "i686-pc-windows-msvc", - "latest", - "0.2.0" - )] - #[test_case( - "latest/i686-pc-windows-msvc/json/42", - CompressionAlgorithm::Zstd, - "i686-pc-windows-msvc", - "42", - "0.2.0" - )] - #[test_case( - "latest/i686-pc-windows-msvc/json/42.gz", - CompressionAlgorithm::Gzip, - "i686-pc-windows-msvc", - "42", - "0.2.0" - )] - #[test_case( - "latest/i686-pc-windows-msvc/json/42.zst", - CompressionAlgorithm::Zstd, - "i686-pc-windows-msvc", - "42", - "0.2.0" - )] - #[tokio::test(flavor = "multi_thread")] - async fn json_download( - request_path_suffix: &str, - expected_compression: CompressionAlgorithm, - expected_target: &str, - expected_format_version: &str, - expected_version: &str, - ) -> Result<()> { - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .default_target("x86_64-unknown-linux-gnu") - .add_target("i686-pc-windows-msvc") - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(true) - .default_target("x86_64-unknown-linux-gnu") - .add_target("i686-pc-windows-msvc") - .create() - .await?; - - let web = env.web_app().await; - - let path = format!("/crate/dummy/{request_path_suffix}"); - let resp = web - .assert_success_cached(&path, CachePolicy::ForeverInCdn, env.config()) - .await?; - assert_eq!( - resp.headers().get(CONTENT_DISPOSITION).unwrap(), - &format!( - "attachment; filename=\"dummy_{expected_version}_{expected_target}_{expected_format_version}.json.{}\"", - expected_compression.file_extension() - ) - ); - web.assert_conditional_get(&path, &resp).await?; - - { - let compressed_body = web.assert_success(&path).await?.bytes().await?.to_vec(); - let json_body = decompress(&*compressed_body, expected_compression, usize::MAX)?; - assert_eq!( - read_format_version_from_rustdoc_json(&*json_body)?, - // for both "Latest", and "Version(42)", the version number in json is the - // specific number. - "42".parse().unwrap() - ); - } - - Ok(()) - } - - #[test_case("")] - #[test_case(".zst")] - #[tokio::test(flavor = "multi_thread")] - async fn test_json_download_fallback_to_old_files_without_compression_extension( - ext: &str, - ) -> Result<()> { - let env = TestEnvironment::new().await?; - - const NAME: &str = "dummy"; - const VERSION: Version = Version::new(0, 1, 0); - const TARGET: &str = "x86_64-unknown-linux-gnu"; - const FORMAT_VERSION: RustdocJsonFormatVersion = RustdocJsonFormatVersion::Latest; - - env.fake_release() - .await - .name(NAME) - .version(VERSION) - .archive_storage(true) - .default_target(TARGET) - .create() - .await?; - - let storage = env.async_storage(); - - let zstd_blob = storage - .get( - &rustdoc_json_path( - NAME, - &VERSION, - TARGET, - FORMAT_VERSION, - Some(CompressionAlgorithm::Zstd), - ), - usize::MAX, - ) - .await?; - - for compression in RUSTDOC_JSON_COMPRESSION_ALGORITHMS { - let path = - rustdoc_json_path(NAME, &VERSION, TARGET, FORMAT_VERSION, Some(*compression)); - storage.delete_prefix(&path).await?; - assert!(!storage.exists(&path).await?); - } - storage - .store_one( - &rustdoc_json_path(NAME, &VERSION, TARGET, FORMAT_VERSION, None), - zstd_blob.content, - ) - .await?; - - let web = env.web_app().await; - - let path = format!("/crate/dummy/latest/json{ext}"); - let resp = web - .assert_success_cached(&path, CachePolicy::ForeverInCdn, env.config()) - .await?; - assert_eq!( - resp.headers().get(CONTENT_DISPOSITION).unwrap(), - &format!("attachment; filename=\"{NAME}_{VERSION}_{TARGET}_latest.json\""), - ); - web.assert_conditional_get(&path, &resp).await?; - Ok(()) - } - - #[test_case("0.1.0/json"; "rustdoc status false")] - #[test_case("0.2.0/unknown-target/json"; "unknown target")] - #[test_case("0.2.0/json/99"; "target file doesnt exist")] - #[test_case("0.42.0/json"; "unknown version")] - #[tokio::test(flavor = "multi_thread")] - async fn json_download_not_found(request_path_suffix: &str) -> Result<()> { - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("0.1.0") - .archive_storage(true) - .default_target("x86_64-unknown-linux-gnu") - .add_target("i686-pc-windows-msvc") - .binary(true) // binary => rustdoc_status = false - .create() - .await?; - - env.fake_release() - .await - .name("dummy") - .version("0.2.0") - .archive_storage(true) - .default_target("x86_64-unknown-linux-gnu") - .add_target("i686-pc-windows-msvc") - .create() - .await?; - - let web = env.web_app().await; - - let response = web - .get(&format!("/crate/dummy/{request_path_suffix}")) - .await?; - assert!(response.headers().get(CONTENT_DISPOSITION).is_none()); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - #[test_case("/dummy/"; "only krate")] - #[test_case("/dummy/latest/"; "with version")] - #[test_case("/dummy/latest/dummy"; "target-name as path, without trailing slash")] - #[test_case("/dummy/latest/dummy/"; "final target")] - async fn test_full_latest_url_without_trailing_slash(path: &str) -> Result<()> { - // test for https://github.com/rust-lang/docs.rs/issues/2989 - - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .create() - .await?; - - let web = env.web_app().await; - const TARGET: &str = "/dummy/latest/dummy/"; - if path == TARGET { - web.get(path).await?.status().is_success(); - } else { - web.assert_redirect_unchecked(path, "/dummy/latest/dummy/") - .await?; - } - - Ok(()) - } - #[tokio::test(flavor = "multi_thread")] - #[test_case( - "/dummy/latest/other_path", - "/dummy/latest/dummy/other_path"; - "other path, without trailing slash" - )] - #[test_case( - "/dummy/latest/other_path.html", - "/dummy/latest/dummy/other_path.html"; - "other html path, without trailing slash" - )] - async fn test_full_latest_url_some_path_but_trailing_slash( - path: &str, - expected_redirect: &str, - ) -> Result<()> { - // test for https://github.com/rust-lang/docs.rs/issues/2989 - - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("dummy") - .version("1.0.0") - .create() - .await?; - - let web = env.web_app().await; - web.assert_redirect_unchecked(path, expected_redirect) - .await?; - - Ok(()) - } - - #[tokio::test(flavor = "multi_thread")] - async fn test_fetch_item_with_semver_url() -> Result<()> { - // https://github.com/rust-lang/docs.rs/issues/3036 - // This fixes an issue where we mistakenly attached a - // trailing `/` to a rustdoc URL when redirecting - // to the exact version, coming from a semver version. - let env = TestEnvironment::new().await?; - - env.fake_release() - .await - .name("itertools") - .version("0.14.0") - .rustdoc_file("itertools/trait.Itertools.html") - .create() - .await?; - - let web = env.web_app().await; - web.assert_redirect( - "/itertools/^0.14/itertools/trait.Itertools.html", - "/itertools/0.14.0/itertools/trait.Itertools.html", - ) - .await?; - - Ok(()) - } -} +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::{ +// Config, +// db::types::version::Version, +// docbuilder::{RUSTDOC_JSON_COMPRESSION_ALGORITHMS, read_format_version_from_rustdoc_json}, +// registry_api::{CrateOwner, OwnerKind}, +// storage::decompress, +// test::*, +// utils::Dependency, +// web::{cache::CachePolicy, encode_url_path}, +// }; +// use anyhow::{Context, Result}; +// use chrono::{NaiveDate, Utc}; +// use kuchikiki::traits::TendrilSink; +// use pretty_assertions::assert_eq; +// use reqwest::StatusCode; +// use std::{collections::BTreeMap, io}; +// use test_case::test_case; +// use tracing::info; + +// /// try decompressing the zip & read the content +// fn check_archive_consistency(compressed_body: &[u8]) -> anyhow::Result<()> { +// let mut zip = zip::ZipArchive::new(io::Cursor::new(compressed_body))?; +// for i in 0..zip.len() { +// let mut file = zip.by_index(i)?; + +// let mut buf = Vec::new(); +// io::copy(&mut file, &mut buf)?; +// } + +// Ok(()) +// } + +// async fn try_latest_version_redirect( +// path: &str, +// web: &axum::Router, +// config: &Config, +// ) -> Result, anyhow::Error> { +// web.assert_success(path).await?; +// let response = web.get(path).await?; +// response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, config); +// let data = response.text().await?; +// info!( +// "fetched path {} and got content {}\nhelp: if this is missing the header, remember to add ", +// path, data +// ); +// let dom = kuchikiki::parse_html().one(data); + +// if let Some(elem) = dom +// .select("form > ul > li > a.warn") +// .expect("invalid selector") +// .next() +// { +// let link = elem.attributes.borrow().get("href").unwrap().to_string(); +// let response = web.get(&link).await?; +// response.assert_cache_control(CachePolicy::ForeverInCdn, config); +// assert!(response.status().is_success() || response.status().is_redirection()); +// Ok(Some(link)) +// } else { +// Ok(None) +// } +// } + +// async fn latest_version_redirect( +// path: &str, +// web: &axum::Router, +// config: &Config, +// ) -> Result { +// try_latest_version_redirect(path, web, config) +// .await? +// .with_context(|| anyhow::anyhow!("no redirect found for {}", path)) +// } + +// #[test_case(true)] +// #[test_case(false)] +// // https://github.com/rust-lang/docs.rs/issues/2313 +// fn help_html(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("krate") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("help.html") +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_success_cached( +// "/krate/0.1.0/help.html", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; + +// web.assert_success_and_conditional_get("/krate/0.1.0/help.html") +// .await?; +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// // regression test for https://github.com/rust-lang/docs.rs/issues/552 +// fn settings_html(archive_storage: bool) { +// async_wrapper(|env| async move { +// // first release works, second fails +// env.fake_release() +// .await +// .name("buggy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("settings.html") +// .rustdoc_file("scrape-examples-help.html") +// .rustdoc_file("directory_1/index.html") +// .rustdoc_file("directory_2.html/index.html") +// .rustdoc_file("all.html") +// .rustdoc_file("directory_3/.gitignore") +// .rustdoc_file("directory_4/empty_file_no_ext") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("buggy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .build_result_failed() +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_success_cached("/", CachePolicy::ShortInCdnAndBrowser, env.config()) +// .await?; +// web.assert_success_cached( +// "/crate/buggy/0.1.0", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/directory_1/index.html", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/directory_2.html/index.html", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/directory_3/.gitignore", +// CachePolicy::ForeverInCdnAndBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/settings.html", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/scrape-examples-help.html", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/all.html", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_success_cached( +// "/buggy/0.1.0/directory_4/empty_file_no_ext", +// CachePolicy::ForeverInCdnAndBrowser, +// env.config(), +// ) +// .await?; +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn default_target_redirects_to_base(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; + +// let web = env.web_app().await; +// // no explicit default-target +// let base = "/dummy/0.1.0/dummy/"; +// web.assert_success_cached( +// base, +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// web.assert_redirect_cached( +// "/dummy/0.1.0/x86_64-unknown-linux-gnu/dummy/", +// base, +// CachePolicy::ForeverInCdn, +// env.config(), +// ) +// .await?; + +// web.assert_success_and_conditional_get("/dummy/latest/dummy/") +// .await?; + +// // set an explicit target that requires cross-compile +// let target = "x86_64-pc-windows-msvc"; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .default_target(target) +// .create() +// .await?; +// let base = "/dummy/0.2.0/dummy/"; +// web.assert_success_and_conditional_get(base).await?; +// web.assert_redirect("/dummy/0.2.0/x86_64-pc-windows-msvc/dummy/", base) +// .await?; + +// // set an explicit target without cross-compile +// // also check that /:crate/:version/:platform/all.html doesn't panic +// let target = "x86_64-unknown-linux-gnu"; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.3.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("all.html") +// .default_target(target) +// .create() +// .await?; +// let base = "/dummy/0.3.0/dummy/"; +// web.assert_success(base).await?; +// web.assert_redirect("/dummy/0.3.0/x86_64-unknown-linux-gnu/dummy/", base) +// .await?; +// web.assert_redirect( +// "/dummy/0.3.0/x86_64-unknown-linux-gnu/all.html", +// "/dummy/0.3.0/all.html", +// ) +// .await?; +// web.assert_redirect("/dummy/0.3.0/", base).await?; +// web.assert_redirect("/dummy/0.3.0/index.html", base).await?; +// Ok(()) +// }); +// } + +// #[test] +// fn latest_url() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; + +// let resp = env +// .web_app() +// .await +// .get("/dummy/latest/dummy/") +// .await? +// .error_for_status()?; + +// resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// let body = resp.text().await?; +// assert!( +// body.contains(" Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .cache_control_stale_while_revalidate(Some(2592000)) +// .build()?, +// ) +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; + +// let web = env.web_app().await; + +// { +// let resp = web.get("/dummy/latest/dummy/").await?; +// resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// web.assert_conditional_get("/dummy/latest/dummy/", &resp) +// .await?; +// } + +// { +// let resp = web.get("/dummy/0.1.0/dummy/").await?; +// resp.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); +// web.assert_conditional_get("/dummy/0.1.0/dummy/", &resp) +// .await?; +// } +// Ok(()) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn go_to_latest_version(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/blah/index.html") +// .rustdoc_file("dummy/blah/blah.html") +// .rustdoc_file("dummy/struct.will-be-deleted.html") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/blah/index.html") +// .rustdoc_file("dummy/blah/blah.html") +// .create() +// .await?; + +// let web = env.web_app().await; + +// // check it works at all +// let redirect = +// latest_version_redirect("/dummy/0.1.0/dummy/", &web, env.config()).await?; +// assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/"); + +// let redirect = +// latest_version_redirect("/dummy/0.1.0/dummy/blah/", &web, env.config()).await?; +// assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/blah/"); + +// // check it keeps the subpage +// let redirect = +// latest_version_redirect("/dummy/0.1.0/dummy/blah/blah.html", &web, env.config()) +// .await?; +// assert_eq!( +// redirect, +// "/crate/dummy/latest/target-redirect/dummy/blah/blah.html" +// ); + +// // check it also works for deleted pages +// let redirect = latest_version_redirect( +// "/dummy/0.1.0/dummy/struct.will-be-deleted.html", +// &web, +// env.config(), +// ) +// .await?; +// assert_eq!( +// redirect, +// "/crate/dummy/latest/target-redirect/dummy/struct.will-be-deleted.html" +// ); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn go_to_latest_version_keeps_platform(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .add_platform("x86_64-pc-windows-msvc") +// .rustdoc_file("dummy/struct.Blah.html") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .add_platform("x86_64-pc-windows-msvc") +// .create() +// .await?; + +// let web = env.web_app().await; + +// let redirect = latest_version_redirect( +// "/dummy/0.1.0/x86_64-pc-windows-msvc/dummy/index.html", +// &web, +// env.config(), +// ) +// .await?; +// assert_eq!( +// redirect, +// "/crate/dummy/latest/target-redirect/x86_64-pc-windows-msvc/dummy/" +// ); + +// let redirect = latest_version_redirect( +// "/dummy/0.1.0/x86_64-pc-windows-msvc/dummy/", +// &web, +// env.config(), +// ) +// .await?; +// assert_eq!( +// redirect, +// "/crate/dummy/latest/target-redirect/x86_64-pc-windows-msvc/dummy/" +// ); + +// let redirect = latest_version_redirect( +// "/dummy/0.1.0/x86_64-pc-windows-msvc/dummy/struct.Blah.html", +// &web, +// env.config(), +// ) +// .await?; +// assert_eq!( +// redirect, +// "/crate/dummy/latest/target-redirect/x86_64-pc-windows-msvc/dummy/struct.Blah.html" +// ); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn redirect_latest_goes_to_crate_if_build_failed(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .build_result_failed() +// .create() +// .await?; + +// let web = env.web_app().await; +// let redirect = +// latest_version_redirect("/dummy/0.1.0/dummy/", &web, env.config()).await?; +// assert_eq!(redirect, "/crate/dummy/latest"); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn redirect_latest_does_not_go_to_yanked_versions(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.1") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .yanked(true) +// .create() +// .await?; + +// let web = env.web_app().await; +// let redirect = +// latest_version_redirect("/dummy/0.1.0/dummy/", &web, env.config()).await?; +// assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/"); + +// let redirect = +// latest_version_redirect("/dummy/0.2.1/dummy/", &web, env.config()).await?; +// assert_eq!(redirect, "/crate/dummy/latest/target-redirect/dummy/"); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn yanked_release_shows_warning_in_nav(archive_storage: bool) { +// async fn has_yanked_warning(path: &str, web: &axum::Router) -> Result { +// web.assert_success(path).await?; +// let data = web.get(path).await?.text().await?; +// Ok(kuchikiki::parse_html() +// .one(data) +// .select("form > ul > li > .warn") +// .expect("invalid selector") +// .any(|el| el.text_contents().contains("yanked"))) +// } + +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .yanked(true) +// .create() +// .await?; + +// assert!(has_yanked_warning("/dummy/0.1.0/dummy/", &web).await?); + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .yanked(true) +// .create() +// .await?; + +// assert!(has_yanked_warning("/dummy/0.1.0/dummy/", &web).await?); + +// Ok(()) +// }) +// } + +// #[test] +// fn badges_are_urlencoded() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("zstd") +// .version("0.5.1+zstd.1.4.4") +// .create() +// .await?; + +// let frontend = env.web_app().await; +// let response = frontend +// .assert_redirect_cached_unchecked( +// "/zstd/badge.svg", +// "https://img.shields.io/docsrs/zstd/latest", +// CachePolicy::ForeverInCdnAndBrowser, +// env.config(), +// ) +// .await?; +// assert_eq!(response.status(), StatusCode::MOVED_PERMANENTLY); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn crate_name_percent_decoded_redirect(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake-crate") +// .version("0.0.1") +// .archive_storage(archive_storage) +// .rustdoc_file("fake_crate/index.html") +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect("/fake%2Dcrate", "/fake-crate/latest/fake_crate/") +// .await?; + +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn base_redirect_handles_mismatched_separators(archive_storage: bool) { +// async_wrapper(|env| async move { +// let rels = [ +// ("dummy-dash", "0.1.0"), +// ("dummy-dash", "0.2.0"), +// ("dummy_underscore", "0.1.0"), +// ("dummy_underscore", "0.2.0"), +// ("dummy_mixed-separators", "0.1.0"), +// ("dummy_mixed-separators", "0.2.0"), +// ]; + +// for (name, version) in rels { +// env.fake_release() +// .await +// .name(name) +// .version(version) +// .archive_storage(archive_storage) +// .rustdoc_file(&(name.replace('-', "_") + "/index.html")) +// .create() +// .await?; +// } + +// let web = env.web_app().await; + +// web.assert_redirect("/dummy_dash", "/dummy-dash/latest/dummy_dash/") +// .await?; +// web.assert_redirect("/dummy_dash/*", "/dummy-dash/latest/dummy_dash/") +// .await?; +// web.assert_redirect("/dummy_dash/0.1.0", "/dummy-dash/0.1.0/dummy_dash/") +// .await?; +// web.assert_redirect( +// "/dummy-underscore", +// "/dummy_underscore/latest/dummy_underscore/", +// ) +// .await?; +// web.assert_redirect( +// "/dummy-underscore/*", +// "/dummy_underscore/latest/dummy_underscore/", +// ) +// .await?; +// web.assert_redirect( +// "/dummy-underscore/0.1.0", +// "/dummy_underscore/0.1.0/dummy_underscore/", +// ) +// .await?; +// web.assert_redirect( +// "/dummy-mixed_separators", +// "/dummy_mixed-separators/latest/dummy_mixed_separators/", +// ) +// .await?; +// web.assert_redirect( +// "/dummy_mixed_separators/*", +// "/dummy_mixed-separators/latest/dummy_mixed_separators/", +// ) +// .await?; +// web.assert_redirect( +// "/dummy-mixed-separators/0.1.0", +// "/dummy_mixed-separators/0.1.0/dummy_mixed_separators/", +// ) +// .await?; + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn specific_pages_do_not_handle_mismatched_separators(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy-dash") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy_dash/index.html") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy_mixed-separators") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy_mixed_separators/index.html") +// .create() +// .await?; + +// let web = env.web_app().await; + +// web.assert_success("/dummy-dash/0.1.0/dummy_dash/index.html") +// .await?; +// web.assert_redirect_unchecked( +// "/crate/dummy_mixed-separators", +// "/crate/dummy_mixed-separators/latest", +// ) +// .await?; + +// web.assert_redirect( +// "/dummy_dash/0.1.0/dummy_dash/index.html", +// "/dummy-dash/0.1.0/dummy_dash/", +// ) +// .await?; + +// assert_eq!( +// web.get("/crate/dummy_mixed_separators/latest") +// .await? +// .status(), +// StatusCode::NOT_FOUND +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn nonexistent_crate_404s() { +// async_wrapper(|env| async move { +// assert_eq!( +// env.web_app().await.get("/dummy").await?.status(), +// StatusCode::NOT_FOUND +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn no_target_target_redirect_404s() { +// async_wrapper(|env| async move { +// assert_eq!( +// env.web_app() +// .await +// .get("/crate/dummy/0.1.0/target-redirect") +// .await? +// .status(), +// StatusCode::NOT_FOUND +// ); + +// assert_eq!( +// env.web_app() +// .await +// .get("/crate/dummy/0.1.0/target-redirect/") +// .await? +// .status(), +// StatusCode::NOT_FOUND +// ); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn platform_links_go_to_current_path(archive_storage: bool) { +// async fn get_platform_links( +// path: &str, +// web: &axum::Router, +// ) -> Result, anyhow::Error> { +// web.assert_success(path).await?; +// let data = web.get(path).await?.text().await?; +// let dom = kuchikiki::parse_html().one(data); +// Ok(dom +// .select(r#"a[aria-label="Platform"] + ul li a"#) +// .expect("invalid selector") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// let url = attributes.get("href").expect("href").to_string(); +// let rel = attributes.get("rel").unwrap_or("").to_string(); +// let name = el.text_contents(); +// (name, url, rel) +// }) +// .collect()) +// } +// async fn assert_platform_links( +// web: &axum::Router, +// path: &str, +// links: &[(&str, &str)], +// ) -> Result<(), anyhow::Error> { +// let mut links: BTreeMap<_, _> = links.iter().copied().collect(); + +// for (platform, link, rel) in dbg!(get_platform_links(path, web).await?) { +// assert_eq!(rel, "nofollow"); +// web.assert_redirect(&link, links.remove(platform.as_str()).unwrap()) +// .await?; +// } + +// assert!(links.is_empty()); + +// Ok(()) +// } + +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// // no explicit default-target +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("dummy/struct.Dummy.html") +// .add_target("x86_64-unknown-linux-gnu") +// .create() +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.1.0/dummy/", +// &[("x86_64-unknown-linux-gnu", "/dummy/0.1.0/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.1.0/dummy/", +// &[("x86_64-unknown-linux-gnu", "/dummy/0.1.0/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.1.0/dummy/struct.Dummy.html", +// &[( +// "x86_64-unknown-linux-gnu", +// "/dummy/0.1.0/dummy/struct.Dummy.html", +// )], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/", +// &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/index.html", +// &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/struct.Dummy.html", +// &[( +// "x86_64-unknown-linux-gnu", +// "/dummy/latest/dummy/struct.Dummy.html", +// )], +// ) +// .await?; + +// // set an explicit target that requires cross-compile +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("dummy/struct.Dummy.html") +// .default_target("x86_64-pc-windows-msvc") +// .create() +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.2.0/dummy/", +// &[("x86_64-pc-windows-msvc", "/dummy/0.2.0/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.2.0/dummy/index.html", +// &[("x86_64-pc-windows-msvc", "/dummy/0.2.0/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.2.0/dummy/struct.Dummy.html", +// &[( +// "x86_64-pc-windows-msvc", +// "/dummy/0.2.0/dummy/struct.Dummy.html", +// )], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/", +// &[("x86_64-pc-windows-msvc", "/dummy/latest/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/index.html", +// &[("x86_64-pc-windows-msvc", "/dummy/latest/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/struct.Dummy.html", +// &[( +// "x86_64-pc-windows-msvc", +// "/dummy/latest/dummy/struct.Dummy.html", +// )], +// ) +// .await?; + +// // set an explicit target without cross-compile +// env.fake_release() +// .await +// .name("dummy") +// .version("0.3.0") +// .archive_storage(archive_storage) +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("dummy/struct.Dummy.html") +// .default_target("x86_64-unknown-linux-gnu") +// .create() +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.3.0/dummy/", +// &[("x86_64-unknown-linux-gnu", "/dummy/0.3.0/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.3.0/dummy/index.html", +// &[("x86_64-unknown-linux-gnu", "/dummy/0.3.0/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.3.0/dummy/struct.Dummy.html", +// &[( +// "x86_64-unknown-linux-gnu", +// "/dummy/0.3.0/dummy/struct.Dummy.html", +// )], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/", +// &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/index.html", +// &[("x86_64-unknown-linux-gnu", "/dummy/latest/dummy/")], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/dummy/struct.Dummy.html", +// &[( +// "x86_64-unknown-linux-gnu", +// "/dummy/latest/dummy/struct.Dummy.html", +// )], +// ) +// .await?; + +// // multiple targets +// env.fake_release() +// .await +// .name("dummy") +// .version("0.4.0") +// .archive_storage(archive_storage) +// .rustdoc_file("settings.html") +// .rustdoc_file("dummy/index.html") +// .rustdoc_file("dummy/struct.Dummy.html") +// .rustdoc_file("dummy/struct.DefaultOnly.html") +// .rustdoc_file("x86_64-pc-windows-msvc/settings.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/index.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/struct.Dummy.html") +// .rustdoc_file("x86_64-pc-windows-msvc/dummy/struct.WindowsOnly.html") +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("x86_64-pc-windows-msvc") +// .create() +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/settings.html", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/settings.html", +// ), +// ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/settings.html"), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/latest/settings.html", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/latest/x86_64-pc-windows-msvc/settings.html", +// ), +// ("x86_64-unknown-linux-gnu", "/dummy/latest/settings.html"), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/dummy/", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", +// ), +// ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/dummy/"), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", +// ), +// ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/dummy/"), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/dummy/", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/", +// ), +// ("x86_64-unknown-linux-gnu", "/dummy/0.4.0/dummy/"), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/dummy/struct.DefaultOnly.html", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/?search=DefaultOnly", +// ), +// ( +// "x86_64-unknown-linux-gnu", +// "/dummy/0.4.0/dummy/struct.DefaultOnly.html", +// ), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/dummy/struct.Dummy.html", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.Dummy.html", +// ), +// ( +// "x86_64-unknown-linux-gnu", +// "/dummy/0.4.0/dummy/struct.Dummy.html", +// ), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.Dummy.html", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.Dummy.html", +// ), +// ( +// "x86_64-unknown-linux-gnu", +// "/dummy/0.4.0/dummy/struct.Dummy.html", +// ), +// ], +// ) +// .await?; + +// assert_platform_links( +// &web, +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.WindowsOnly.html", +// &[ +// ( +// "x86_64-pc-windows-msvc", +// "/dummy/0.4.0/x86_64-pc-windows-msvc/dummy/struct.WindowsOnly.html", +// ), +// ( +// "x86_64-unknown-linux-gnu", +// "/dummy/0.4.0/dummy/?search=WindowsOnly", +// ), +// ], +// ) +// .await?; + +// Ok(()) +// }); +// } + +// #[test] +// fn test_target_redirect_with_corrected_name() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo_ab") +// .version("0.0.1") +// .archive_storage(true) +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect_unchecked( +// "/crate/foo-ab/0.0.1/target-redirect/x86_64-unknown-linux-gnu", +// "/foo-ab/0.0.1/foo_ab/", +// ) +// .await?; +// // `-` becomes `_` but we keep the query arguments. +// web.assert_redirect_unchecked( +// "/foo-ab/0.0.1/foo_ab/?search=a", +// "/foo_ab/0.0.1/foo_ab/?search=a", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn test_target_redirect_not_found() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// assert_eq!( +// web.get("/crate/fdsafdsafdsafdsa/0.1.0/target-redirect/aarch64-apple-darwin/") +// .await? +// .status(), +// StatusCode::NOT_FOUND, +// ); +// Ok(()) +// }) +// } + +// #[test] +// fn test_redirect_to_latest_302() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_redirect_cached( +// "/dummy", +// "/dummy/latest/dummy/", +// CachePolicy::ForeverInCdn, +// env.config(), +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_fully_yanked_crate_404s(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .archive_storage(archive_storage) +// .yanked(true) +// .create() +// .await?; + +// assert_eq!( +// env.web_app() +// .await +// .get("/crate/dummy/latest") +// .await? +// .status(), +// StatusCode::NOT_FOUND +// ); + +// assert_eq!( +// env.web_app().await.get("/dummy/").await?.status(), +// StatusCode::NOT_FOUND +// ); + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_no_trailing_target_slash(archive_storage: bool) { +// // regression test for https://github.com/rust-lang/docs.rs/issues/856 +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(archive_storage) +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_redirect( +// "/crate/dummy/0.1.0/target-redirect/aarch64-apple-darwin", +// "/dummy/0.1.0/dummy/", +// ) +// .await?; +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(archive_storage) +// .add_platform("aarch64-apple-darwin") +// .create() +// .await?; +// web.assert_redirect( +// "/crate/dummy/0.2.0/target-redirect/aarch64-apple-darwin", +// "/dummy/0.2.0/aarch64-apple-darwin/dummy/", +// ) +// .await?; +// web.assert_redirect( +// "/crate/dummy/0.2.0/target-redirect/platform-that-does-not-exist", +// "/dummy/0.2.0/dummy/", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn test_redirect_crate_coloncolon_path() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// env.fake_release() +// .await +// .name("some_random_crate") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("some_other_crate") +// .create() +// .await?; + +// web.assert_redirect( +// "/some_random_crate::somepath", +// "/some_random_crate/latest/some_random_crate/?search=somepath", +// ) +// .await?; +// web.assert_redirect( +// "/some_random_crate::some::path", +// "/some_random_crate/latest/some_random_crate/?search=some%3A%3Apath", +// ) +// .await?; +// web.assert_redirect( +// "/some_random_crate::some::path?go_to_first=true", +// "/some_random_crate/latest/some_random_crate/?go_to_first=true&search=some%3A%3Apath", +// ).await?; + +// web.assert_redirect_unchecked( +// "/std::some::path", +// "https://doc.rust-lang.org/stable/std/?search=some%3A%3Apath", +// ) +// .await?; + +// Ok(()) +// }) +// } + +// #[test] +// // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655147643 +// fn test_no_panic_on_missing_kind() { +// async_wrapper(|env| async move { +// let id = env +// .fake_release() +// .await +// .name("strum") +// .version("0.13.0") +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// // https://stackoverflow.com/questions/18209625/how-do-i-modify-fields-inside-the-new-postgresql-json-datatype +// sqlx::query!( +// r#"UPDATE releases SET dependencies = dependencies::jsonb #- '{0,2}' WHERE id = $1"#, id.0 +// ).execute(&mut *conn).await?; + +// let web = env.web_app().await; +// web.assert_success("/strum/0.13.0/strum/").await?; +// web.assert_success("/crate/strum/0.13.0").await?; +// Ok(()) +// }) +// } + +// #[test] +// // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655154405 +// fn test_readme_rendered_as_html() { +// async_wrapper(|env| async move { +// let readme = "# Overview"; +// env.fake_release() +// .await +// .name("strum") +// .version("0.18.0") +// .readme(readme) +// .create() +// .await?; +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/strum/0.18.0") +// .await? +// .text() +// .await?, +// ); +// let rendered = page.select_first("#main").expect("missing readme"); +// println!("{}", rendered.text_contents()); +// rendered +// .as_node() +// .select_first("h1") +// .expect("`# Overview` was not rendered as HTML"); +// Ok(()) +// }) +// } + +// #[test] +// // regression test for https://github.com/rust-lang/docs.rs/pull/885#issuecomment-655149288 +// fn test_build_status_is_accurate() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.3.0") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.2.0") +// .build_result_failed() +// .create() +// .await?; +// let web = env.web_app().await; + +// let status = |version| { +// let web = web.clone(); +// async move { +// let page = kuchikiki::parse_html() +// .one(web.get("/crate/hexponent/0.3.0").await?.text().await?); +// let selector = format!(r#"ul > li a[href="/crate/hexponent/{version}"]"#); +// let anchor = page +// .select(&selector) +// .unwrap() +// .find(|a| a.text_contents().trim().split(" ").next().unwrap() == version) +// .unwrap(); +// let attributes = anchor.as_node().as_element().unwrap().attributes.borrow(); +// let classes = attributes.get("class").unwrap(); +// Ok::<_, anyhow::Error>(classes.split(' ').all(|c| c != "warn")) +// } +// }; + +// assert!(status("0.3.0").await?); +// assert!(!status("0.2.0").await?); +// Ok(()) +// }) +// } + +// #[test] +// fn test_crate_release_version_and_date() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.3.0") +// .release_time( +// NaiveDate::from_ymd_opt(2021, 1, 12) +// .unwrap() +// .and_hms_milli_opt(0, 0, 0, 0) +// .unwrap() +// .and_local_timezone(Utc) +// .unwrap(), +// ) +// .create() +// .await?; +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.2.0") +// .release_time( +// NaiveDate::from_ymd_opt(2020, 12, 1) +// .unwrap() +// .and_hms_milli_opt(0, 0, 0, 0) +// .unwrap() +// .and_local_timezone(Utc) +// .unwrap(), +// ) +// .create() +// .await?; +// let web = env.web_app().await; + +// let status = |version, date| { +// let web = web.clone(); +// async move { +// let page = kuchikiki::parse_html() +// .one(web.get("/crate/hexponent/0.3.0").await?.text().await?); +// let selector = format!(r#"ul > li a[href="/crate/hexponent/{version}"]"#); +// let full = format!("{version} ({date})"); +// Result::::Ok(page.select(&selector).unwrap().any(|a| { +// eprintln!("++++++> {:?}", a.text_contents()); +// a.text_contents().trim() == full +// })) +// } +// }; + +// assert!(status("0.3.0", "2021-01-12").await?); +// assert!(status("0.2.0", "2020-12-01").await?); +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_no_trailing_rustdoc_slash(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("tokio") +// .version("0.2.21") +// .archive_storage(archive_storage) +// .rustdoc_file("tokio/time/index.html") +// .create() +// .await?; + +// env.web_app() +// .await +// .assert_redirect("/tokio/0.2.21/tokio/time", "/tokio/0.2.21/tokio/time/") +// .await?; + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_non_ascii(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("const_unit_poc") +// .version("1.0.0") +// .archive_storage(archive_storage) +// .rustdoc_file("const_unit_poc/units/constant.Ω.html") +// .create() +// .await?; +// env.web_app() +// .await +// .assert_success(&encode_url_path( +// "/const_unit_poc/1.0.0/const_unit_poc/units/constant.Ω.html", +// )) +// .await?; +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_latest_version_keeps_query(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("tungstenite") +// .version("0.10.0") +// .archive_storage(archive_storage) +// .rustdoc_file("tungstenite/index.html") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("tungstenite") +// .version("0.11.0") +// .archive_storage(archive_storage) +// .rustdoc_file("tungstenite/index.html") +// .create() +// .await?; +// assert_eq!( +// latest_version_redirect( +// "/tungstenite/0.10.0/tungstenite/?search=String+-%3E+Message", +// &env.web_app().await, +// env.config() +// ) +// .await?, +// "/crate/tungstenite/latest/target-redirect/tungstenite/?search=String+-%3E+Message", +// ); +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn latest_version_works_when_source_deleted(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("pyo3") +// .version("0.2.7") +// .archive_storage(archive_storage) +// .source_file("src/objects/exc.rs", b"//! some docs") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("pyo3") +// .version("0.13.2") +// .create() +// .await?; +// let target_redirect = "/crate/pyo3/latest/target-redirect/src/pyo3/objects/exc.rs.html"; +// let web = env.web_app().await; +// assert_eq!( +// latest_version_redirect( +// "/pyo3/0.2.7/src/pyo3/objects/exc.rs.html", +// &web, +// env.config(), +// ) +// .await?, +// target_redirect +// ); + +// web.assert_redirect(target_redirect, "/pyo3/latest/pyo3/?search=exc") +// .await?; +// Ok(()) +// }) +// } + +// fn parse_release_links_from_menu(body: &str) -> Vec { +// kuchikiki::parse_html() +// .one(body) +// .select(r#"ul > li > a"#) +// .expect("invalid selector") +// .map(|elem| elem.attributes.borrow().get("href").unwrap().to_string()) +// .collect() +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_version_link_goes_to_docs(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.3.0") +// .archive_storage(archive_storage) +// .rustdoc_file("hexponent/index.html") +// .add_target("x86_64-unknown-linux-gnu") +// .default_target("x86_64-pc-windows-msvc") +// .create() +// .await?; +// env.fake_release() +// .await +// .name("hexponent") +// .version("0.3.1") +// .archive_storage(archive_storage) +// .rustdoc_file("hexponent/index.html") +// .rustdoc_file("hexponent/something.html") +// .add_target("x86_64-unknown-linux-gnu") +// .default_target("x86_64-pc-windows-msvc") +// .create() +// .await?; + +// // test rustdoc pages stay on the documentation +// let releases_response = env +// .web_app() +// .await +// .get("/crate/hexponent/0.3.1/menus/releases/x86_64-unknown-linux-gnu/hexponent/index.html") +// .await?; +// assert!(releases_response.status().is_success()); +// releases_response.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// assert_eq!( +// parse_release_links_from_menu(&releases_response.text().await?), +// vec![ +// "/crate/hexponent/0.3.1/target-redirect/x86_64-unknown-linux-gnu/hexponent/" +// .to_owned(), +// "/crate/hexponent/0.3.0/target-redirect/x86_64-unknown-linux-gnu/hexponent/" +// .to_owned(), +// ] +// ); + +// // test if target-redirect includes path +// let releases_response = env +// .web_app() +// .await +// .get("/crate/hexponent/0.3.1/menus/releases/hexponent/something.html") +// .await?; +// assert!(releases_response.status().is_success()); +// releases_response.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// assert_eq!( +// parse_release_links_from_menu(&releases_response.text().await?), +// vec![ +// "/crate/hexponent/0.3.1/target-redirect/hexponent/something.html".to_owned(), +// "/crate/hexponent/0.3.0/target-redirect/hexponent/something.html".to_owned(), +// ] +// ); + +// // test /crate pages stay on /crate +// let page = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/hexponent/0.3.0") +// .await? +// .text() +// .await?, +// ); +// let selector = r#"ul > li a[href="/crate/hexponent/0.3.1"]"#.to_string(); +// assert_eq!( +// page.select(&selector).unwrap().count(), +// 1, +// "link to /crate not found" +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_repository_link_in_topbar_dropdown() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("testing") +// .repo("https://git.example.com") +// .version("0.1.0") +// .rustdoc_file("testing/index.html") +// .create() +// .await?; + +// let dom = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/testing/0.1.0/testing/") +// .await? +// .text() +// .await?, +// ); + +// assert_eq!( +// dom.select(r#"ul > li a[href="https://git.example.com"]"#) +// .unwrap() +// .count(), +// 1, +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_repository_link_in_topbar_dropdown_github() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("testing") +// .version("0.1.0") +// .rustdoc_file("testing/index.html") +// .github_stats("https://git.example.com", 123, 321, 333) +// .create() +// .await?; + +// let dom = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/testing/0.1.0/testing/") +// .await? +// .text() +// .await?, +// ); + +// assert_eq!( +// dom.select(r#"ul > li a[href="https://git.example.com"]"#) +// .unwrap() +// .count(), +// 1, +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_owner_links_with_team() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("testing") +// .version("0.1.0") +// .add_owner(CrateOwner { +// login: "some-user".into(), +// kind: OwnerKind::User, +// avatar: "".into(), +// }) +// .add_owner(CrateOwner { +// login: "some-team".into(), +// kind: OwnerKind::Team, +// avatar: "".into(), +// }) +// .create() +// .await?; + +// let dom = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/testing/0.1.0/testing/") +// .await? +// .text() +// .await?, +// ); + +// let owner_links: Vec<_> = dom +// .select(r#"#topbar-owners > li > a"#) +// .expect("invalid selector") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// let url = attributes.get("href").expect("href").trim().to_string(); +// let name = el.text_contents().trim().to_string(); +// (name, url) +// }) +// .collect(); + +// assert_eq!( +// owner_links, +// vec![ +// ( +// "some-user".into(), +// "https://crates.io/users/some-user".into() +// ), +// ( +// "some-team".into(), +// "https://crates.io/teams/some-team".into() +// ), +// ] +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_dependency_optional_suffix() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("testing") +// .version("0.1.0") +// .rustdoc_file("testing/index.html") +// .add_dependency( +// Dependency::new("optional-dep".to_string(), "1.2.3".parse().unwrap()) +// .set_optional(true), +// ) +// .create() +// .await?; + +// let dom = kuchikiki::parse_html().one(dbg!( +// env.web_app() +// .await +// .get("/testing/0.1.0/testing/") +// .await? +// .error_for_status()? +// .text() +// .await? +// )); +// assert!( +// dom.select( +// r#"a[href="/optional-dep/^1.2.3/"] > i[class="dependencies normal"] + i"# +// ) +// .expect("should have optional dependency") +// .any(|el| { el.text_contents().contains("optional") }) +// ); +// let dom = kuchikiki::parse_html().one( +// env.web_app() +// .await +// .get("/crate/testing/0.1.0") +// .await? +// .text() +// .await?, +// ); +// assert!( +// dom.select( +// r#"a[href="/crate/optional-dep/^1.2.3"] > i[class="dependencies normal"] + i"# +// ) +// .expect("should have optional dependency") +// .any(|el| { el.text_contents().contains("optional") }) +// ); +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_missing_target_redirects_to_search(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("winapi") +// .version("0.3.9") +// .archive_storage(archive_storage) +// .rustdoc_file("winapi/macro.ENUM.html") +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect( +// "/winapi/0.3.9/x86_64-unknown-linux-gnu/winapi/macro.ENUM.html", +// "/winapi/0.3.9/winapi/macro.ENUM.html", +// ) +// .await?; + +// web.assert_not_found("/winapi/0.3.9/winapi/struct.not_here.html") +// .await?; + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_redirect_source_not_rust(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("winapi") +// .version("0.3.8") +// .archive_storage(archive_storage) +// .source_file("src/docs.md", b"created by Peter Rabbit") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("winapi") +// .version("0.3.9") +// .archive_storage(archive_storage) +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_success("/winapi/0.3.8/src/winapi/docs.md.html") +// .await?; +// // people can end up here from clicking "go to latest" while in source view +// web.assert_redirect( +// "/crate/winapi/0.3.9/target-redirect/src/winapi/docs.md.html", +// "/winapi/0.3.9/winapi/", +// ) +// .await?; +// Ok(()) +// }) +// } + +// #[test] +// fn noindex_nonlatest() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .rustdoc_file("dummy/index.html") +// .create() +// .await?; + +// let web = env.web_app().await; + +// assert!( +// web.get("/dummy/0.1.0/dummy/") +// .await? +// .headers() +// .get("x-robots-tag") +// .unwrap() +// .to_str() +// .unwrap() +// .contains("noindex") +// ); + +// assert!( +// web.get("/dummy/latest/dummy/") +// .await? +// .headers() +// .get("x-robots-tag") +// .is_none() +// ); +// Ok(()) +// }) +// } + +// #[test] +// fn download_unknown_version_404() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; +// web.assert_not_found("/crate/dummy/0.1.0/download").await?; + +// Ok(()) +// }); +// } + +// #[test] +// fn download_old_storage_version_404() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(false) +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_not_found("/crate/dummy/0.1.0/download").await?; + +// Ok(()) +// }); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn download_semver() -> Result<()> { +// let env = TestEnvironment::with_config(TestEnvironment::base_config().build()?).await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .create() +// .await?; + +// let web = env.web_app().await; + +// web.assert_redirect_cached( +// "/crate/dummy/0.1/download", +// "/crate/dummy/0.1.0/download", +// CachePolicy::ForeverInCdn, +// env.config(), +// ) +// .await?; +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn download_specfic_version() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .create() +// .await?; + +// let web = env.web_app().await; +// let path = "/crate/dummy/0.1.0/download"; + +// let resp = web +// .assert_success_cached(path, CachePolicy::ForeverInCdn, env.config()) +// .await?; +// assert_eq!( +// resp.headers().get(CONTENT_DISPOSITION).unwrap(), +// "attachment; filename=\"rustdoc-dummy-0.1.0.zip\"" +// ); +// web.assert_conditional_get(path, &resp).await?; + +// check_archive_consistency(&web.assert_success(path).await?.bytes().await?)?; + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn download_latest_version() -> Result<()> { +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(true) +// .create() +// .await?; + +// let web = env.web_app().await; +// let path = "/crate/dummy/latest/download"; + +// let resp = web +// .assert_success_cached(path, CachePolicy::ForeverInCdn, env.config()) +// .await?; +// assert_eq!( +// resp.headers().get(CONTENT_DISPOSITION).unwrap(), +// "attachment; filename=\"rustdoc-dummy-0.2.0.zip\"" +// ); +// web.assert_conditional_get(path, &resp).await?; + +// check_archive_consistency(&web.assert_success(path).await?.bytes().await?)?; + +// Ok(()) +// } + +// #[test_case("something.js")] +// #[test_case("something.css")] +// fn serve_release_specific_static_assets(name: &str) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .rustdoc_file_with(name, b"content") +// .create() +// .await?; + +// let web = env.web_app().await; + +// assert_eq!( +// web.assert_success(&format!("/dummy/0.1.0/{name}")) +// .await? +// .text() +// .await?, +// "content" +// ); + +// web.assert_success_and_conditional_get(&format!("/dummy/0.1.0/{name}")) +// .await?; + +// Ok(()) +// }) +// } + +// #[tokio::test(flavor = "multi_thread")] +// #[test_case("folder/file.js")] +// #[test_case("root.css")] +// async fn test_static_asset_handler(path: &str) -> Result<()> { +// let env = TestEnvironment::new().await?; + +// let storage = env.async_storage(); +// storage +// .store_one( +// format!("{RUSTDOC_STATIC_STORAGE_PREFIX}{path}"), +// b"static content", +// ) +// .await?; + +// let web = env.web_app().await; + +// assert_eq!( +// web.assert_success(&format!("/-/rustdoc.static/{path}"),) +// .await? +// .text() +// .await?, +// "static content" +// ); + +// web.assert_success_and_conditional_get(&format!("/-/rustdoc.static/{path}")) +// .await?; + +// Ok(()) +// } + +// #[test_case("search-1234.js")] +// #[test_case("settings-1234.js")] +// fn fallback_to_root_storage_for_some_js_assets(path: &str) { +// // tests for two separate things needed to serve old rustdoc content +// // 1. `/{crate}/{version}/asset.js`, where we try to find the assets in the rustdoc archive +// // 2. `/asset.js` where we try to find it in RUSTDOC_STATIC_STORAGE_PREFIX +// // +// // For 2), new builds use the assets from RUSTDOC_STATIC_STORAGE_PREFIX via +// // `/-/rustdoc.static/asset.js`. +// // +// // For 1) I'm actually not sure, new builds don't seem to have these assets. +// // ( the logic is special-cased to `search-` and `settings-` prefixes.) +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .create() +// .await?; + +// const ROOT_ASSET: &str = "normalize-20200403-1.44.0-nightly-74bd074ee.css"; + +// let storage = env.async_storage(); +// storage.store_one(ROOT_ASSET, *b"content").await?; +// storage.store_one(path, *b"more_content").await?; + +// let web = env.web_app().await; + +// let response = web.get(&format!("/dummy/0.1.0/{ROOT_ASSET}")).await?; +// assert_eq!( +// response.status(), +// StatusCode::NOT_FOUND, +// "{:?}", +// response.headers().get("Location"), +// ); + +// for (path, expected_content) in [ +// (format!("/{ROOT_ASSET}"), "content"), +// (format!("/dummy/0.1.0/{path}"), "more_content"), +// ] { +// let resp = web.assert_success(&path).await?; +// web.assert_conditional_get(&path, &resp).await?; +// assert_eq!(resp.text().await?, expected_content); +// } + +// Ok(()) +// }) +// } + +// #[test] +// fn redirect_with_encoded_chars_in_path() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("clap") +// .version("2.24.0") +// .add_platform("i686-pc-windows-gnu") +// .archive_storage(true) +// .create() +// .await?; +// let web = env.web_app().await; + +// web.assert_redirect_cached_unchecked( +// "/clap/2.24.0/i686-pc-windows-gnu/clap/which%20is%20a%20part%20of%20%5B%60Display%60%5D", +// "/crate/clap/2.24.0/target-redirect/i686-pc-windows-gnu/clap/which%20is%20a%20part%20of%20[%60Display%60]", +// CachePolicy::ForeverInCdn, +// env.config(), +// ).await?; + +// Ok(()) +// }) +// } + +// #[test] +// fn search_with_encoded_chars_in_path() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("clap") +// .version("2.24.0") +// .archive_storage(true) +// .create() +// .await?; +// let web = env.web_app().await; + +// web.assert_redirect_cached_unchecked( +// "/clap/latest/clapproc%20macro%20%60Parser%60%20not%20expanded:%20Cannot%20create%20expander%20for", +// "/clap/latest/clap/clapproc%20macro%20%60Parser%60%20not%20expanded:%20Cannot%20create%20expander%20for", +// CachePolicy::ForeverInCdn, +// env.config(), +// ).await?; + +// Ok(()) +// }) +// } + +// #[test_case("/something/1.2.3/some_path/", "/crate/something/1.2.3")] +// #[test_case("/something/latest/some_path/", "/crate/something/latest")] +// fn rustdoc_page_from_failed_build_redirects_to_crate(path: &str, expected: &str) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("something") +// .version("1.2.3") +// .archive_storage(true) +// .build_result_failed() +// .create() +// .await?; +// let web = env.web_app().await; + +// web.assert_redirect_cached(path, expected, CachePolicy::ForeverInCdn, env.config()) +// .await?; + +// Ok(()) +// }) +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn test_redirect_with_query_args(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake") +// .version("0.0.1") +// .archive_storage(archive_storage) +// .rustdoc_file("fake/index.html") +// .binary(true) // binary => rustdoc_status = false +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect("/fake?a=b", "/crate/fake/latest?a=b") +// .await?; + +// Ok(()) +// }); +// } + +// #[test_case("/crate/dummy/0.1/json", "/crate/dummy/0.1.0/json")] +// #[tokio::test(flavor = "multi_thread")] +// async fn json_download_semver_redirect(path: &str, expected_redirect: &str) -> Result<()> { +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("i686-pc-windows-msvc") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(true) +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("i686-pc-windows-msvc") +// .create() +// .await?; + +// let web = env.web_app().await; + +// web.assert_redirect_cached( +// path, +// expected_redirect, +// CachePolicy::ForeverInCdn, +// env.config(), +// ) +// .await?; +// Ok(()) +// } + +// #[test_case( +// "latest/json", +// CompressionAlgorithm::Zstd, +// "x86_64-unknown-linux-gnu", +// "latest", +// "0.2.0" +// )] +// #[test_case( +// "latest/json.gz", +// CompressionAlgorithm::Gzip, +// "x86_64-unknown-linux-gnu", +// "latest", +// "0.2.0" +// )] +// #[test_case( +// "0.1.0/json", +// CompressionAlgorithm::Zstd, +// "x86_64-unknown-linux-gnu", +// "latest", +// "0.1.0" +// )] +// #[test_case( +// "latest/json/latest", +// CompressionAlgorithm::Zstd, +// "x86_64-unknown-linux-gnu", +// "latest", +// "0.2.0" +// )] +// #[test_case( +// "latest/json/latest.gz", +// CompressionAlgorithm::Gzip, +// "x86_64-unknown-linux-gnu", +// "latest", +// "0.2.0" +// )] +// #[test_case( +// "latest/json/42", +// CompressionAlgorithm::Zstd, +// "x86_64-unknown-linux-gnu", +// "42", +// "0.2.0" +// )] +// #[test_case( +// "latest/i686-pc-windows-msvc/json", +// CompressionAlgorithm::Zstd, +// "i686-pc-windows-msvc", +// "latest", +// "0.2.0" +// )] +// #[test_case( +// "latest/i686-pc-windows-msvc/json.gz", +// CompressionAlgorithm::Gzip, +// "i686-pc-windows-msvc", +// "latest", +// "0.2.0" +// )] +// #[test_case( +// "latest/i686-pc-windows-msvc/json/42", +// CompressionAlgorithm::Zstd, +// "i686-pc-windows-msvc", +// "42", +// "0.2.0" +// )] +// #[test_case( +// "latest/i686-pc-windows-msvc/json/42.gz", +// CompressionAlgorithm::Gzip, +// "i686-pc-windows-msvc", +// "42", +// "0.2.0" +// )] +// #[test_case( +// "latest/i686-pc-windows-msvc/json/42.zst", +// CompressionAlgorithm::Zstd, +// "i686-pc-windows-msvc", +// "42", +// "0.2.0" +// )] +// #[tokio::test(flavor = "multi_thread")] +// async fn json_download( +// request_path_suffix: &str, +// expected_compression: CompressionAlgorithm, +// expected_target: &str, +// expected_format_version: &str, +// expected_version: &str, +// ) -> Result<()> { +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("i686-pc-windows-msvc") +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(true) +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("i686-pc-windows-msvc") +// .create() +// .await?; + +// let web = env.web_app().await; + +// let path = format!("/crate/dummy/{request_path_suffix}"); +// let resp = web +// .assert_success_cached(&path, CachePolicy::ForeverInCdn, env.config()) +// .await?; +// assert_eq!( +// resp.headers().get(CONTENT_DISPOSITION).unwrap(), +// &format!( +// "attachment; filename=\"dummy_{expected_version}_{expected_target}_{expected_format_version}.json.{}\"", +// expected_compression.file_extension() +// ) +// ); +// web.assert_conditional_get(&path, &resp).await?; + +// { +// let compressed_body = web.assert_success(&path).await?.bytes().await?.to_vec(); +// let json_body = decompress(&*compressed_body, expected_compression, usize::MAX)?; +// assert_eq!( +// read_format_version_from_rustdoc_json(&*json_body)?, +// // for both "Latest", and "Version(42)", the version number in json is the +// // specific number. +// "42".parse().unwrap() +// ); +// } + +// Ok(()) +// } + +// #[test_case("")] +// #[test_case(".zst")] +// #[tokio::test(flavor = "multi_thread")] +// async fn test_json_download_fallback_to_old_files_without_compression_extension( +// ext: &str, +// ) -> Result<()> { +// let env = TestEnvironment::new().await?; + +// const NAME: &str = "dummy"; +// const VERSION: Version = Version::new(0, 1, 0); +// const TARGET: &str = "x86_64-unknown-linux-gnu"; +// const FORMAT_VERSION: RustdocJsonFormatVersion = RustdocJsonFormatVersion::Latest; + +// env.fake_release() +// .await +// .name(NAME) +// .version(VERSION) +// .archive_storage(true) +// .default_target(TARGET) +// .create() +// .await?; + +// let storage = env.async_storage(); + +// let zstd_blob = storage +// .get( +// &rustdoc_json_path( +// NAME, +// &VERSION, +// TARGET, +// FORMAT_VERSION, +// Some(CompressionAlgorithm::Zstd), +// ), +// usize::MAX, +// ) +// .await?; + +// for compression in RUSTDOC_JSON_COMPRESSION_ALGORITHMS { +// let path = +// rustdoc_json_path(NAME, &VERSION, TARGET, FORMAT_VERSION, Some(*compression)); +// storage.delete_prefix(&path).await?; +// assert!(!storage.exists(&path).await?); +// } +// storage +// .store_one( +// &rustdoc_json_path(NAME, &VERSION, TARGET, FORMAT_VERSION, None), +// zstd_blob.content, +// ) +// .await?; + +// let web = env.web_app().await; + +// let path = format!("/crate/dummy/latest/json{ext}"); +// let resp = web +// .assert_success_cached(&path, CachePolicy::ForeverInCdn, env.config()) +// .await?; +// assert_eq!( +// resp.headers().get(CONTENT_DISPOSITION).unwrap(), +// &format!("attachment; filename=\"{NAME}_{VERSION}_{TARGET}_latest.json\""), +// ); +// web.assert_conditional_get(&path, &resp).await?; +// Ok(()) +// } + +// #[test_case("0.1.0/json"; "rustdoc status false")] +// #[test_case("0.2.0/unknown-target/json"; "unknown target")] +// #[test_case("0.2.0/json/99"; "target file doesnt exist")] +// #[test_case("0.42.0/json"; "unknown version")] +// #[tokio::test(flavor = "multi_thread")] +// async fn json_download_not_found(request_path_suffix: &str) -> Result<()> { +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.1.0") +// .archive_storage(true) +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("i686-pc-windows-msvc") +// .binary(true) // binary => rustdoc_status = false +// .create() +// .await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("0.2.0") +// .archive_storage(true) +// .default_target("x86_64-unknown-linux-gnu") +// .add_target("i686-pc-windows-msvc") +// .create() +// .await?; + +// let web = env.web_app().await; + +// let response = web +// .get(&format!("/crate/dummy/{request_path_suffix}")) +// .await?; +// assert!(response.headers().get(CONTENT_DISPOSITION).is_none()); +// assert_eq!(response.status(), StatusCode::NOT_FOUND); +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// #[test_case("/dummy/"; "only krate")] +// #[test_case("/dummy/latest/"; "with version")] +// #[test_case("/dummy/latest/dummy"; "target-name as path, without trailing slash")] +// #[test_case("/dummy/latest/dummy/"; "final target")] +// async fn test_full_latest_url_without_trailing_slash(path: &str) -> Result<()> { +// // test for https://github.com/rust-lang/docs.rs/issues/2989 + +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .create() +// .await?; + +// let web = env.web_app().await; +// const TARGET: &str = "/dummy/latest/dummy/"; +// if path == TARGET { +// web.get(path).await?.status().is_success(); +// } else { +// web.assert_redirect_unchecked(path, "/dummy/latest/dummy/") +// .await?; +// } + +// Ok(()) +// } +// #[tokio::test(flavor = "multi_thread")] +// #[test_case( +// "/dummy/latest/other_path", +// "/dummy/latest/dummy/other_path"; +// "other path, without trailing slash" +// )] +// #[test_case( +// "/dummy/latest/other_path.html", +// "/dummy/latest/dummy/other_path.html"; +// "other html path, without trailing slash" +// )] +// async fn test_full_latest_url_some_path_but_trailing_slash( +// path: &str, +// expected_redirect: &str, +// ) -> Result<()> { +// // test for https://github.com/rust-lang/docs.rs/issues/2989 + +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("dummy") +// .version("1.0.0") +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect_unchecked(path, expected_redirect) +// .await?; + +// Ok(()) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn test_fetch_item_with_semver_url() -> Result<()> { +// // https://github.com/rust-lang/docs.rs/issues/3036 +// // This fixes an issue where we mistakenly attached a +// // trailing `/` to a rustdoc URL when redirecting +// // to the exact version, coming from a semver version. +// let env = TestEnvironment::new().await?; + +// env.fake_release() +// .await +// .name("itertools") +// .version("0.14.0") +// .rustdoc_file("itertools/trait.Itertools.html") +// .create() +// .await?; + +// let web = env.web_app().await; +// web.assert_redirect( +// "/itertools/^0.14/itertools/trait.Itertools.html", +// "/itertools/0.14.0/itertools/trait.Itertools.html", +// ) +// .await?; + +// Ok(()) +// } +// } diff --git a/crates/docs_rs_web/src/sitemap.rs b/crates/docs_rs_web/src/sitemap.rs index d02c2d555..f378c5a44 100644 --- a/crates/docs_rs_web/src/sitemap.rs +++ b/crates/docs_rs_web/src/sitemap.rs @@ -1,14 +1,11 @@ use crate::{ - Config, - docbuilder::Limits, + AxumErrorPage, + config::Config, + // docbuilder::Limits, + error::{AxumNope, AxumResult}, + extractors::{DbConnection, Path}, impl_axum_webpage, - utils::report_error, - web::{ - AxumErrorPage, - error::{AxumNope, AxumResult}, - extractors::{DbConnection, Path}, - page::templates::{RenderBrands, RenderSolid, filters}, - }, + page::templates::{RenderBrands, RenderSolid, filters}, }; use anyhow::Context as _; use askama::Template; @@ -226,133 +223,133 @@ pub(crate) async fn about_handler(subpage: Option>) -> AxumResult Vec { - let dom = kuchikiki::parse_html().one(body); - - dom.select(".package-menu > ul > li > a") - .expect("invalid selector") - .map(|el| { - let attributes = el.attributes.borrow(); - attributes.get("href").unwrap().to_string() - }) - .collect() - } - - #[test_case(true)] - #[test_case(false)] - fn fetch_source_file_utf8_path(archive_storage: bool) { - async_wrapper(|env| async move { - let filename = "序.pdf"; - - env.fake_release() - .await - .archive_storage(archive_storage) - .name("fake") - .version("0.1.0") - .source_file(filename, b"some_random_content") - .create() - .await?; - - let web = env.web_app().await; - let response = web - .get(&format!( - "/crate/fake/0.1.0/source/{}", - encode_url_path(filename) - )) - .await?; - assert!(response.status().is_success()); - assert_eq!( - response.headers().get("link").unwrap(), - "; rel=\"canonical\"", - ); - assert!(response.text().await?.contains("some_random_content")); - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn fetch_source_file_content(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .archive_storage(archive_storage) - .name("fake") - .version("0.1.0") - .source_file("some_filename.rs", b"some_random_content") - .create() - .await?; - let web = env.web_app().await; - web.assert_success_cached( - "/crate/fake/0.1.0/source/", - CachePolicy::ForeverInCdnAndStaleInBrowser, - env.config(), - ) - .await?; - let response = web.get("/crate/fake/0.1.0/source/some_filename.rs").await?; - assert!(response.status().is_success()); - assert_eq!( - response.headers().get("link").unwrap(), - "; rel=\"canonical\"" - ); - response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); - assert!(response.text().await?.contains("some_random_content")); - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn fetch_binary(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .archive_storage(archive_storage) - .name("fake") - .version("0.1.0") - .source_file("some_file.pdf", b"some_random_content") - .create() - .await?; - let web = env.web_app().await; - - const URL: &str = "/crate/fake/0.1.0/source/some_file.pdf"; - - // first request, uncached - let response = web.get(URL).await?; - assert!(response.status().is_success()); - let headers = response.headers(); - assert_eq!( - headers.get("link").unwrap(), - "; rel=\"canonical\"" - ); - assert_eq!( - headers.typed_get::().unwrap(), - APPLICATION_PDF.into(), - ); - response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); - - let etag: ETag = headers.typed_get().unwrap(); - - assert!(response.text().await?.contains("some_random_content")); - - let response = web - .get_with_headers(URL, |headers| { - headers.typed_insert(IfNoneMatch::from(etag)); - }) - .await?; - assert_eq!(response.status(), StatusCode::NOT_MODIFIED); - - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn cargo_ok_not_skipped(archive_storage: bool) { - async_wrapper(|env| async move { - env.fake_release() - .await - .archive_storage(archive_storage) - .name("fake") - .version("0.1.0") - .source_file(".cargo-ok", b"ok") - .source_file("README.md", b"hello") - .create() - .await?; - let web = env.web_app().await; - web.assert_success("/crate/fake/0.1.0/source/").await?; - Ok(()) - }); - } - - #[test_case(true)] - #[test_case(false)] - fn empty_file_list_dont_break_the_view(archive_storage: bool) { - async_wrapper(|env| async move { - let release_id = env - .fake_release() - .await - .archive_storage(archive_storage) - .name("fake") - .version("0.1.0") - .source_file("README.md", b"hello") - .create() - .await?; - - let path = "/crate/fake/0.1.0/source/README.md"; - let web = env.web_app().await; - web.assert_success(path).await?; - - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - "UPDATE releases - SET files = NULL - WHERE id = $1", - release_id.0, - ) - .execute(&mut *conn) - .await?; - - assert!(web.get(path).await?.status().is_success()); - - Ok(()) - }); - } - - #[test] - fn latest_contains_links_to_latest() { - async_wrapper(|env| async move { - env.fake_release() - .await - .archive_storage(true) - .name("fake") - .version("0.1.0") - .source_file(".cargo-ok", b"ok") - .source_file("README.md", b"hello") - .create() - .await?; - let resp = env - .web_app() - .await - .get("/crate/fake/latest/source/") - .await?; - resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); - let body = resp.text().await?; - assert!(body.contains(""#)); - - let response = web - .get("/crate/fake/0.1.0/source/Cargo.lock") - .await? - .text() - .await?; - assert!(response.contains(r#""#)); - - Ok(()) - }); - } - - #[test] - fn dotfiles_with_extension_are_highlighted() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake") - .version("0.1.0") - .source_file(".rustfmt.toml", b"[rustfmt]") - .create() - .await?; - - let web = env.web_app().await; - - let response = web - .get("/crate/fake/0.1.0/source/.rustfmt.toml") - .await? - .text() - .await?; - assert!(response.contains(r#""#)); - - Ok(()) - }); - } - - #[test] - fn json_is_served_as_rendered_html() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake") - .version("0.1.0") - .source_file("Cargo.toml", b"") - .source_file("config.json", b"{}") - .create() - .await?; - - let web = env.web_app().await; - - let response = web.get("/crate/fake/0.1.0/source/config.json").await?; - assert!( - response - .headers() - .get("content-type") - .unwrap() - .to_str() - .unwrap() - .starts_with("text/html") - ); - - let text = response.text().await?; - assert!(text.starts_with(r#""#)); - - // file list doesn't show "../" - assert_eq!( - get_file_list_links(&text), - vec!["./Cargo.toml", "./config.json"] - ); - - Ok(()) - }); - } - - #[test] - fn root_file_list() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake") - .version("0.1.0") - .source_file("Cargo.toml", b"some_random_content") - .source_file("folder1/some_filename.rs", b"some_random_content") - .source_file("folder2/another_filename.rs", b"some_random_content") - .source_file("root_filename.rs", b"some_random_content") - .create() - .await?; - - let web = env.web_app().await; - let response = web.get("/crate/fake/0.1.0/source/").await?; - assert!(response.status().is_success()); - response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); - - assert_eq!( - get_file_list_links(&response.text().await?), - vec![ - "./folder1/", - "./folder2/", - "./Cargo.toml", - "./root_filename.rs" - ] - ); - Ok(()) - }); - } - - #[test] - fn child_file_list() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("fake") - .version("0.1.0") - .source_file("folder1/some_filename.rs", b"some_random_content") - .source_file("folder1/more_filenames.rs", b"some_random_content") - .source_file("folder2/another_filename.rs", b"some_random_content") - .source_file("root_filename.rs", b"some_random_content") - .create() - .await?; - - let web = env.web_app().await; - let response = web - .get("/crate/fake/0.1.0/source/folder1/some_filename.rs") - .await?; - assert!(response.status().is_success()); - response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); - - assert_eq!( - get_file_list_links(&response.text().await?), - vec!["../", "./more_filenames.rs", "./some_filename.rs"], - ); - Ok(()) - }); - } - - #[tokio::test(flavor = "multi_thread")] - async fn large_file_test() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .max_file_size(1) - .max_file_size_html(1) - .build()?, - ) - .await?; - - env.fake_release() - .await - .name("fake") - .version("0.1.0") - .source_file("large_file.rs", b"some_random_content") - .create() - .await?; - - let web = env.web_app().await; - let response = web.get("/crate/fake/0.1.0/source/large_file.rs").await?; - assert_eq!(response.status(), StatusCode::OK); - assert!( - response - .text() - .await? - .contains("This file is too large to display") - ); - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// test::{AxumResponseTestExt, AxumRouterTestExt, TestEnvironment, async_wrapper}, +// web::{cache::CachePolicy, encode_url_path, headers::IfNoneMatch}, +// }; +// use anyhow::Result; +// use axum_extra::headers::{ContentType, ETag, HeaderMapExt as _}; +// use kuchikiki::traits::TendrilSink; +// use mime::APPLICATION_PDF; +// use reqwest::StatusCode; +// use test_case::test_case; + +// fn get_file_list_links(body: &str) -> Vec { +// let dom = kuchikiki::parse_html().one(body); + +// dom.select(".package-menu > ul > li > a") +// .expect("invalid selector") +// .map(|el| { +// let attributes = el.attributes.borrow(); +// attributes.get("href").unwrap().to_string() +// }) +// .collect() +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn fetch_source_file_utf8_path(archive_storage: bool) { +// async_wrapper(|env| async move { +// let filename = "序.pdf"; + +// env.fake_release() +// .await +// .archive_storage(archive_storage) +// .name("fake") +// .version("0.1.0") +// .source_file(filename, b"some_random_content") +// .create() +// .await?; + +// let web = env.web_app().await; +// let response = web +// .get(&format!( +// "/crate/fake/0.1.0/source/{}", +// encode_url_path(filename) +// )) +// .await?; +// assert!(response.status().is_success()); +// assert_eq!( +// response.headers().get("link").unwrap(), +// "; rel=\"canonical\"", +// ); +// assert!(response.text().await?.contains("some_random_content")); +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn fetch_source_file_content(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .archive_storage(archive_storage) +// .name("fake") +// .version("0.1.0") +// .source_file("some_filename.rs", b"some_random_content") +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_success_cached( +// "/crate/fake/0.1.0/source/", +// CachePolicy::ForeverInCdnAndStaleInBrowser, +// env.config(), +// ) +// .await?; +// let response = web.get("/crate/fake/0.1.0/source/some_filename.rs").await?; +// assert!(response.status().is_success()); +// assert_eq!( +// response.headers().get("link").unwrap(), +// "; rel=\"canonical\"" +// ); +// response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); +// assert!(response.text().await?.contains("some_random_content")); +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn fetch_binary(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .archive_storage(archive_storage) +// .name("fake") +// .version("0.1.0") +// .source_file("some_file.pdf", b"some_random_content") +// .create() +// .await?; +// let web = env.web_app().await; + +// const URL: &str = "/crate/fake/0.1.0/source/some_file.pdf"; + +// // first request, uncached +// let response = web.get(URL).await?; +// assert!(response.status().is_success()); +// let headers = response.headers(); +// assert_eq!( +// headers.get("link").unwrap(), +// "; rel=\"canonical\"" +// ); +// assert_eq!( +// headers.typed_get::().unwrap(), +// APPLICATION_PDF.into(), +// ); +// response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); + +// let etag: ETag = headers.typed_get().unwrap(); + +// assert!(response.text().await?.contains("some_random_content")); + +// let response = web +// .get_with_headers(URL, |headers| { +// headers.typed_insert(IfNoneMatch::from(etag)); +// }) +// .await?; +// assert_eq!(response.status(), StatusCode::NOT_MODIFIED); + +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn cargo_ok_not_skipped(archive_storage: bool) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .archive_storage(archive_storage) +// .name("fake") +// .version("0.1.0") +// .source_file(".cargo-ok", b"ok") +// .source_file("README.md", b"hello") +// .create() +// .await?; +// let web = env.web_app().await; +// web.assert_success("/crate/fake/0.1.0/source/").await?; +// Ok(()) +// }); +// } + +// #[test_case(true)] +// #[test_case(false)] +// fn empty_file_list_dont_break_the_view(archive_storage: bool) { +// async_wrapper(|env| async move { +// let release_id = env +// .fake_release() +// .await +// .archive_storage(archive_storage) +// .name("fake") +// .version("0.1.0") +// .source_file("README.md", b"hello") +// .create() +// .await?; + +// let path = "/crate/fake/0.1.0/source/README.md"; +// let web = env.web_app().await; +// web.assert_success(path).await?; + +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// "UPDATE releases +// SET files = NULL +// WHERE id = $1", +// release_id.0, +// ) +// .execute(&mut *conn) +// .await?; + +// assert!(web.get(path).await?.status().is_success()); + +// Ok(()) +// }); +// } + +// #[test] +// fn latest_contains_links_to_latest() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .archive_storage(true) +// .name("fake") +// .version("0.1.0") +// .source_file(".cargo-ok", b"ok") +// .source_file("README.md", b"hello") +// .create() +// .await?; +// let resp = env +// .web_app() +// .await +// .get("/crate/fake/latest/source/") +// .await?; +// resp.assert_cache_control(CachePolicy::ForeverInCdn, env.config()); +// let body = resp.text().await?; +// assert!(body.contains(""#)); + +// let response = web +// .get("/crate/fake/0.1.0/source/Cargo.lock") +// .await? +// .text() +// .await?; +// assert!(response.contains(r#""#)); + +// Ok(()) +// }); +// } + +// #[test] +// fn dotfiles_with_extension_are_highlighted() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake") +// .version("0.1.0") +// .source_file(".rustfmt.toml", b"[rustfmt]") +// .create() +// .await?; + +// let web = env.web_app().await; + +// let response = web +// .get("/crate/fake/0.1.0/source/.rustfmt.toml") +// .await? +// .text() +// .await?; +// assert!(response.contains(r#""#)); + +// Ok(()) +// }); +// } + +// #[test] +// fn json_is_served_as_rendered_html() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake") +// .version("0.1.0") +// .source_file("Cargo.toml", b"") +// .source_file("config.json", b"{}") +// .create() +// .await?; + +// let web = env.web_app().await; + +// let response = web.get("/crate/fake/0.1.0/source/config.json").await?; +// assert!( +// response +// .headers() +// .get("content-type") +// .unwrap() +// .to_str() +// .unwrap() +// .starts_with("text/html") +// ); + +// let text = response.text().await?; +// assert!(text.starts_with(r#""#)); + +// // file list doesn't show "../" +// assert_eq!( +// get_file_list_links(&text), +// vec!["./Cargo.toml", "./config.json"] +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn root_file_list() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake") +// .version("0.1.0") +// .source_file("Cargo.toml", b"some_random_content") +// .source_file("folder1/some_filename.rs", b"some_random_content") +// .source_file("folder2/another_filename.rs", b"some_random_content") +// .source_file("root_filename.rs", b"some_random_content") +// .create() +// .await?; + +// let web = env.web_app().await; +// let response = web.get("/crate/fake/0.1.0/source/").await?; +// assert!(response.status().is_success()); +// response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); + +// assert_eq!( +// get_file_list_links(&response.text().await?), +// vec![ +// "./folder1/", +// "./folder2/", +// "./Cargo.toml", +// "./root_filename.rs" +// ] +// ); +// Ok(()) +// }); +// } + +// #[test] +// fn child_file_list() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("fake") +// .version("0.1.0") +// .source_file("folder1/some_filename.rs", b"some_random_content") +// .source_file("folder1/more_filenames.rs", b"some_random_content") +// .source_file("folder2/another_filename.rs", b"some_random_content") +// .source_file("root_filename.rs", b"some_random_content") +// .create() +// .await?; + +// let web = env.web_app().await; +// let response = web +// .get("/crate/fake/0.1.0/source/folder1/some_filename.rs") +// .await?; +// assert!(response.status().is_success()); +// response.assert_cache_control(CachePolicy::ForeverInCdnAndStaleInBrowser, env.config()); + +// assert_eq!( +// get_file_list_links(&response.text().await?), +// vec!["../", "./more_filenames.rs", "./some_filename.rs"], +// ); +// Ok(()) +// }); +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn large_file_test() -> Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .max_file_size(1) +// .max_file_size_html(1) +// .build()?, +// ) +// .await?; + +// env.fake_release() +// .await +// .name("fake") +// .version("0.1.0") +// .source_file("large_file.rs", b"some_random_content") +// .create() +// .await?; + +// let web = env.web_app().await; +// let response = web.get("/crate/fake/0.1.0/source/large_file.rs").await?; +// assert_eq!(response.status(), StatusCode::OK); +// assert!( +// response +// .text() +// .await? +// .contains("This file is too large to display") +// ); +// Ok(()) +// } +// } diff --git a/crates/docs_rs_web/src/statics.rs b/crates/docs_rs_web/src/statics.rs index 6faab497e..dc7a4d489 100644 --- a/crates/docs_rs_web/src/statics.rs +++ b/crates/docs_rs_web/src/statics.rs @@ -128,236 +128,236 @@ pub(crate) fn build_static_router() -> AxumRouter { .layer(middleware::from_fn(conditional_get)) } -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}, - web::headers::compute_etag, - }; - use axum::{Router, body::Body}; - use http::{ - HeaderMap, - header::{CONTENT_LENGTH, CONTENT_TYPE, ETAG}, - }; - use std::fs; - use test_case::test_case; - use tower::ServiceExt as _; - - const STATIC_SEARCH_PATHS: &[&str] = &["static", "vendor"]; - - fn content_length(resp: &Response) -> u64 { - resp.headers() - .get(CONTENT_LENGTH) - .expect("content-length header") - .to_str() - .unwrap() - .parse() - .unwrap() - } - - fn etag(resp: &Response) -> ETag { - resp.headers().typed_get().unwrap() - } - - async fn test_conditional_get(web: &Router, path: &str) -> anyhow::Result<()> { - fn req(path: &str, f: impl FnOnce(&mut HeaderMap)) -> Request { - let mut builder = Request::builder().uri(path); - f(builder.headers_mut().unwrap()); - builder.body(Body::empty()).unwrap() - } - - // original request = 200 - let resp = web.clone().oneshot(req(path, |_| {})).await?; - - assert_eq!(resp.status(), StatusCode::OK); - let etag = etag(&resp); - - { - // if-none-match with correct etag - let if_none_match: IfNoneMatch = etag.into(); - - let cached_response = web - .clone() - .oneshot(req(path, |h| h.typed_insert(if_none_match))) - .await?; - - assert_eq!(cached_response.status(), StatusCode::NOT_MODIFIED); - } - - { - let other_if_none_match: IfNoneMatch = "\"some-other-etag\"" - .parse::() - .expect("valid etag") - .into(); - - let uncached_response = web - .clone() - .oneshot(req(path, |h| h.typed_insert(other_if_none_match))) - .await?; - - assert_eq!(uncached_response.status(), StatusCode::OK); - } - - Ok(()) - } - - #[test] - fn style_css() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - const PATH: &str = "/-/static/style.css"; - let resp = web.get(PATH).await?; - assert!(resp.status().is_success()); - resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); - let headers = resp.headers(); - assert_eq!( - headers.get(CONTENT_TYPE), - Some(&"text/css".parse().unwrap()), - ); - - assert_eq!(content_length(&resp), STYLE_CSS.len() as u64); - assert_eq!(etag(&resp), compute_etag(STYLE_CSS.as_bytes())); - assert_eq!(resp.bytes().await?, STYLE_CSS.as_bytes()); - - test_conditional_get(&web, PATH).await?; - - Ok(()) - }); - } - - #[test] - fn vendored_css() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - const PATH: &str = "/-/static/vendored.css"; - - let resp = web.get(PATH).await?; - assert!(resp.status().is_success(), "{}", resp.text().await?); - - resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); - assert_eq!( - resp.headers().get(CONTENT_TYPE), - Some(&"text/css".parse().unwrap()), - ); - assert_eq!(content_length(&resp), VENDORED_CSS.len() as u64); - assert_eq!(etag(&resp), compute_etag(VENDORED_CSS.as_bytes())); - assert_eq!(resp.text().await?, VENDORED_CSS); - - test_conditional_get(&web, PATH).await?; - - Ok(()) - }); - } - - #[test] - fn io_error_not_a_directory_leads_to_404() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - // just to be sure that `index.js` exists - assert!(web.get("/-/static/index.js").await?.status().is_success()); - - // `index.js` exists, but is not a directory, - // so trying to fetch it via `ServeDir` will lead - // to an IO-error. - let resp = web.get("/-/static/index.js/something").await?; - assert_eq!(resp.status().as_u16(), StatusCode::NOT_FOUND); - assert!(resp.headers().get(ETAG).is_none()); - - Ok(()) - }); - } - - #[test_case("/-/static/index.js", "resetClipboardTimeout")] - #[test_case("/-/static/menu.js", "closeMenu")] - #[test_case("/-/static/keyboard.js", "handleKey")] - #[test_case("/-/static/source.js", "toggleSource")] - fn js_content(path: &str, expected_content: &str) { - async_wrapper(|env| async move { - let web = env.web_app().await; - - let resp = web.get(path).await?; - assert!(resp.status().is_success()); - resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); - assert_eq!( - resp.headers().get(CONTENT_TYPE), - Some(&"text/javascript".parse().unwrap()), - ); - assert!(content_length(&resp) > 10); - etag(&resp); // panics if etag missing or invalid - assert!(resp.text().await?.contains(expected_content)); - - test_conditional_get(&web, path).await?; - - Ok(()) - }); - } - - #[test] - fn static_files() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - for root in STATIC_SEARCH_PATHS { - for entry in walkdir::WalkDir::new(root) { - let entry = entry?; - if !entry.file_type().is_file() { - continue; - } - let file = entry.path().strip_prefix(root).unwrap(); - let path = entry.path(); - - let url = format!("/-/static/{}", file.to_str().unwrap()); - let resp = web.get(&url).await?; - - assert!(resp.status().is_success(), "failed to fetch {url:?}"); - resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); - let content = fs::read(path).unwrap(); - assert_eq!(etag(&resp), compute_etag(&content)); - assert_eq!(resp.bytes().await?, content, "failed to fetch {url:?}",); - - test_conditional_get(&web, &url).await?; - } - } - - Ok(()) - }); - } - - #[test] - fn static_file_that_doesnt_exist() { - async_wrapper(|env| async move { - let response = env.web_app().await.get("/-/static/whoop-de-do.png").await?; - response.assert_cache_control(CachePolicy::NoCaching, env.config()); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - assert!(response.headers().get(ETAG).is_none()); - - Ok(()) - }); - } - - #[test] - fn static_mime_types() { - async_wrapper(|env| async move { - let web = env.web_app().await; - - let files = &[("vendored.css", "text/css")]; - - for (file, mime) in files { - let url = format!("/-/static/{file}"); - let resp = web.get(&url).await?; - - assert_eq!( - resp.headers().get(CONTENT_TYPE), - Some(&mime.parse().unwrap()), - "{url:?} has an incorrect content type", - ); - } - - Ok(()) - }); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::{ +// test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}, +// web::headers::compute_etag, +// }; +// use axum::{Router, body::Body}; +// use http::{ +// HeaderMap, +// header::{CONTENT_LENGTH, CONTENT_TYPE, ETAG}, +// }; +// use std::fs; +// use test_case::test_case; +// use tower::ServiceExt as _; + +// const STATIC_SEARCH_PATHS: &[&str] = &["static", "vendor"]; + +// fn content_length(resp: &Response) -> u64 { +// resp.headers() +// .get(CONTENT_LENGTH) +// .expect("content-length header") +// .to_str() +// .unwrap() +// .parse() +// .unwrap() +// } + +// fn etag(resp: &Response) -> ETag { +// resp.headers().typed_get().unwrap() +// } + +// async fn test_conditional_get(web: &Router, path: &str) -> anyhow::Result<()> { +// fn req(path: &str, f: impl FnOnce(&mut HeaderMap)) -> Request { +// let mut builder = Request::builder().uri(path); +// f(builder.headers_mut().unwrap()); +// builder.body(Body::empty()).unwrap() +// } + +// // original request = 200 +// let resp = web.clone().oneshot(req(path, |_| {})).await?; + +// assert_eq!(resp.status(), StatusCode::OK); +// let etag = etag(&resp); + +// { +// // if-none-match with correct etag +// let if_none_match: IfNoneMatch = etag.into(); + +// let cached_response = web +// .clone() +// .oneshot(req(path, |h| h.typed_insert(if_none_match))) +// .await?; + +// assert_eq!(cached_response.status(), StatusCode::NOT_MODIFIED); +// } + +// { +// let other_if_none_match: IfNoneMatch = "\"some-other-etag\"" +// .parse::() +// .expect("valid etag") +// .into(); + +// let uncached_response = web +// .clone() +// .oneshot(req(path, |h| h.typed_insert(other_if_none_match))) +// .await?; + +// assert_eq!(uncached_response.status(), StatusCode::OK); +// } + +// Ok(()) +// } + +// #[test] +// fn style_css() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// const PATH: &str = "/-/static/style.css"; +// let resp = web.get(PATH).await?; +// assert!(resp.status().is_success()); +// resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); +// let headers = resp.headers(); +// assert_eq!( +// headers.get(CONTENT_TYPE), +// Some(&"text/css".parse().unwrap()), +// ); + +// assert_eq!(content_length(&resp), STYLE_CSS.len() as u64); +// assert_eq!(etag(&resp), compute_etag(STYLE_CSS.as_bytes())); +// assert_eq!(resp.bytes().await?, STYLE_CSS.as_bytes()); + +// test_conditional_get(&web, PATH).await?; + +// Ok(()) +// }); +// } + +// #[test] +// fn vendored_css() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// const PATH: &str = "/-/static/vendored.css"; + +// let resp = web.get(PATH).await?; +// assert!(resp.status().is_success(), "{}", resp.text().await?); + +// resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); +// assert_eq!( +// resp.headers().get(CONTENT_TYPE), +// Some(&"text/css".parse().unwrap()), +// ); +// assert_eq!(content_length(&resp), VENDORED_CSS.len() as u64); +// assert_eq!(etag(&resp), compute_etag(VENDORED_CSS.as_bytes())); +// assert_eq!(resp.text().await?, VENDORED_CSS); + +// test_conditional_get(&web, PATH).await?; + +// Ok(()) +// }); +// } + +// #[test] +// fn io_error_not_a_directory_leads_to_404() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// // just to be sure that `index.js` exists +// assert!(web.get("/-/static/index.js").await?.status().is_success()); + +// // `index.js` exists, but is not a directory, +// // so trying to fetch it via `ServeDir` will lead +// // to an IO-error. +// let resp = web.get("/-/static/index.js/something").await?; +// assert_eq!(resp.status().as_u16(), StatusCode::NOT_FOUND); +// assert!(resp.headers().get(ETAG).is_none()); + +// Ok(()) +// }); +// } + +// #[test_case("/-/static/index.js", "resetClipboardTimeout")] +// #[test_case("/-/static/menu.js", "closeMenu")] +// #[test_case("/-/static/keyboard.js", "handleKey")] +// #[test_case("/-/static/source.js", "toggleSource")] +// fn js_content(path: &str, expected_content: &str) { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// let resp = web.get(path).await?; +// assert!(resp.status().is_success()); +// resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); +// assert_eq!( +// resp.headers().get(CONTENT_TYPE), +// Some(&"text/javascript".parse().unwrap()), +// ); +// assert!(content_length(&resp) > 10); +// etag(&resp); // panics if etag missing or invalid +// assert!(resp.text().await?.contains(expected_content)); + +// test_conditional_get(&web, path).await?; + +// Ok(()) +// }); +// } + +// #[test] +// fn static_files() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// for root in STATIC_SEARCH_PATHS { +// for entry in walkdir::WalkDir::new(root) { +// let entry = entry?; +// if !entry.file_type().is_file() { +// continue; +// } +// let file = entry.path().strip_prefix(root).unwrap(); +// let path = entry.path(); + +// let url = format!("/-/static/{}", file.to_str().unwrap()); +// let resp = web.get(&url).await?; + +// assert!(resp.status().is_success(), "failed to fetch {url:?}"); +// resp.assert_cache_control(CachePolicy::ForeverInCdnAndBrowser, env.config()); +// let content = fs::read(path).unwrap(); +// assert_eq!(etag(&resp), compute_etag(&content)); +// assert_eq!(resp.bytes().await?, content, "failed to fetch {url:?}",); + +// test_conditional_get(&web, &url).await?; +// } +// } + +// Ok(()) +// }); +// } + +// #[test] +// fn static_file_that_doesnt_exist() { +// async_wrapper(|env| async move { +// let response = env.web_app().await.get("/-/static/whoop-de-do.png").await?; +// response.assert_cache_control(CachePolicy::NoCaching, env.config()); +// assert_eq!(response.status(), StatusCode::NOT_FOUND); +// assert!(response.headers().get(ETAG).is_none()); + +// Ok(()) +// }); +// } + +// #[test] +// fn static_mime_types() { +// async_wrapper(|env| async move { +// let web = env.web_app().await; + +// let files = &[("vendored.css", "text/css")]; + +// for (file, mime) in files { +// let url = format!("/-/static/{file}"); +// let resp = web.get(&url).await?; + +// assert_eq!( +// resp.headers().get(CONTENT_TYPE), +// Some(&mime.parse().unwrap()), +// "{url:?} has an incorrect content type", +// ); +// } + +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_web/src/status.rs b/crates/docs_rs_web/src/status.rs index 1ee8dc248..56a778a14 100644 --- a/crates/docs_rs_web/src/status.rs +++ b/crates/docs_rs_web/src/status.rs @@ -1,4 +1,4 @@ -use crate::web::{ +use crate::{ cache::CachePolicy, error::{AxumNope, AxumResult}, extractors::{DbConnection, rustdoc::RustdocParams}, @@ -44,166 +44,166 @@ pub(crate) async fn status_handler( ) } -#[cfg(test)] -mod tests { - use crate::{ - test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}, - web::{ReqVersion, cache::CachePolicy}, - }; - use reqwest::StatusCode; - use test_case::test_case; - - #[test_case("latest")] - #[test_case("0.1")] - #[test_case("0.1.0")] - #[test_case("=0.1.0"; "exact_version")] - fn status(req_version: &str) { - async_wrapper(|env| async move { - let req_version: ReqVersion = req_version.parse()?; - - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .create() - .await?; - - let response = env - .web_app() - .await - .get_and_follow_redirects(&format!("/crate/foo/{req_version}/status.json")) - .await?; - response.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); - assert_eq!(response.headers()["access-control-allow-origin"], "*"); - assert_eq!(response.status(), StatusCode::OK); - let value: serde_json::Value = serde_json::from_str(&response.text().await?)?; - - assert_eq!( - value, - serde_json::json!({ - "version": "0.1.0", - "doc_status": true, - }) - ); - - Ok(()) - }); - } - - #[test] - fn redirect_latest() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .create() - .await?; - - let web = env.web_app().await; - let redirect = web - .assert_redirect("/crate/foo/*/status.json", "/crate/foo/latest/status.json") - .await?; - redirect.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); - assert_eq!(redirect.headers()["access-control-allow-origin"], "*"); - - Ok(()) - }); - } - - #[test_case("0.1")] - #[test_case("~0.1"; "semver")] - fn redirect(req_version: &str) { - async_wrapper(|env| async move { - let req_version: ReqVersion = req_version.parse()?; - - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .create() - .await?; - - let web = env.web_app().await; - let redirect = web - .assert_redirect( - &format!("/crate/foo/{req_version}/status.json"), - "/crate/foo/0.1.0/status.json", - ) - .await?; - redirect.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); - assert_eq!(redirect.headers()["access-control-allow-origin"], "*"); - - Ok(()) - }); - } - - #[test_case("latest")] - #[test_case("0.1")] - #[test_case("0.1.0")] - #[test_case("=0.1.0"; "exact_version")] - fn failure(req_version: &str) { - async_wrapper(|env| async move { - let req_version: ReqVersion = req_version.parse()?; - - env.fake_release() - .await - .name("foo") - .version("0.1.0") - .build_result_failed() - .create() - .await?; - - let response = env - .web_app() - .await - .get_and_follow_redirects(&format!("/crate/foo/{req_version}/status.json")) - .await?; - response.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); - assert_eq!(response.headers()["access-control-allow-origin"], "*"); - assert_eq!(response.status(), StatusCode::OK); - let value: serde_json::Value = serde_json::from_str(&response.text().await?)?; - - assert_eq!( - value, - serde_json::json!({ - "version": "0.1.0", - "doc_status": false, - }) - ); - - Ok(()) - }); - } - - // crate not found - #[test_case("bar", "0.1")] - #[test_case("bar", "0.1.0")] - // version not found - #[test_case("foo", "=0.1.0"; "exact_version")] - #[test_case("foo", "0.2")] - #[test_case("foo", "0.2.0")] - // invalid semver - #[test_case("foo", "0,1")] - #[test_case("foo", "0,1,0")] - fn not_found(krate: &str, req_version: &str) { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("foo") - .version("0.1.1") - .create() - .await?; - - let response = env - .web_app() - .await - .get_and_follow_redirects(&format!("/crate/{krate}/{req_version}/status.json")) - .await?; - response.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); - assert_eq!(response.headers()["access-control-allow-origin"], "*"); - assert_eq!(response.status(), StatusCode::NOT_FOUND); - Ok(()) - }); - } -} +// #[cfg(test)] +// mod tests { +// use crate::{ +// test::{AxumResponseTestExt, AxumRouterTestExt, async_wrapper}, +// web::{ReqVersion, cache::CachePolicy}, +// }; +// use reqwest::StatusCode; +// use test_case::test_case; + +// #[test_case("latest")] +// #[test_case("0.1")] +// #[test_case("0.1.0")] +// #[test_case("=0.1.0"; "exact_version")] +// fn status(req_version: &str) { +// async_wrapper(|env| async move { +// let req_version: ReqVersion = req_version.parse()?; + +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .create() +// .await?; + +// let response = env +// .web_app() +// .await +// .get_and_follow_redirects(&format!("/crate/foo/{req_version}/status.json")) +// .await?; +// response.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); +// assert_eq!(response.headers()["access-control-allow-origin"], "*"); +// assert_eq!(response.status(), StatusCode::OK); +// let value: serde_json::Value = serde_json::from_str(&response.text().await?)?; + +// assert_eq!( +// value, +// serde_json::json!({ +// "version": "0.1.0", +// "doc_status": true, +// }) +// ); + +// Ok(()) +// }); +// } + +// #[test] +// fn redirect_latest() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .create() +// .await?; + +// let web = env.web_app().await; +// let redirect = web +// .assert_redirect("/crate/foo/*/status.json", "/crate/foo/latest/status.json") +// .await?; +// redirect.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); +// assert_eq!(redirect.headers()["access-control-allow-origin"], "*"); + +// Ok(()) +// }); +// } + +// #[test_case("0.1")] +// #[test_case("~0.1"; "semver")] +// fn redirect(req_version: &str) { +// async_wrapper(|env| async move { +// let req_version: ReqVersion = req_version.parse()?; + +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .create() +// .await?; + +// let web = env.web_app().await; +// let redirect = web +// .assert_redirect( +// &format!("/crate/foo/{req_version}/status.json"), +// "/crate/foo/0.1.0/status.json", +// ) +// .await?; +// redirect.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); +// assert_eq!(redirect.headers()["access-control-allow-origin"], "*"); + +// Ok(()) +// }); +// } + +// #[test_case("latest")] +// #[test_case("0.1")] +// #[test_case("0.1.0")] +// #[test_case("=0.1.0"; "exact_version")] +// fn failure(req_version: &str) { +// async_wrapper(|env| async move { +// let req_version: ReqVersion = req_version.parse()?; + +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.0") +// .build_result_failed() +// .create() +// .await?; + +// let response = env +// .web_app() +// .await +// .get_and_follow_redirects(&format!("/crate/foo/{req_version}/status.json")) +// .await?; +// response.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); +// assert_eq!(response.headers()["access-control-allow-origin"], "*"); +// assert_eq!(response.status(), StatusCode::OK); +// let value: serde_json::Value = serde_json::from_str(&response.text().await?)?; + +// assert_eq!( +// value, +// serde_json::json!({ +// "version": "0.1.0", +// "doc_status": false, +// }) +// ); + +// Ok(()) +// }); +// } + +// // crate not found +// #[test_case("bar", "0.1")] +// #[test_case("bar", "0.1.0")] +// // version not found +// #[test_case("foo", "=0.1.0"; "exact_version")] +// #[test_case("foo", "0.2")] +// #[test_case("foo", "0.2.0")] +// // invalid semver +// #[test_case("foo", "0,1")] +// #[test_case("foo", "0,1,0")] +// fn not_found(krate: &str, req_version: &str) { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("foo") +// .version("0.1.1") +// .create() +// .await?; + +// let response = env +// .web_app() +// .await +// .get_and_follow_redirects(&format!("/crate/{krate}/{req_version}/status.json")) +// .await?; +// response.assert_cache_control(CachePolicy::NoStoreMustRevalidate, env.config()); +// assert_eq!(response.headers()["access-control-allow-origin"], "*"); +// assert_eq!(response.status(), StatusCode::NOT_FOUND); +// Ok(()) +// }); +// } +// } diff --git a/src/utils/html.rs b/crates/docs_rs_web/src/utils/html.rs similarity index 79% rename from src/utils/html.rs rename to crates/docs_rs_web/src/utils/html.rs index 891655270..0b05808d6 100644 --- a/src/utils/html.rs +++ b/crates/docs_rs_web/src/utils/html.rs @@ -1,13 +1,10 @@ use crate::{ - utils::report_error, - web::{ - metrics::WebMetrics, - page::{ - TemplateData, - templates::{Body, Head, Vendored}, - }, - rustdoc::RustdocPage, + metrics::WebMetrics, + page::{ + TemplateData, + templates::{Body, Head, Vendored}, }, + rustdoc::RustdocPage, }; use anyhow::{Context as _, anyhow}; use askama::Template; @@ -235,58 +232,58 @@ where .instrument(stream_span) } -#[cfg(test)] -mod test { - use crate::test::{AxumResponseTestExt, AxumRouterTestExt, V1, async_wrapper}; +// #[cfg(test)] +// mod test { +// use crate::test::{AxumResponseTestExt, AxumRouterTestExt, V1, async_wrapper}; - #[test] - fn rewriting_only_injects_css_once() { - async_wrapper(|env| async move { - env.fake_release().await - .name("testing") - .version(V1) - // A somewhat representative rustdoc html file from 2016 - .rustdoc_file_with("2016/index.html", br#" - - - - - - - - "#) - // A somewhat representative rustdoc html file from late 2022 - .rustdoc_file_with("2022/index.html", br#" - - - - - - - - - - - - "#) - .create().await?; +// #[test] +// fn rewriting_only_injects_css_once() { +// async_wrapper(|env| async move { +// env.fake_release().await +// .name("testing") +// .version(V1) +// // A somewhat representative rustdoc html file from 2016 +// .rustdoc_file_with("2016/index.html", br#" +// +// +// +// +// +// +// +// "#) +// // A somewhat representative rustdoc html file from late 2022 +// .rustdoc_file_with("2022/index.html", br#" +// +// +// +// +// +// +// +// +// +// +// +// "#) +// .create().await?; - let web = env.web_app().await; - let output = web - .get(&format!("/testing/{V1}/2016/")) - .await? - .text() - .await?; - assert_eq!(output.matches(r#"href="/-/static/vendored.css"#).count(), 1); +// let web = env.web_app().await; +// let output = web +// .get(&format!("/testing/{V1}/2016/")) +// .await? +// .text() +// .await?; +// assert_eq!(output.matches(r#"href="/-/static/vendored.css"#).count(), 1); - let output = web - .get(&format!("/testing/{V1}/2022/")) - .await? - .text() - .await?; - assert_eq!(output.matches(r#"href="/-/static/vendored.css"#).count(), 1); +// let output = web +// .get(&format!("/testing/{V1}/2022/")) +// .await? +// .text() +// .await?; +// assert_eq!(output.matches(r#"href="/-/static/vendored.css"#).count(), 1); - Ok(()) - }); - } -} +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_web/src/utils/mod.rs b/crates/docs_rs_web/src/utils/mod.rs new file mode 100644 index 000000000..96680ef2d --- /dev/null +++ b/crates/docs_rs_web/src/utils/mod.rs @@ -0,0 +1,2 @@ +pub(crate) mod html; +pub(crate) mod rustc_version; diff --git a/src/utils/rustc_version.rs b/crates/docs_rs_web/src/utils/rustc_version.rs similarity index 100% rename from src/utils/rustc_version.rs rename to crates/docs_rs_web/src/utils/rustc_version.rs diff --git a/crates/docs_rs_web/templates/crate/details.html b/crates/docs_rs_web/templates/crate/details.html index 1efbdf5e8..0554d0439 100644 --- a/crates/docs_rs_web/templates/crate/details.html +++ b/crates/docs_rs_web/templates/crate/details.html @@ -193,11 +193,11 @@ {# If there's a readme, display it #} {%- if let Some(readme) = readme -%} - {{ crate::web::markdown::render(readme)|safe }} + {{ crate::markdown::render(readme)|safe }} {# If there's not a readme then attempt to display the long description #} {%- elif let Some(rustdoc) = rustdoc -%} - {{ crate::web::markdown::render_with_default(rustdoc, "rust")|safe }} + {{ crate::markdown::render_with_default(rustdoc, "rust")|safe }} {%- endif -%} diff --git a/crates/docs_rs_web/templates/rustdoc/topbar.html b/crates/docs_rs_web/templates/rustdoc/topbar.html index ef1371764..d64737ac8 100644 --- a/crates/docs_rs_web/templates/rustdoc/topbar.html +++ b/crates/docs_rs_web/templates/rustdoc/topbar.html @@ -47,11 +47,11 @@ {{- crate::icons::IconScaleUnbalancedFlip.render_solid(false, false, "") }} {%+ for item in parsed_licenses -%} {%- match item -%} - {%- when crate::web::licenses::LicenseSegment::Spdx(license) -%} + {%- when crate::licenses::LicenseSegment::Spdx(license) -%} {{ license }} - {%- when crate::web::licenses::LicenseSegment::UnknownLicense(license) -%} + {%- when crate::licenses::LicenseSegment::UnknownLicense(license) -%} {{ license }} - {%- when crate::web::licenses::LicenseSegment::GlueTokens(tokens) -%} + {%- when crate::licenses::LicenseSegment::GlueTokens(tokens) -%} {{ tokens }} {%- endmatch -%} {%- endfor -%} diff --git a/crates/docs_rs_web_utils/src/escaped_uri.rs b/crates/docs_rs_web_utils/src/escaped_uri.rs index 273344397..7ac0aab2b 100644 --- a/crates/docs_rs_web_utils/src/escaped_uri.rs +++ b/crates/docs_rs_web_utils/src/escaped_uri.rs @@ -295,211 +295,211 @@ impl PartialEq for EscapedURI { } } -#[cfg(test)] -mod tests { - use super::EscapedURI; - use crate::web::{cache::CachePolicy, error::AxumNope}; - use axum::response::IntoResponse as _; - use http::Uri; - use test_case::test_case; - - fn test_serialization_roundtrip(input: &EscapedURI) { - let s = input.to_string(); - assert_eq!(input, s); // tests the ParialEq impl - assert_eq!(s.parse::().unwrap(), *input); - } - - #[test] - fn test_redirect_error_encodes_url_path() { - let response = AxumNope::Redirect( - EscapedURI::from_path("/something>"), - CachePolicy::ForeverInCdnAndBrowser, - ) - .into_response(); - - assert_eq!(response.status(), 302); - assert_eq!(response.headers().get("Location").unwrap(), "/something%3E"); - } - - #[test_case("/something" => "/something")] - #[test_case("/something>" => "/something%3E")] - fn test_escaped_uri_encodes_from_path(input: &str) -> String { - let escaped = EscapedURI::from_path(input); - test_serialization_roundtrip(&escaped); - escaped.path().to_owned() - } - - #[test_case("/something" => "/something"; "plain path")] - #[test_case("/semver/%5E1.2.3" => "/semver/^1.2.3"; "we encode less")] - #[test_case("/somethingäöü" => "/something%C3%A4%C3%B6%C3%BC"; "path with umlauts")] - fn test_escaped_uri_encodes_path_from_uri(path: &str) -> String { - let uri: Uri = path.parse().unwrap(); - let escaped = EscapedURI::from_uri(uri); - test_serialization_roundtrip(&escaped); - escaped.path().to_string() - } - - #[test] - fn test_escaped_uri_from_uri_with_query_args() { - let uri: Uri = "/something?key=value&foo=bar".parse().unwrap(); - let escaped = EscapedURI::from_uri(uri); - test_serialization_roundtrip(&escaped); - assert_eq!(escaped.path(), "/something"); - assert_eq!(escaped.query(), Some("key=value&foo=bar")); - } - - #[test] - fn test_escaped_uri_from_uri_with_query_args_and_fragment() { - let input = "/something?key=value&foo=bar#frag"; - let escaped: EscapedURI = input.parse().unwrap(); - test_serialization_roundtrip(&escaped); - assert_eq!(escaped.path(), "/something"); - assert_eq!(escaped.query(), Some("key=value&foo=bar")); - assert_eq!(escaped.fragment(), Some("frag")); - assert_eq!(escaped.to_string(), input); - } - - #[test] - fn test_escaped_uri_from_uri_with_query_args_and_fragment_to_encode() { - let input = "/something?key=value&foo=bar#fräöag"; - let escaped: EscapedURI = input.parse().unwrap(); - test_serialization_roundtrip(&escaped); - assert_eq!(escaped.path(), "/something"); - assert_eq!(escaped.query(), Some("key=value&foo=bar")); - assert_eq!(escaped.fragment(), Some("fr%C3%A4%C3%B6ag")); - assert_eq!( - escaped.to_string(), - "/something?key=value&foo=bar#fr%C3%A4%C3%B6ag" - ); - } - - #[test_case("/something>")] - #[test_case("/something?key=().is_err()); - } - - #[test_case( - "/something", "key=value&foo=bar" - => ("/something".into(), "key=value&foo=bar".into()); - "plain convert" - )] - #[test_case( - "/something", "value=foo\rbar&key= ("/something".into(), "value=foo%0Dbar&key=%3Cvalue".into()); - "invalid query gets re-encoded without error" - )] - fn test_escaped_uri_from_raw_query(path: &str, query: &str) -> (String, String) { - let uri = EscapedURI::from_path_and_raw_query(path, Some(query)); - test_serialization_roundtrip(&uri); - - (uri.path().to_owned(), uri.query().unwrap().to_owned()) - } - - #[test] - fn test_escaped_uri_from_query() { - let uri = - EscapedURI::from_path_and_query("/something", &[("key", "value"), ("foo", "bar")]); - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), Some("key=value&foo=bar")); - } - - #[test] - fn test_escaped_uri_from_query_with_chars_to_encode() { - let uri = - EscapedURI::from_path_and_query("/something", &[("key", "value>"), ("foo", "\rbar")]); - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), Some("key=value%3E&foo=%0Dbar")); - } - - #[test] - fn test_escaped_uri_append_query_pairs_without_path() { - let uri = Uri::builder().build().unwrap(); - - let parts = uri.into_parts(); - // `append_query_pairs` has a special case when path_and_query is `None`, - // which I want to test here. - assert!(parts.path_and_query.is_none()); - - // also tests appending query pairs if there are no existing query args - let uri = EscapedURI::from_uri(Uri::from_parts(parts).unwrap()) - .append_query_pairs(&[("foo", "bar"), ("bar", "baz")]); - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/"); - assert_eq!(uri.query(), Some("foo=bar&bar=baz")); - } - - #[test] - fn test_escaped_uri_append_query_pairs() { - let uri = EscapedURI::from_path_and_query("/something", &[("key", "value")]) - .append_query_pairs(&[("foo", "bar"), ("bar", "baz")]) - .append_query_pair("last", "one"); - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), Some("key=value&foo=bar&bar=baz&last=one")); - } - - #[test] - fn test_escaped_uri_append_fragment() { - let uri = EscapedURI::from_path("/something").with_fragment("some-fragment"); - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), None); - assert_eq!(uri.fragment(), Some("some-fragment")); - assert_eq!(uri.to_string(), "/something#some-fragment"); - } - - #[test] - fn test_escaped_uri_append_fragment_encode() { - let uri = EscapedURI::from_path("/something").with_fragment("some-äö-fragment"); - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), None); - assert_eq!(uri.fragment(), Some("some-%C3%A4%C3%B6-fragment")); - assert_eq!(uri.to_string(), "/something#some-%C3%A4%C3%B6-fragment"); - } - - #[test] - fn test_escaped_uri_replace_fragment() { - let uri = EscapedURI::from_path("/something") - .with_fragment("some-fragment") - .with_fragment("other-fragment"); - - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), None); - assert_eq!(uri.fragment(), Some("other-fragment")); - assert_eq!(uri.to_string(), "/something#other-fragment"); - } - - #[test] - fn test_comparision() { - let uri = EscapedURI::from_path("/something").with_fragment("other-fragment"); - - test_serialization_roundtrip(&uri); - - assert_eq!(uri.path(), "/something"); - assert_eq!(uri.query(), None); - assert_eq!(uri.fragment(), Some("other-fragment")); - assert_eq!(uri.to_string(), "/something#other-fragment"); - } - - #[test] - fn test_not_eq() { - let uri = EscapedURI::from_path("/something").with_fragment("other-fragment"); - assert_ne!(uri, "/something"); - } -} +// #[cfg(test)] +// mod tests { +// use super::EscapedURI; +// use crate::web::{cache::CachePolicy, error::AxumNope}; +// use axum::response::IntoResponse as _; +// use http::Uri; +// use test_case::test_case; + +// fn test_serialization_roundtrip(input: &EscapedURI) { +// let s = input.to_string(); +// assert_eq!(input, s); // tests the ParialEq impl +// assert_eq!(s.parse::().unwrap(), *input); +// } + +// #[test] +// fn test_redirect_error_encodes_url_path() { +// let response = AxumNope::Redirect( +// EscapedURI::from_path("/something>"), +// CachePolicy::ForeverInCdnAndBrowser, +// ) +// .into_response(); + +// assert_eq!(response.status(), 302); +// assert_eq!(response.headers().get("Location").unwrap(), "/something%3E"); +// } + +// #[test_case("/something" => "/something")] +// #[test_case("/something>" => "/something%3E")] +// fn test_escaped_uri_encodes_from_path(input: &str) -> String { +// let escaped = EscapedURI::from_path(input); +// test_serialization_roundtrip(&escaped); +// escaped.path().to_owned() +// } + +// #[test_case("/something" => "/something"; "plain path")] +// #[test_case("/semver/%5E1.2.3" => "/semver/^1.2.3"; "we encode less")] +// #[test_case("/somethingäöü" => "/something%C3%A4%C3%B6%C3%BC"; "path with umlauts")] +// fn test_escaped_uri_encodes_path_from_uri(path: &str) -> String { +// let uri: Uri = path.parse().unwrap(); +// let escaped = EscapedURI::from_uri(uri); +// test_serialization_roundtrip(&escaped); +// escaped.path().to_string() +// } + +// #[test] +// fn test_escaped_uri_from_uri_with_query_args() { +// let uri: Uri = "/something?key=value&foo=bar".parse().unwrap(); +// let escaped = EscapedURI::from_uri(uri); +// test_serialization_roundtrip(&escaped); +// assert_eq!(escaped.path(), "/something"); +// assert_eq!(escaped.query(), Some("key=value&foo=bar")); +// } + +// #[test] +// fn test_escaped_uri_from_uri_with_query_args_and_fragment() { +// let input = "/something?key=value&foo=bar#frag"; +// let escaped: EscapedURI = input.parse().unwrap(); +// test_serialization_roundtrip(&escaped); +// assert_eq!(escaped.path(), "/something"); +// assert_eq!(escaped.query(), Some("key=value&foo=bar")); +// assert_eq!(escaped.fragment(), Some("frag")); +// assert_eq!(escaped.to_string(), input); +// } + +// #[test] +// fn test_escaped_uri_from_uri_with_query_args_and_fragment_to_encode() { +// let input = "/something?key=value&foo=bar#fräöag"; +// let escaped: EscapedURI = input.parse().unwrap(); +// test_serialization_roundtrip(&escaped); +// assert_eq!(escaped.path(), "/something"); +// assert_eq!(escaped.query(), Some("key=value&foo=bar")); +// assert_eq!(escaped.fragment(), Some("fr%C3%A4%C3%B6ag")); +// assert_eq!( +// escaped.to_string(), +// "/something?key=value&foo=bar#fr%C3%A4%C3%B6ag" +// ); +// } + +// #[test_case("/something>")] +// #[test_case("/something?key=().is_err()); +// } + +// #[test_case( +// "/something", "key=value&foo=bar" +// => ("/something".into(), "key=value&foo=bar".into()); +// "plain convert" +// )] +// #[test_case( +// "/something", "value=foo\rbar&key= ("/something".into(), "value=foo%0Dbar&key=%3Cvalue".into()); +// "invalid query gets re-encoded without error" +// )] +// fn test_escaped_uri_from_raw_query(path: &str, query: &str) -> (String, String) { +// let uri = EscapedURI::from_path_and_raw_query(path, Some(query)); +// test_serialization_roundtrip(&uri); + +// (uri.path().to_owned(), uri.query().unwrap().to_owned()) +// } + +// #[test] +// fn test_escaped_uri_from_query() { +// let uri = +// EscapedURI::from_path_and_query("/something", &[("key", "value"), ("foo", "bar")]); +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), Some("key=value&foo=bar")); +// } + +// #[test] +// fn test_escaped_uri_from_query_with_chars_to_encode() { +// let uri = +// EscapedURI::from_path_and_query("/something", &[("key", "value>"), ("foo", "\rbar")]); +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), Some("key=value%3E&foo=%0Dbar")); +// } + +// #[test] +// fn test_escaped_uri_append_query_pairs_without_path() { +// let uri = Uri::builder().build().unwrap(); + +// let parts = uri.into_parts(); +// // `append_query_pairs` has a special case when path_and_query is `None`, +// // which I want to test here. +// assert!(parts.path_and_query.is_none()); + +// // also tests appending query pairs if there are no existing query args +// let uri = EscapedURI::from_uri(Uri::from_parts(parts).unwrap()) +// .append_query_pairs(&[("foo", "bar"), ("bar", "baz")]); +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/"); +// assert_eq!(uri.query(), Some("foo=bar&bar=baz")); +// } + +// #[test] +// fn test_escaped_uri_append_query_pairs() { +// let uri = EscapedURI::from_path_and_query("/something", &[("key", "value")]) +// .append_query_pairs(&[("foo", "bar"), ("bar", "baz")]) +// .append_query_pair("last", "one"); +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), Some("key=value&foo=bar&bar=baz&last=one")); +// } + +// #[test] +// fn test_escaped_uri_append_fragment() { +// let uri = EscapedURI::from_path("/something").with_fragment("some-fragment"); +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), None); +// assert_eq!(uri.fragment(), Some("some-fragment")); +// assert_eq!(uri.to_string(), "/something#some-fragment"); +// } + +// #[test] +// fn test_escaped_uri_append_fragment_encode() { +// let uri = EscapedURI::from_path("/something").with_fragment("some-äö-fragment"); +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), None); +// assert_eq!(uri.fragment(), Some("some-%C3%A4%C3%B6-fragment")); +// assert_eq!(uri.to_string(), "/something#some-%C3%A4%C3%B6-fragment"); +// } + +// #[test] +// fn test_escaped_uri_replace_fragment() { +// let uri = EscapedURI::from_path("/something") +// .with_fragment("some-fragment") +// .with_fragment("other-fragment"); + +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), None); +// assert_eq!(uri.fragment(), Some("other-fragment")); +// assert_eq!(uri.to_string(), "/something#other-fragment"); +// } + +// #[test] +// fn test_comparision() { +// let uri = EscapedURI::from_path("/something").with_fragment("other-fragment"); + +// test_serialization_roundtrip(&uri); + +// assert_eq!(uri.path(), "/something"); +// assert_eq!(uri.query(), None); +// assert_eq!(uri.fragment(), Some("other-fragment")); +// assert_eq!(uri.to_string(), "/something#other-fragment"); +// } + +// #[test] +// fn test_not_eq() { +// let uri = EscapedURI::from_path("/something").with_fragment("other-fragment"); +// assert_ne!(uri, "/something"); +// } +// } diff --git a/src/config.rs b/src/config.rs index ab88c4008..1187d133a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,123 +1,123 @@ -use anyhow::{Result, bail}; -use docs_rs_env_vars::{env, maybe_env, require_env}; -use std::{path::PathBuf, sync::Arc, time::Duration}; -use url::Url; - -#[derive(Debug)] -pub struct Config { - pub prefix: PathBuf, - - pub database: Arc, - pub watcher: Arc, - pub build_queue: Arc, - pub storage: Arc, - - // Access token for APIs for crates.io (careful: use - // constant_time_eq for comparisons!) - pub(crate) cratesio_token: Option, - - // amount of retries for external API calls, mostly crates.io - pub crates_io_api_call_retries: u32, - - // request timeout in seconds - pub(crate) request_timeout: Option, - pub(crate) report_request_timeouts: bool, - - // The most memory that can be used to parse an HTML file - pub(crate) max_parse_memory: usize, - - /// amount of threads for CPU intensive rendering - pub(crate) render_threads: usize, - - // random crate search generates a number of random IDs to - // efficiently find a random crate with > 100 GH stars. - // The amount depends on the ratio of crates with >100 stars - // to the count of all crates. - // At the time of creating this setting, it is set to - // `500` for a ratio of 7249 over 54k crates. - // For unit-tests the number has to be higher. - pub(crate) random_crate_search_view_size: u32, - - // Where to collect metrics for the metrics initiative. - // When empty, we won't collect metrics. - pub(crate) compiler_metrics_collection_path: Option, - - // Content Security Policy - pub(crate) csp_report_only: bool, - - // Cache-Control header, for versioned URLs. - // If both are absent, don't generate the header. If only one is present, - // generate just that directive. Values are in seconds. - pub(crate) cache_control_stale_while_revalidate: Option, - - // Activate full page caching. - // When disabled, we still cache static assets. - // This only affects pages that depend on invalidations to work. - pub(crate) cache_invalidatable_responses: bool, - - pub(crate) build_workspace_reinitialization_interval: Duration, - - // Build params - pub(crate) build_attempts: u16, - pub(crate) delay_between_build_attempts: Duration, - pub(crate) rustwide_workspace: PathBuf, - pub(crate) temp_dir: PathBuf, - pub(crate) inside_docker: bool, - pub(crate) docker_image: Option, - pub(crate) build_cpu_limit: Option, - pub(crate) build_default_memory_limit: Option, - pub(crate) include_default_targets: bool, - pub(crate) disable_memory_limit: bool, - - pub(crate) opentelemetry: docs_rs_opentelemetry::Config, -} - -impl Config { - pub fn from_env() -> Result { - let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; - let temp_dir = prefix.join("tmp"); - - Ok(Self { - prefix, - database: docs_rs_database::Config::from_environment()?.into(), - watcher: docs_rs_watcher::Config::from_environment()?.into(), - build_queue: docs_rs_build_queue::Config::from_environment()?.into(), - storage: docs_rs_storage::Config::from_environment()?.into(), - opentelemetry: docs_rs_opentelemetry::Config::from_environment()?.into(), - - build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, - delay_between_build_attempts: Duration::from_secs(env::( - "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", - 60, - )?), - crates_io_api_call_retries: env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?, - cratesio_token: maybe_env("DOCSRS_CRATESIO_TOKEN")?, - // LOL HTML only uses as much memory as the size of the start tag! - // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 - max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?, - render_threads: env("DOCSRS_RENDER_THREADS", num_cpus::get())?, - request_timeout: maybe_env::("DOCSRS_REQUEST_TIMEOUT")?.map(Duration::from_secs), - report_request_timeouts: env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?, - random_crate_search_view_size: env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?, - csp_report_only: env("DOCSRS_CSP_REPORT_ONLY", false)?, - cache_control_stale_while_revalidate: maybe_env( - "CACHE_CONTROL_STALE_WHILE_REVALIDATE", - )?, - cache_invalidatable_responses: env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?, - compiler_metrics_collection_path: maybe_env("DOCSRS_COMPILER_METRICS_PATH")?, - temp_dir: temp_dir, - rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, - inside_docker: env("DOCSRS_DOCKER", false)?, - docker_image: maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")? - .or(maybe_env("DOCSRS_DOCKER_IMAGE")?), - build_cpu_limit: maybe_env("DOCSRS_BUILD_CPU_LIMIT")?, - build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, - include_default_targets: env("DOCSRS_INCLUDE_DEFAULT_TARGETS", true)?, - disable_memory_limit: env("DOCSRS_DISABLE_MEMORY_LIMIT", false)?, - build_workspace_reinitialization_interval: Duration::from_secs(env( - "DOCSRS_BUILD_WORKSPACE_REINITIALIZATION_INTERVAL", - 86400, - )?), - }) - } -} +// use anyhow::{Result, bail}; +// use docs_rs_env_vars::{env, maybe_env, require_env}; +// use std::{path::PathBuf, sync::Arc, time::Duration}; +// use url::Url; + +// #[derive(Debug)] +// pub struct Config { +// pub prefix: PathBuf, + +// pub database: Arc, +// pub watcher: Arc, +// pub build_queue: Arc, +// pub storage: Arc, + +// // Access token for APIs for crates.io (careful: use +// // constant_time_eq for comparisons!) +// pub(crate) cratesio_token: Option, + +// // amount of retries for external API calls, mostly crates.io +// pub crates_io_api_call_retries: u32, + +// // request timeout in seconds +// pub(crate) request_timeout: Option, +// pub(crate) report_request_timeouts: bool, + +// // The most memory that can be used to parse an HTML file +// pub(crate) max_parse_memory: usize, + +// /// amount of threads for CPU intensive rendering +// pub(crate) render_threads: usize, + +// // random crate search generates a number of random IDs to +// // efficiently find a random crate with > 100 GH stars. +// // The amount depends on the ratio of crates with >100 stars +// // to the count of all crates. +// // At the time of creating this setting, it is set to +// // `500` for a ratio of 7249 over 54k crates. +// // For unit-tests the number has to be higher. +// pub(crate) random_crate_search_view_size: u32, + +// // Where to collect metrics for the metrics initiative. +// // When empty, we won't collect metrics. +// pub(crate) compiler_metrics_collection_path: Option, + +// // Content Security Policy +// pub(crate) csp_report_only: bool, + +// // Cache-Control header, for versioned URLs. +// // If both are absent, don't generate the header. If only one is present, +// // generate just that directive. Values are in seconds. +// pub(crate) cache_control_stale_while_revalidate: Option, + +// // Activate full page caching. +// // When disabled, we still cache static assets. +// // This only affects pages that depend on invalidations to work. +// pub(crate) cache_invalidatable_responses: bool, + +// pub(crate) build_workspace_reinitialization_interval: Duration, + +// // Build params +// pub(crate) build_attempts: u16, +// pub(crate) delay_between_build_attempts: Duration, +// pub(crate) rustwide_workspace: PathBuf, +// pub(crate) temp_dir: PathBuf, +// pub(crate) inside_docker: bool, +// pub(crate) docker_image: Option, +// pub(crate) build_cpu_limit: Option, +// pub(crate) build_default_memory_limit: Option, +// pub(crate) include_default_targets: bool, +// pub(crate) disable_memory_limit: bool, + +// pub(crate) opentelemetry: docs_rs_opentelemetry::Config, +// } + +// impl Config { +// pub fn from_env() -> Result { +// let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; +// let temp_dir = prefix.join("tmp"); + +// Ok(Self { +// prefix, +// database: docs_rs_database::Config::from_environment()?.into(), +// watcher: docs_rs_watcher::Config::from_environment()?.into(), +// build_queue: docs_rs_build_queue::Config::from_environment()?.into(), +// storage: docs_rs_storage::Config::from_environment()?.into(), +// opentelemetry: docs_rs_opentelemetry::Config::from_environment()?.into(), + +// build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, +// delay_between_build_attempts: Duration::from_secs(env::( +// "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", +// 60, +// )?), +// crates_io_api_call_retries: env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?, +// cratesio_token: maybe_env("DOCSRS_CRATESIO_TOKEN")?, +// // LOL HTML only uses as much memory as the size of the start tag! +// // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 +// max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?, +// render_threads: env("DOCSRS_RENDER_THREADS", num_cpus::get())?, +// request_timeout: maybe_env::("DOCSRS_REQUEST_TIMEOUT")?.map(Duration::from_secs), +// report_request_timeouts: env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?, +// random_crate_search_view_size: env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?, +// csp_report_only: env("DOCSRS_CSP_REPORT_ONLY", false)?, +// cache_control_stale_while_revalidate: maybe_env( +// "CACHE_CONTROL_STALE_WHILE_REVALIDATE", +// )?, +// cache_invalidatable_responses: env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?, +// compiler_metrics_collection_path: maybe_env("DOCSRS_COMPILER_METRICS_PATH")?, +// temp_dir: temp_dir, +// rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, +// inside_docker: env("DOCSRS_DOCKER", false)?, +// docker_image: maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")? +// .or(maybe_env("DOCSRS_DOCKER_IMAGE")?), +// build_cpu_limit: maybe_env("DOCSRS_BUILD_CPU_LIMIT")?, +// build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, +// include_default_targets: env("DOCSRS_INCLUDE_DEFAULT_TARGETS", true)?, +// disable_memory_limit: env("DOCSRS_DISABLE_MEMORY_LIMIT", false)?, +// build_workspace_reinitialization_interval: Duration::from_secs(env( +// "DOCSRS_BUILD_WORKSPACE_REINITIALIZATION_INTERVAL", +// 86400, +// )?), +// }) +// } +// } diff --git a/src/lib.rs b/src/lib.rs index 220e79279..b467fc4c1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,26 +19,3 @@ pub mod metrics; mod test; pub mod utils; mod web; - -use web::page::GlobalAlert; - -// Warning message shown in the navigation bar of every page. Set to `None` to hide it. -pub(crate) static GLOBAL_ALERT: Option = None; -/* -pub(crate) static GLOBAL_ALERT: Option = Some(GlobalAlert { - url: "https://blog.rust-lang.org/2019/09/18/upcoming-docsrs-changes.html", - text: "Upcoming docs.rs breaking changes!", - css_class: "error", - fa_icon: "exclamation-triangle", -}); -*/ - -/// Where rustdoc's static files are stored in S3. -/// Since the prefix starts with `/`, it needs to be referenced with a double slash in -/// API & AWS CLI. -/// Example: -/// `s3://rust-docs-rs//rustdoc-static/something.css` -pub const RUSTDOC_STATIC_STORAGE_PREFIX: &str = "/rustdoc-static/"; - -/// Maximum number of targets allowed for a crate to be documented on. -pub const DEFAULT_MAX_TARGETS: usize = 10; diff --git a/src/test/mod.rs b/src/test/mod.rs index 5b2130daa..0c52e654b 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -25,7 +25,6 @@ use anyhow::{Context as _, anyhow}; use axum::body::Bytes; use axum::{Router, body::Body, http::Request, response::Response as AxumResponse}; use axum_extra::headers::{ETag, HeaderMapExt as _}; -use fn_error_context::context; use futures_util::stream::TryStreamExt; use http::{ HeaderMap, HeaderName, HeaderValue, StatusCode, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index aba76d5ec..72669d995 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -15,7 +15,6 @@ pub use self::{ }; mod copy; -pub mod daemon; mod html; pub(crate) mod queue_builder; pub(crate) mod rustc_version; From c5e211390a5c5abf6974201d5ccb83d18670eb17 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 08:27:39 +0100 Subject: [PATCH 20/46] IPW --- Cargo.lock | 23 ++ crates/docs_rs_build_utils/Cargo.toml | 15 ++ crates/docs_rs_build_utils/src/config.rs | 14 ++ crates/docs_rs_build_utils/src/lib.rs | 3 + crates/docs_rs_build_utils/src/limits.rs | 202 ++++++++++++++++++ .../docs_rs_build_utils/src}/overrides.rs | 7 +- crates/docs_rs_builder/Cargo.toml | 8 + crates/docs_rs_builder/src/config.rs | 10 + crates/docs_rs_builder/src/lib.rs | 3 + crates/docs_rs_web/Cargo.toml | 1 + crates/docs_rs_web/src/builds.rs | 2 +- src/docbuilder/limits.rs | 198 ----------------- 12 files changed, 284 insertions(+), 202 deletions(-) create mode 100644 crates/docs_rs_build_utils/Cargo.toml create mode 100644 crates/docs_rs_build_utils/src/config.rs create mode 100644 crates/docs_rs_build_utils/src/lib.rs create mode 100644 crates/docs_rs_build_utils/src/limits.rs rename {src/db => crates/docs_rs_build_utils/src}/overrides.rs (97%) create mode 100644 crates/docs_rs_builder/Cargo.toml create mode 100644 crates/docs_rs_builder/src/config.rs create mode 100644 crates/docs_rs_builder/src/lib.rs delete mode 100644 src/docbuilder/limits.rs diff --git a/Cargo.lock b/Cargo.lock index 13d67251d..356be8d9f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1960,6 +1960,28 @@ dependencies = [ "tracing", ] +[[package]] +name = "docs_rs_build_utils" +version = "0.1.0" +dependencies = [ + "anyhow", + "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_utils", + "futures-util", + "serde", + "sqlx", + "tracing", +] + +[[package]] +name = "docs_rs_builder" +version = "0.1.0" +dependencies = [ + "docs_rs_database", + "serde", +] + [[package]] name = "docs_rs_cargo_metadata" version = "0.1.0" @@ -2193,6 +2215,7 @@ dependencies = [ "constant_time_eq", "derive_more 2.0.1", "docs_rs_build_queue", + "docs_rs_build_utils", "docs_rs_cargo_metadata", "docs_rs_database", "docs_rs_env_vars", diff --git a/crates/docs_rs_build_utils/Cargo.toml b/crates/docs_rs_build_utils/Cargo.toml new file mode 100644 index 000000000..e8f1e0b9d --- /dev/null +++ b/crates/docs_rs_build_utils/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "docs_rs_build_utils" +version = "0.1.0" +edition = "2024" + +[dependencies] +docs_rs_database = { path = "../docs_rs_database" } +serde = { workspace = true} +docs_rs_utils = { path = "../docs_rs_utils" } +sqlx = { workspace = true } +anyhow = { workspace = true } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +futures-util = { workspace = true } +tracing = { workspace = true } + diff --git a/crates/docs_rs_build_utils/src/config.rs b/crates/docs_rs_build_utils/src/config.rs new file mode 100644 index 000000000..47ecd4446 --- /dev/null +++ b/crates/docs_rs_build_utils/src/config.rs @@ -0,0 +1,14 @@ +use docs_rs_env_vars::{env, maybe_env}; + +#[derive(Debug)] +pub struct Config { + pub(crate) build_default_memory_limit: Option, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, + }) + } +} diff --git a/crates/docs_rs_build_utils/src/lib.rs b/crates/docs_rs_build_utils/src/lib.rs new file mode 100644 index 000000000..e94129a29 --- /dev/null +++ b/crates/docs_rs_build_utils/src/lib.rs @@ -0,0 +1,3 @@ +pub(crate) mod config; +pub mod limits; +pub mod overrides; diff --git a/crates/docs_rs_build_utils/src/limits.rs b/crates/docs_rs_build_utils/src/limits.rs new file mode 100644 index 000000000..48d6a48cb --- /dev/null +++ b/crates/docs_rs_build_utils/src/limits.rs @@ -0,0 +1,202 @@ +use crate::{ + config::Config, + overrides::{self, Overrides}, +}; +use anyhow::Result; +use serde::Serialize; +use std::time::Duration; + +const GB: usize = 1024 * 1024 * 1024; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Limits { + pub memory: usize, + pub targets: usize, + pub timeout: Duration, + pub networking: bool, + pub max_log_size: usize, +} + +impl Limits { + pub fn new(config: &Config) -> Self { + Self { + // 3 GB default default + memory: config.build_default_memory_limit.unwrap_or(3 * GB), + timeout: Duration::from_secs(15 * 60), // 15 minutes + targets: docs_rs_utils::DEFAULT_MAX_TARGETS, + networking: false, + max_log_size: 100 * 1024, // 100 KB + } + } + + pub async fn for_crate( + config: &Config, + conn: &mut sqlx::PgConnection, + name: &str, + ) -> Result { + let default = Self::new(config); + let overrides = Overrides::for_crate(conn, name).await?.unwrap_or_default(); + Ok(Self { + memory: overrides + .memory + .unwrap_or(default.memory) + .max(default.memory), + targets: overrides + .targets + .or(overrides.timeout.map(|_| 1)) + .unwrap_or(default.targets), + timeout: overrides.timeout.unwrap_or(default.timeout), + networking: default.networking, + max_log_size: default.max_log_size, + }) + } + + pub fn memory(&self) -> usize { + self.memory + } + + pub fn timeout(&self) -> Duration { + self.timeout + } + + pub fn networking(&self) -> bool { + self.networking + } + + pub fn max_log_size(&self) -> usize { + self.max_log_size + } + + pub fn targets(&self) -> usize { + self.targets + } +} + +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::test::*; + +// #[test] +// fn retrieve_limits() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let defaults = Limits::new(env.config()); + +// let krate = "hexponent"; +// // limits work if no crate has limits set +// let hexponent = Limits::for_crate(env.config(), &mut conn, krate).await?; +// assert_eq!(hexponent, defaults); + +// Overrides::save( +// &mut conn, +// krate, +// Overrides { +// targets: Some(15), +// ..Overrides::default() +// }, +// ) +// .await?; +// // limits work if crate has limits set +// let hexponent = Limits::for_crate(env.config(), &mut conn, krate).await?; +// assert_eq!( +// hexponent, +// Limits { +// targets: 15, +// ..defaults +// } +// ); + +// // all limits work +// let krate = "regex"; +// let limits = Limits { +// memory: defaults.memory * 2, +// timeout: defaults.timeout * 2, +// targets: 1, +// ..defaults +// }; +// Overrides::save( +// &mut conn, +// krate, +// Overrides { +// memory: Some(limits.memory), +// targets: Some(limits.targets), +// timeout: Some(limits.timeout), +// }, +// ) +// .await?; +// assert_eq!( +// limits, +// Limits::for_crate(env.config(), &mut conn, krate).await? +// ); +// Ok(()) +// }) +// } + +// #[test] +// fn targets_default_to_one_with_timeout() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; +// let krate = "hexponent"; +// Overrides::save( +// &mut conn, +// krate, +// Overrides { +// timeout: Some(Duration::from_secs(20 * 60)), +// ..Overrides::default() +// }, +// ) +// .await?; +// let limits = Limits::for_crate(env.config(), &mut conn, krate).await?; +// assert_eq!(limits.targets, 1); + +// Ok(()) +// }) +// } + +// #[tokio::test(flavor = "multi_thread")] +// async fn config_default_memory_limit() -> Result<()> { +// let env = TestEnvironment::with_config( +// TestEnvironment::base_config() +// .build_default_memory_limit(Some(6 * GB)) +// .build()?, +// ) +// .await?; + +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let limits = Limits::for_crate(env.config(), &mut conn, "krate").await?; +// assert_eq!(limits.memory, 6 * GB); + +// Ok(()) +// } + +// #[test] +// fn overrides_dont_lower_memory_limit() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let defaults = Limits::new(env.config()); + +// Overrides::save( +// &mut conn, +// "krate", +// Overrides { +// memory: Some(defaults.memory / 2), +// ..Overrides::default() +// }, +// ) +// .await?; + +// let limits = Limits::for_crate(env.config(), &mut conn, "krate").await?; +// assert_eq!(limits, defaults); + +// Ok(()) +// }) +// } +// } diff --git a/src/db/overrides.rs b/crates/docs_rs_build_utils/src/overrides.rs similarity index 97% rename from src/db/overrides.rs rename to crates/docs_rs_build_utils/src/overrides.rs index 4329c1c0e..af2614e86 100644 --- a/src/db/overrides.rs +++ b/crates/docs_rs_build_utils/src/overrides.rs @@ -1,6 +1,7 @@ -use crate::error::Result; +use anyhow::Result; use futures_util::stream::TryStreamExt; use std::time::Duration; +use tracing::warn; #[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] pub struct Overrides { @@ -40,7 +41,7 @@ impl Overrides { pub async fn save(conn: &mut sqlx::PgConnection, krate: &str, overrides: Self) -> Result<()> { if overrides.timeout.is_some() && overrides.targets.is_none() { - tracing::warn!( + warn!( "setting `Overrides::timeout` implies a default `Overrides::targets = 1`, prefer setting this explicitly" ); } @@ -50,7 +51,7 @@ impl Overrides { .await? .is_none() { - tracing::warn!("setting overrides for unknown crate `{krate}`"); + warn!("setting overrides for unknown crate `{krate}`"); } sqlx::query!( diff --git a/crates/docs_rs_builder/Cargo.toml b/crates/docs_rs_builder/Cargo.toml new file mode 100644 index 000000000..703ae9f1b --- /dev/null +++ b/crates/docs_rs_builder/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "docs_rs_builder" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1.0", features = ["derive"] } +docs_rs_database = { path = "../docs_rs_database" } diff --git a/crates/docs_rs_builder/src/config.rs b/crates/docs_rs_builder/src/config.rs new file mode 100644 index 000000000..cf5876deb --- /dev/null +++ b/crates/docs_rs_builder/src/config.rs @@ -0,0 +1,10 @@ +use docs_rs_env_vars::{env, maybe_env, require_env}; +use std::{path::PathBuf, time::Duration}; +use url::Url; + +#[derive(Debug)] +pub struct Config {} + +impl Config { + pub fn from_environment() -> anyhow::Result {} +} diff --git a/crates/docs_rs_builder/src/lib.rs b/crates/docs_rs_builder/src/lib.rs new file mode 100644 index 000000000..d30159c71 --- /dev/null +++ b/crates/docs_rs_builder/src/lib.rs @@ -0,0 +1,3 @@ +mod config; +pub mod limits; +mod overrides; diff --git a/crates/docs_rs_web/Cargo.toml b/crates/docs_rs_web/Cargo.toml index f35171733..b6c866b53 100644 --- a/crates/docs_rs_web/Cargo.toml +++ b/crates/docs_rs_web/Cargo.toml @@ -54,6 +54,7 @@ rayon = { workspace = true } opentelemetry_sdk = { workspace = true } strum = { workspace = true } font-awesome-as-a-crate = { path = "../font-awesome-as-a-crate" } +docs_rs_build_utils = { path = "../docs_rs_build_utils" } [build-dependencies] diff --git a/crates/docs_rs_web/src/builds.rs b/crates/docs_rs_web/src/builds.rs index 5f81645f5..cea39c949 100644 --- a/crates/docs_rs_web/src/builds.rs +++ b/crates/docs_rs_web/src/builds.rs @@ -1,5 +1,4 @@ use crate::{ - // docbuilder::Limits, MetaData, ReqVersion, cache::CachePolicy, @@ -21,6 +20,7 @@ use axum_extra::{ use chrono::{DateTime, Utc}; use constant_time_eq::constant_time_eq; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; +use docs_rs_build_utils::limis::Limits; use docs_rs_database::types::{BuildId, BuildStatus, version::Version}; use docs_rs_headers::CanonicalUrl; use http::StatusCode; diff --git a/src/docbuilder/limits.rs b/src/docbuilder/limits.rs deleted file mode 100644 index f1e21a27c..000000000 --- a/src/docbuilder/limits.rs +++ /dev/null @@ -1,198 +0,0 @@ -use crate::{Config, db::Overrides, error::Result}; -use serde::Serialize; -use std::time::Duration; - -const GB: usize = 1024 * 1024 * 1024; - -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] -pub(crate) struct Limits { - pub memory: usize, - pub targets: usize, - pub timeout: Duration, - pub networking: bool, - pub max_log_size: usize, -} - -impl Limits { - pub(crate) fn new(config: &Config) -> Self { - Self { - // 3 GB default default - memory: config.build_default_memory_limit.unwrap_or(3 * GB), - timeout: Duration::from_secs(15 * 60), // 15 minutes - targets: crate::DEFAULT_MAX_TARGETS, - networking: false, - max_log_size: 100 * 1024, // 100 KB - } - } - - pub(crate) async fn for_crate( - config: &Config, - conn: &mut sqlx::PgConnection, - name: &str, - ) -> Result { - let default = Self::new(config); - let overrides = Overrides::for_crate(conn, name).await?.unwrap_or_default(); - Ok(Self { - memory: overrides - .memory - .unwrap_or(default.memory) - .max(default.memory), - targets: overrides - .targets - .or(overrides.timeout.map(|_| 1)) - .unwrap_or(default.targets), - timeout: overrides.timeout.unwrap_or(default.timeout), - networking: default.networking, - max_log_size: default.max_log_size, - }) - } - - pub(crate) fn memory(&self) -> usize { - self.memory - } - - pub(crate) fn timeout(&self) -> Duration { - self.timeout - } - - pub(crate) fn networking(&self) -> bool { - self.networking - } - - pub(crate) fn max_log_size(&self) -> usize { - self.max_log_size - } - - pub(crate) fn targets(&self) -> usize { - self.targets - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::test::*; - - #[test] - fn retrieve_limits() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let defaults = Limits::new(env.config()); - - let krate = "hexponent"; - // limits work if no crate has limits set - let hexponent = Limits::for_crate(env.config(), &mut conn, krate).await?; - assert_eq!(hexponent, defaults); - - Overrides::save( - &mut conn, - krate, - Overrides { - targets: Some(15), - ..Overrides::default() - }, - ) - .await?; - // limits work if crate has limits set - let hexponent = Limits::for_crate(env.config(), &mut conn, krate).await?; - assert_eq!( - hexponent, - Limits { - targets: 15, - ..defaults - } - ); - - // all limits work - let krate = "regex"; - let limits = Limits { - memory: defaults.memory * 2, - timeout: defaults.timeout * 2, - targets: 1, - ..defaults - }; - Overrides::save( - &mut conn, - krate, - Overrides { - memory: Some(limits.memory), - targets: Some(limits.targets), - timeout: Some(limits.timeout), - }, - ) - .await?; - assert_eq!( - limits, - Limits::for_crate(env.config(), &mut conn, krate).await? - ); - Ok(()) - }) - } - - #[test] - fn targets_default_to_one_with_timeout() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - let krate = "hexponent"; - Overrides::save( - &mut conn, - krate, - Overrides { - timeout: Some(Duration::from_secs(20 * 60)), - ..Overrides::default() - }, - ) - .await?; - let limits = Limits::for_crate(env.config(), &mut conn, krate).await?; - assert_eq!(limits.targets, 1); - - Ok(()) - }) - } - - #[tokio::test(flavor = "multi_thread")] - async fn config_default_memory_limit() -> Result<()> { - let env = TestEnvironment::with_config( - TestEnvironment::base_config() - .build_default_memory_limit(Some(6 * GB)) - .build()?, - ) - .await?; - - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let limits = Limits::for_crate(env.config(), &mut conn, "krate").await?; - assert_eq!(limits.memory, 6 * GB); - - Ok(()) - } - - #[test] - fn overrides_dont_lower_memory_limit() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let defaults = Limits::new(env.config()); - - Overrides::save( - &mut conn, - "krate", - Overrides { - memory: Some(defaults.memory / 2), - ..Overrides::default() - }, - ) - .await?; - - let limits = Limits::for_crate(env.config(), &mut conn, "krate").await?; - assert_eq!(limits, defaults); - - Ok(()) - }) - } -} From df99079bf274e8203cb7f47b2b639b71fde08ed4 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 08:38:17 +0100 Subject: [PATCH 21/46] WIP --- Cargo.lock | 2 +- Cargo.toml | 2 +- crates/docs_rs_web/Cargo.toml | 1 + crates/docs_rs_web/src/builds.rs | 3 ++- crates/docs_rs_web/src/error.rs | 2 +- crates/docs_rs_web/src/features.rs | 2 +- crates/docs_rs_web/src/lib.rs | 4 +++- crates/docs_rs_web/src/rustdoc.rs | 2 +- crates/docs_rs_web/src/sitemap.rs | 1 + 9 files changed, 12 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 356be8d9f..014362d01 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1935,7 +1935,6 @@ dependencies = [ "test-case", "thiserror 2.0.17", "tokio", - "toml 0.9.8", "tower", "tracing", "tracing-log", @@ -2250,6 +2249,7 @@ dependencies = [ "thiserror 2.0.17", "tokio", "tokio-util", + "toml 0.9.8", "tower", "tower-http", "tracing", diff --git a/Cargo.toml b/Cargo.toml index c3d3f7ee2..f41e01ba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable" sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } clap = { version = "4.0.22", features = [ "derive" ] } askama = "0.14.0" +toml = "0.9.2" # dev dependencies test-case = "3.0.0" @@ -99,7 +100,6 @@ sysinfo = { version = "0.37.2", default-features = false, features = ["system"] tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } -toml = "0.9.2" tracing = { workspace = true } tracing-log = "0.2.0" url = { workspace = true } diff --git a/crates/docs_rs_web/Cargo.toml b/crates/docs_rs_web/Cargo.toml index b6c866b53..abafe2813 100644 --- a/crates/docs_rs_web/Cargo.toml +++ b/crates/docs_rs_web/Cargo.toml @@ -55,6 +55,7 @@ opentelemetry_sdk = { workspace = true } strum = { workspace = true } font-awesome-as-a-crate = { path = "../font-awesome-as-a-crate" } docs_rs_build_utils = { path = "../docs_rs_build_utils" } +toml = { workspace = true } [build-dependencies] diff --git a/crates/docs_rs_web/src/builds.rs b/crates/docs_rs_web/src/builds.rs index cea39c949..971c85f26 100644 --- a/crates/docs_rs_web/src/builds.rs +++ b/crates/docs_rs_web/src/builds.rs @@ -8,6 +8,7 @@ use crate::{ impl_axum_webpage, // filters, match_version, + page::templates::filters, page::templates::{RenderBrands, RenderRegular, RenderSolid}, }; use anyhow::{Result, anyhow}; @@ -20,7 +21,7 @@ use axum_extra::{ use chrono::{DateTime, Utc}; use constant_time_eq::constant_time_eq; use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; -use docs_rs_build_utils::limis::Limits; +use docs_rs_build_utils::limits::Limits; use docs_rs_database::types::{BuildId, BuildStatus, version::Version}; use docs_rs_headers::CanonicalUrl; use http::StatusCode; diff --git a/crates/docs_rs_web/src/error.rs b/crates/docs_rs_web/src/error.rs index 5d2ee760c..445e81140 100644 --- a/crates/docs_rs_web/src/error.rs +++ b/crates/docs_rs_web/src/error.rs @@ -104,7 +104,7 @@ impl AxumNope { status: StatusCode::UNAUTHORIZED, }, AxumNope::InternalError(source) => { - crate::utils::report_error(&source); + error!(?source, "Internal server error"); ErrorInfo { title: "Internal Server Error", message: Cow::Owned(source.to_string()), diff --git a/crates/docs_rs_web/src/features.rs b/crates/docs_rs_web/src/features.rs index 94a4a8b98..4c3c60ac3 100644 --- a/crates/docs_rs_web/src/features.rs +++ b/crates/docs_rs_web/src/features.rs @@ -7,7 +7,7 @@ use crate::{ rustdoc::{PageKind, RustdocParams}, }, impl_axum_webpage, match_version, - page::templates::{RenderBrands, RenderRegular, RenderSolid}, + page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, }; use anyhow::anyhow; use askama::Template; diff --git a/crates/docs_rs_web/src/lib.rs b/crates/docs_rs_web/src/lib.rs index 2dd27a8fb..8a2d1e9bd 100644 --- a/crates/docs_rs_web/src/lib.rs +++ b/crates/docs_rs_web/src/lib.rs @@ -69,6 +69,8 @@ use std::{ use tower::ServiceBuilder; use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::TraceLayer}; +use crate::metrics::WebMetrics; + use self::crate_details::Release; use page::GlobalAlert; @@ -471,7 +473,7 @@ async fn set_sentry_transaction_name_from_axum_route( async fn apply_middleware( router: AxumRouter, - context: &Context, + // context: &Context, template_data: Option>, ) -> Result { let has_templates = template_data.is_some(); diff --git a/crates/docs_rs_web/src/rustdoc.rs b/crates/docs_rs_web/src/rustdoc.rs index 5e5d4ac7a..584134741 100644 --- a/crates/docs_rs_web/src/rustdoc.rs +++ b/crates/docs_rs_web/src/rustdoc.rs @@ -564,7 +564,7 @@ impl RustdocPage { etag.map(TypedHeader), Extension(cache_policy), TypedHeader(ContentType::from(mime::TEXT_HTML_UTF_8)), - Body::from_stream(utils::rewrite_rustdoc_html_stream( + Body::from_stream(utils::html::rewrite_rustdoc_html_stream( template_data, rustdoc_html.content, max_parse_memory, diff --git a/crates/docs_rs_web/src/sitemap.rs b/crates/docs_rs_web/src/sitemap.rs index f378c5a44..2d94a55e2 100644 --- a/crates/docs_rs_web/src/sitemap.rs +++ b/crates/docs_rs_web/src/sitemap.rs @@ -18,6 +18,7 @@ use axum::{ }; use axum_extra::{TypedHeader, headers::ContentType}; use chrono::{TimeZone, Utc}; +use docs_rs_build_utils::limits::Limits; use docs_rs_database::{ mimes, service_config::{ConfigName, get_config}, From c99308e8c3475abd6f6963b24065288d1eaf9ea9 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 10:57:49 +0100 Subject: [PATCH 22/46] WIP --- Cargo.lock | 38 +- Cargo.toml | 8 +- NOTES.d | 4 + crates/docs_rs_builder/Cargo.toml | 30 +- crates/docs_rs_builder/src/config.rs | 56 +- crates/docs_rs_builder/src/db/add_package.rs | 1302 ++++++++++++++++ crates/docs_rs_builder/src/db/blacklist.rs | 123 ++ crates/docs_rs_builder/src/db/mod.rs | 3 + crates/docs_rs_builder/src/docbuilder/mod.rs | 1 + .../src}/docbuilder/rustwide_builder.rs | 68 +- crates/docs_rs_builder/src/lib.rs | 7 +- .../docs_rs_builder/src/metrics.rs | 24 +- .../docs_rs_builder/src}/utils/copy.rs | 0 .../docs_rs_builder/src}/utils/mod.rs | 30 +- .../src/utils/queue_builder.rs | 75 + .../docs_rs_builder/src}/utils/version.rs | 0 crates/docs_rs_cargo_metadata/src/db.rs | 104 +- crates/docs_rs_cargo_metadata/src/lib.rs | 2 +- crates/docs_rs_context/Cargo.toml | 2 + crates/docs_rs_context/src/lib.rs | 87 +- crates/docs_rs_database/Cargo.toml | 1 + crates/docs_rs_database/src/lib.rs | 1 + .../docs_rs_database/src/migrate.rs | 17 +- crates/docs_rs_registry_api/Cargo.toml | 1 + crates/docs_rs_registry_api/src/config.rs | 22 + crates/docs_rs_registry_api/src/lib.rs | 54 +- crates/docs_rs_utils/Cargo.toml | 2 + crates/docs_rs_utils/src/lib.rs | 2 + crates/docs_rs_utils/src/rustc_version.rs | 82 ++ crates/docs_rs_web/src/lib.rs | 3 +- src/config.rs | 123 -- src/context.rs | 84 -- src/db/add_package.rs | 1303 ----------------- src/db/blacklist.rs | 123 -- src/docbuilder/mod.rs | 13 - src/lib.rs | 21 - src/test/fakes.rs | 743 ---------- src/test/headers.rs | 24 - src/test/mod.rs | 664 --------- src/test/test_metrics.rs | 115 -- src/utils/queue_builder.rs | 70 - 41 files changed, 1955 insertions(+), 3477 deletions(-) create mode 100644 NOTES.d create mode 100644 crates/docs_rs_builder/src/db/add_package.rs create mode 100644 crates/docs_rs_builder/src/db/blacklist.rs create mode 100644 crates/docs_rs_builder/src/db/mod.rs create mode 100644 crates/docs_rs_builder/src/docbuilder/mod.rs rename {src => crates/docs_rs_builder/src}/docbuilder/rustwide_builder.rs (97%) rename src/metrics/mod.rs => crates/docs_rs_builder/src/metrics.rs (60%) rename {src => crates/docs_rs_builder/src}/utils/copy.rs (100%) rename {src => crates/docs_rs_builder/src}/utils/mod.rs (71%) create mode 100644 crates/docs_rs_builder/src/utils/queue_builder.rs rename {src => crates/docs_rs_builder/src}/utils/version.rs (100%) rename src/db/mod.rs => crates/docs_rs_database/src/migrate.rs (83%) create mode 100644 crates/docs_rs_registry_api/src/config.rs create mode 100644 crates/docs_rs_utils/src/rustc_version.rs delete mode 100644 src/config.rs delete mode 100644 src/context.rs delete mode 100644 src/db/add_package.rs delete mode 100644 src/db/blacklist.rs delete mode 100644 src/docbuilder/mod.rs delete mode 100644 src/test/fakes.rs delete mode 100644 src/test/headers.rs delete mode 100644 src/test/mod.rs delete mode 100644 src/test/test_metrics.rs delete mode 100644 src/utils/queue_builder.rs diff --git a/Cargo.lock b/Cargo.lock index 014362d01..d4038f06b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1892,6 +1892,7 @@ dependencies = [ "docs_rs_headers", "docs_rs_logging", "docs_rs_opentelemetry", + "docs_rs_registry_api", "docs_rs_storage", "docs_rs_utils", "docs_rs_watcher", @@ -1900,14 +1901,11 @@ dependencies = [ "fn-error-context", "futures-util", "getrandom 0.3.4", - "hex", - "hostname", "http 1.4.0", "http-body-util", "indoc", "itertools 0.14.0", "kuchikiki", - "log", "mime", "mockito", "num_cpus", @@ -1930,7 +1928,6 @@ dependencies = [ "slug", "sqlx", "strum", - "sysinfo", "tempfile", "test-case", "thiserror 2.0.17", @@ -1977,8 +1974,36 @@ dependencies = [ name = "docs_rs_builder" version = "0.1.0" dependencies = [ + "anyhow", + "docs_rs_build_utils", + "docs_rs_cargo_metadata", + "docs_rs_context", "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_opentelemetry", + "docs_rs_registry_api", + "docs_rs_storage", + "docs_rs_utils", + "docs_rs_watcher", + "docsrs-metadata", + "futures-util", + "hostname", + "itertools 0.14.0", + "log", + "opentelemetry", + "regex", + "rustwide", "serde", + "serde_json", + "slug", + "sqlx", + "sysinfo", + "tempfile", + "thiserror 2.0.17", + "tokio", + "toml 0.9.8", + "tracing", + "url", ] [[package]] @@ -2002,6 +2027,8 @@ dependencies = [ "docs_rs_build_queue", "docs_rs_database", "docs_rs_opentelemetry", + "docs_rs_storage", + "tokio", ] [[package]] @@ -2014,6 +2041,7 @@ dependencies = [ "docs_rs_env_vars", "docs_rs_opentelemetry", "futures-util", + "hex", "mime", "mime_guess", "opentelemetry", @@ -2105,6 +2133,7 @@ dependencies = [ "bincode 2.0.1", "chrono", "docs_rs_database", + "docs_rs_env_vars", "docs_rs_utils", "reqwest", "serde", @@ -2157,6 +2186,7 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "regex", "time", "tokio", "tracing", diff --git a/Cargo.toml b/Cargo.toml index f41e01ba2..ff0cb3d0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,6 +45,7 @@ sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "an clap = { version = "4.0.22", features = [ "derive" ] } askama = "0.14.0" toml = "0.9.2" +slug = "0.1.1" # dev dependencies test-case = "3.0.0" @@ -69,14 +70,12 @@ docs_rs_watcher = { path = "crates/docs_rs_watcher" } docs_rs_logging = { path = "crates/docs_rs_logging" } docs_rs_web_utils = { path = "crates/docs_rs_web_utils" } docsrs-metadata = { path = "crates/metadata" } +docs_rs_registry_api = { path = "crates/docs_rs_registry_api" } fn-error-context = "0.2.0" futures-util = { workspace = true } getrandom = "0.3.1" -hex = "0.4.3" -hostname = "0.4.0" http = { workspace = true } itertools = { workspace = true } -log = "0.4" mime = { workspace = true } num_cpus = "1.15.0" opentelemetry = { workspace = true } @@ -93,10 +92,9 @@ sentry = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } serde_with = { workspace = true } -slug = "0.1.1" +slug = { workspace = true } sqlx = { workspace = true } strum = { workspace = true } -sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } tempfile = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } diff --git a/NOTES.d b/NOTES.d new file mode 100644 index 000000000..e32a75894 --- /dev/null +++ b/NOTES.d @@ -0,0 +1,4 @@ +# validat + +* [ ] validate if some dependencies can bemoved from workspace to only crate deps +* [ ] big binary crates should not depend on each other. the shared functionality should be extracte diff --git a/crates/docs_rs_builder/Cargo.toml b/crates/docs_rs_builder/Cargo.toml index 703ae9f1b..735c884b8 100644 --- a/crates/docs_rs_builder/Cargo.toml +++ b/crates/docs_rs_builder/Cargo.toml @@ -4,5 +4,33 @@ version = "0.1.0" edition = "2024" [dependencies] -serde = { version = "1.0", features = ["derive"] } +serde = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_storage = { path = "../docs_rs_storage" } +docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } +docs_rs_watcher = { path = "../docs_rs_watcher" } +docs_rs_registry_api = { path = "../docs_rs_registry_api" } +docs_rs_build_utils = { path = "../docs_rs_build_utils" } +docs_rs_context = { path = "../docs_rs_context" } +url = { workspace = true } +anyhow = { workspace = true } +serde_json = { workspace = true } +slug = { workspace = true } +futures-util = { workspace = true } +docs_rs_utils = { path = "../docs_rs_utils" } +docsrs-metadata = { path = "../metadata" } +opentelemetry = { workspace = true } +itertools = { workspace = true } +regex = { workspace = true } +rustwide = { workspace = true } +tracing = { workspace = true } +sqlx = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } +sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } +log = "0.4" +tempfile = { workspace = true } +toml = { workspace = true } +hostname = "0.4.0" diff --git a/crates/docs_rs_builder/src/config.rs b/crates/docs_rs_builder/src/config.rs index cf5876deb..30b5678c6 100644 --- a/crates/docs_rs_builder/src/config.rs +++ b/crates/docs_rs_builder/src/config.rs @@ -1,10 +1,60 @@ use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{path::PathBuf, time::Duration}; -use url::Url; #[derive(Debug)] -pub struct Config {} +pub struct Config { + pub(crate) prefix: PathBuf, + pub(crate) temp_dir: PathBuf, + + // Where to collect metrics for the metrics initiative. + // When empty, we won't collect metrics. + pub(crate) compiler_metrics_collection_path: Option, + + pub(crate) build_workspace_reinitialization_interval: Duration, + + // Build params + pub(crate) build_attempts: u16, + pub(crate) delay_between_build_attempts: Duration, + pub(crate) rustwide_workspace: PathBuf, + pub(crate) inside_docker: bool, + pub(crate) docker_image: Option, + pub(crate) build_cpu_limit: Option, + pub(crate) build_default_memory_limit: Option, + pub(crate) include_default_targets: bool, + pub(crate) disable_memory_limit: bool, +} impl Config { - pub fn from_environment() -> anyhow::Result {} + pub fn from_environment() -> anyhow::Result { + let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; + Ok(Self { + temp_dir: prefix.join("tmp"), + // api_host: env( + // "DOCSRS_FASTLY_API_HOST", + // "https://api.fastly.com".parse().unwrap(), + // )?, + // api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, + // service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, + prefix, + build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, + delay_between_build_attempts: Duration::from_secs(env::( + "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", + 60, + )?), + rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, + inside_docker: env("DOCSRS_DOCKER", false)?, + docker_image: maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")? + .or(maybe_env("DOCSRS_DOCKER_IMAGE")?), + + build_cpu_limit: maybe_env("DOCSRS_BUILD_CPU_LIMIT")?, + build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, + include_default_targets: env("DOCSRS_INCLUDE_DEFAULT_TARGETS", true)?, + disable_memory_limit: env("DOCSRS_DISABLE_MEMORY_LIMIT", false)?, + build_workspace_reinitialization_interval: Duration::from_secs(env( + "DOCSRS_BUILD_WORKSPACE_REINITIALIZATION_INTERVAL", + 86400, + )?), + compiler_metrics_collection_path: maybe_env("DOCSRS_COMPILER_METRICS_PATH")?, + }) + } } diff --git a/crates/docs_rs_builder/src/db/add_package.rs b/crates/docs_rs_builder/src/db/add_package.rs new file mode 100644 index 000000000..f133cba33 --- /dev/null +++ b/crates/docs_rs_builder/src/db/add_package.rs @@ -0,0 +1,1302 @@ +use crate::docbuilder::rustwide_builder::DocCoverage; +use anyhow::{Context, Result, anyhow}; +use docs_rs_cargo_metadata::Package as MetadataPackage; +use docs_rs_cargo_metadata::db::ReleaseDependencyList; +use docs_rs_database::types::{ + BuildId, BuildStatus, CrateId, Feature, ReleaseId, version::Version, +}; +use docs_rs_registry_api::{CrateData, CrateOwner, ReleaseData}; +use docs_rs_storage::CompressionAlgorithm; +use docs_rs_utils::rustc_version::parse_rustc_date; +use futures_util::stream::TryStreamExt; +use serde_json::Value; +use slug::slugify; +use std::{ + collections::{HashMap, HashSet}, + fs, + io::{BufRead, BufReader}, + path::Path, +}; +use tracing::{debug, error, info, instrument}; + +/// Adds a package into database. +/// +/// Package must be built first. +/// +/// NOTE: `source_files` refers to the files originally in the crate, +/// not the files generated by rustdoc. +#[allow(clippy::too_many_arguments)] +#[instrument(skip(conn, compression_algorithms))] +pub(crate) async fn finish_release( + conn: &mut sqlx::PgConnection, + crate_id: CrateId, + release_id: ReleaseId, + metadata_pkg: &MetadataPackage, + source_dir: &Path, + default_target: &str, + source_files: Value, + doc_targets: Vec, + registry_data: &ReleaseData, + has_docs: bool, + has_examples: bool, + compression_algorithms: impl IntoIterator, + repository_id: Option, + archive_storage: bool, + source_size: u64, +) -> Result<()> { + debug!("updating release data"); + let dependencies: ReleaseDependencyList = metadata_pkg + .dependencies + .iter() + .cloned() + .map(Into::into) + .collect(); + let rustdoc = get_rustdoc(metadata_pkg, source_dir).unwrap_or(None); + let readme = get_readme(metadata_pkg, source_dir).unwrap_or(None); + let features = get_features(metadata_pkg); + let is_library = metadata_pkg.is_library(); + + let result = sqlx::query!( + r#"UPDATE releases + SET release_time = $2, + dependencies = $3, + target_name = $4, + yanked = $5, + rustdoc_status = $6, + test_status = $7, + license = $8, + repository_url = $9, + homepage_url = $10, + description = $11, + description_long = $12, + readme = $13, + keywords = $14, + have_examples = $15, + downloads = $16, + files = $17, + doc_targets = $18, + is_library = $19, + documentation_url = $20, + default_target = $21, + features = $22, + repository_id = $23, + archive_storage = $24, + source_size = $25 + WHERE id = $1"#, + release_id.0, + registry_data.release_time, + serde_json::to_value(&dependencies)?, + metadata_pkg.package_name(), + registry_data.yanked, + has_docs, + false, // TODO: Add test status somehow + metadata_pkg.license, + metadata_pkg.repository, + metadata_pkg.homepage, + metadata_pkg.description, + rustdoc, + readme, + serde_json::to_value(&metadata_pkg.keywords)?, + has_examples, + registry_data.downloads, + source_files, + serde_json::to_value(doc_targets)?, + is_library, + metadata_pkg.documentation, + default_target, + features as Vec, + repository_id, + archive_storage, + source_size as i64, + ) + .execute(&mut *conn) + .await?; + + if result.rows_affected() < 1 { + return Err(anyhow!("Failed to update release")); + } + + add_keywords_into_database(conn, metadata_pkg, release_id).await?; + add_compression_into_database(conn, compression_algorithms.into_iter(), release_id).await?; + + update_latest_version_id(&mut *conn, crate_id) + .await + .context("couldn't update latest version id")?; + + update_build_status(conn, release_id).await?; + + Ok(()) +} + +pub async fn update_latest_version_id( + conn: &mut sqlx::PgConnection, + crate_id: CrateId, +) -> Result<()> { + todo!(); + // let releases = releases_for_crate(conn, crate_id).await?; + + // sqlx::query!( + // "UPDATE crates + // SET latest_version_id = $2 + // WHERE id = $1", + // crate_id.0, + // latest_release(&releases).map(|release| release.id.0), + // ) + // .execute(&mut *conn) + // .await?; + + Ok(()) +} + +pub async fn update_build_status( + conn: &mut sqlx::PgConnection, + release_id: ReleaseId, +) -> Result<()> { + sqlx::query!( + "INSERT INTO release_build_status(rid, last_build_time, build_status) + SELECT + summary.id, + summary.last_build_time, + CASE + WHEN summary.success_count > 0 THEN 'success'::build_status + WHEN summary.failure_count > 0 THEN 'failure'::build_status + ELSE 'in_progress'::build_status + END as build_status + + FROM ( + SELECT + r.id, + MAX(b.build_finished) as last_build_time, + SUM(CASE WHEN b.build_status = 'success' THEN 1 ELSE 0 END) as success_count, + SUM(CASE WHEN b.build_status = 'failure' THEN 1 ELSE 0 END) as failure_count + FROM + releases as r + LEFT OUTER JOIN builds AS b on b.rid = r.id + WHERE + r.id = $1 + GROUP BY r.id + ) as summary + + ON CONFLICT (rid) DO UPDATE + SET + last_build_time = EXCLUDED.last_build_time, + build_status=EXCLUDED.build_status", + release_id.0, + ) + .execute(&mut *conn) + .await?; + + let crate_id = crate_id_from_release_id(&mut *conn, release_id).await?; + update_latest_version_id(&mut *conn, crate_id) + .await + .context("couldn't update latest version id")?; + + Ok(()) +} + +async fn crate_id_from_release_id( + conn: &mut sqlx::PgConnection, + release_id: ReleaseId, +) -> Result { + Ok(sqlx::query_scalar!( + r#" + SELECT crate_id as "crate_id: CrateId" + FROM releases + WHERE id = $1"#, + release_id.0, + ) + .fetch_one(&mut *conn) + .await?) +} + +#[instrument(skip(conn))] +pub(crate) async fn add_doc_coverage( + conn: &mut sqlx::PgConnection, + release_id: ReleaseId, + doc_coverage: DocCoverage, +) -> Result { + debug!("Adding doc coverage into database"); + Ok(sqlx::query_scalar!( + "INSERT INTO doc_coverage ( + release_id, total_items, documented_items, + total_items_needing_examples, items_with_examples + ) + VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (release_id) DO UPDATE + SET + total_items = $2, + documented_items = $3, + total_items_needing_examples = $4, + items_with_examples = $5 + RETURNING release_id", + release_id.0, + &doc_coverage.total_items, + &doc_coverage.documented_items, + &doc_coverage.total_items_needing_examples, + &doc_coverage.items_with_examples, + ) + .fetch_one(&mut *conn) + .await?) +} + +/// Adds a build into database +#[instrument(skip(conn))] +pub(crate) async fn finish_build( + conn: &mut sqlx::PgConnection, + build_id: BuildId, + rustc_version: &str, + docsrs_version: &str, + build_status: BuildStatus, + documentation_size: Option, + errors: Option<&str>, +) -> Result<()> { + debug!("updating build after finishing"); + let hostname = hostname::get()?; + + let rustc_date = match parse_rustc_date(rustc_version) { + Ok(date) => Some(date), + Err(err) => { + // in the database we see cases where the rustc version is missing + // in the builds-table. In this case & if we can't parse the version + // we just want to log an error, but still finish the build. + error!( + "Failed to parse date from rustc version \"{}\": {:?}", + rustc_version, err + ); + None + } + }; + + let release_id = sqlx::query_scalar!( + r#"UPDATE builds + SET + rustc_version = $1, + docsrs_version = $2, + build_status = $3, + build_server = $4, + errors = $5, + documentation_size = $6, + rustc_nightly_date = $7, + build_finished = NOW() + WHERE + id = $8 + RETURNING rid as "rid: ReleaseId" "#, + rustc_version, + docsrs_version, + build_status as BuildStatus, + hostname.to_str().unwrap_or(""), + errors, + documentation_size.map(|v| v as i64), + rustc_date, + build_id.0, + ) + .fetch_one(&mut *conn) + .await?; + + update_build_status(conn, release_id).await?; + + Ok(()) +} + +#[instrument(skip(conn))] +pub(crate) async fn update_build_with_error( + conn: &mut sqlx::PgConnection, + build_id: BuildId, + errors: Option<&str>, +) -> Result { + debug!("updating build with error"); + let release_id = sqlx::query_scalar!( + r#"UPDATE builds + SET + build_status = $1, + errors = $2 + WHERE id = $3 + RETURNING rid as "rid: ReleaseId" "#, + BuildStatus::Failure as BuildStatus, + errors, + build_id.0, + ) + .fetch_one(&mut *conn) + .await?; + + update_build_status(conn, release_id).await?; + + Ok(build_id) +} + +pub(crate) async fn initialize_crate(conn: &mut sqlx::PgConnection, name: &str) -> Result { + sqlx::query_scalar!( + "INSERT INTO crates (name) + VALUES ($1) + ON CONFLICT (name) DO UPDATE + SET -- this `SET` is needed so the id is always returned. + name = EXCLUDED.name + RETURNING id", + name + ) + .fetch_one(&mut *conn) + .await + .map_err(Into::into) + .map(CrateId) +} + +pub(crate) async fn initialize_release( + conn: &mut sqlx::PgConnection, + crate_id: CrateId, + version: &Version, +) -> Result { + let release_id = sqlx::query_scalar!( + r#"INSERT INTO releases (crate_id, version, archive_storage) + VALUES ($1, $2, TRUE) + ON CONFLICT (crate_id, version) DO UPDATE + SET -- this `SET` is needed so the id is always returned. + version = EXCLUDED.version + RETURNING id as "id: ReleaseId" "#, + crate_id.0, + version as _, + ) + .fetch_one(&mut *conn) + .await?; + + update_build_status(conn, release_id).await?; + + Ok(release_id) +} + +pub(crate) async fn initialize_build( + conn: &mut sqlx::PgConnection, + release_id: ReleaseId, +) -> Result { + let hostname = hostname::get()?; + + let build_id = sqlx::query_scalar!( + r#"INSERT INTO builds(rid, build_status, build_server, build_started) + VALUES ($1, $2, $3, NOW()) + RETURNING id as "id: BuildId" "#, + release_id.0, + BuildStatus::InProgress as BuildStatus, + hostname.to_str().unwrap_or(""), + ) + .fetch_one(&mut *conn) + .await?; + + update_build_status(conn, release_id).await?; + + Ok(build_id) +} + +/// Reads features and converts them to Vec with default being first +fn get_features(pkg: &MetadataPackage) -> Vec { + let mut features = Vec::with_capacity(pkg.features.len()); + if let Some(subfeatures) = pkg.features.get("default") { + features.push(Feature::new("default".into(), subfeatures.clone())); + }; + features.extend( + pkg.features + .iter() + .filter(|(name, _)| *name != "default") + .map(|(name, subfeatures)| Feature::new(name.clone(), subfeatures.clone())), + ); + features +} + +/// Reads readme if there is any read defined in Cargo.toml of a Package +fn get_readme(pkg: &MetadataPackage, source_dir: &Path) -> Result> { + let readme_path = source_dir.join(pkg.readme.as_deref().unwrap_or("README.md")); + + if !readme_path.exists() { + return Ok(None); + } + + let readme = fs::read_to_string(readme_path)?; + + if readme.is_empty() { + Ok(None) + } else if readme.len() > 51200 { + Ok(Some(format!( + "(Readme ignored due to being too long. ({} > 51200))", + readme.len() + ))) + } else { + Ok(Some(readme)) + } +} + +fn get_rustdoc(pkg: &MetadataPackage, source_dir: &Path) -> Result> { + if let Some(src_path) = &pkg.targets.first().and_then(|t| t.src_path.as_ref()) { + let src_path = Path::new(src_path); + if src_path.is_absolute() { + read_rust_doc(src_path) + } else { + read_rust_doc(&source_dir.join(src_path)) + } + } else { + // FIXME: should we care about metabuild targets? + Ok(None) + } +} + +/// Reads rustdoc from library +fn read_rust_doc(file_path: &Path) -> Result> { + let reader = fs::File::open(file_path).map(BufReader::new)?; + let mut rustdoc = String::new(); + + for line in reader.lines() { + let line = line?; + if line.starts_with("//!") { + // some lines may or may not have a space between the `//!` and the start of the text + let mut line = line.trim_start_matches("//!"); + if line.starts_with(' ') { + line = &line[1..]; + } + if !line.is_empty() { + rustdoc.push_str(line); + } + rustdoc.push('\n'); + } + } + + if rustdoc.is_empty() { + Ok(None) + } else if rustdoc.len() > 51200 { + Ok(Some(format!( + "(Library doc comment ignored due to being too long. ({} > 51200))", + rustdoc.len() + ))) + } else { + Ok(Some(rustdoc)) + } +} + +/// Adds keywords into database +async fn add_keywords_into_database( + conn: &mut sqlx::PgConnection, + pkg: &MetadataPackage, + release_id: ReleaseId, +) -> Result<()> { + let wanted_keywords: HashMap = pkg + .keywords + .iter() + .map(|kw| (slugify(kw), kw.clone())) + .collect(); + + let existing_keyword_slugs: HashSet = sqlx::query!( + "SELECT slug FROM keywords WHERE slug = ANY($1)", + &wanted_keywords.keys().cloned().collect::>()[..], + ) + .fetch(&mut *conn) + .map_ok(|row| row.slug) + .try_collect() + .await?; + + // we create new keywords one-by-one, since most of the time we already have them, + // and because support for multi-record inserts is a mess without adding a new + // library + for (slug, name) in wanted_keywords + .iter() + .filter(|(k, _)| !(existing_keyword_slugs.contains(*k))) + { + sqlx::query!( + "INSERT INTO keywords (name, slug) VALUES ($1, $2)", + name, + slug + ) + .execute(&mut *conn) + .await?; + } + + sqlx::query!( + "INSERT INTO keyword_rels (rid, kid) + SELECT $1 as rid, id as kid + FROM keywords + WHERE slug = ANY($2) + ON CONFLICT DO NOTHING;", + release_id.0, + &wanted_keywords.keys().cloned().collect::>()[..], + ) + .execute(&mut *conn) + .await?; + + Ok(()) +} + +#[instrument(skip(conn))] +pub async fn update_crate_data_in_database( + conn: &mut sqlx::PgConnection, + name: &str, + registry_data: &CrateData, +) -> Result<()> { + info!("Updating crate data for {}", name); + let crate_id = sqlx::query_scalar!( + r#"SELECT id as "id: CrateId" FROM crates WHERE crates.name = $1"#, + name + ) + .fetch_one(&mut *conn) + .await?; + + update_owners_in_database(conn, ®istry_data.owners, crate_id).await?; + + Ok(()) +} + +/// Adds owners into database +async fn update_owners_in_database( + conn: &mut sqlx::PgConnection, + owners: &[CrateOwner], + crate_id: CrateId, +) -> Result<()> { + // Update any existing owner data since it is mutable and could have changed since last + // time we pulled it + + let mut oids: Vec = Vec::new(); + + for owner in owners { + oids.push( + sqlx::query_scalar!( + "INSERT INTO owners (login, avatar, kind) + VALUES ($1, $2, $3) + ON CONFLICT (login) DO UPDATE + SET + avatar = EXCLUDED.avatar, + kind = EXCLUDED.kind + RETURNING id", + owner.login, + owner.avatar, + owner.kind as _, + ) + .fetch_one(&mut *conn) + .await?, + ); + } + + sqlx::query!( + "INSERT INTO owner_rels (cid, oid) + SELECT $1,oid + FROM UNNEST($2::int[]) as oid + ON CONFLICT (cid,oid) + DO NOTHING", + crate_id.0, + &oids[..] + ) + .execute(&mut *conn) + .await?; + + sqlx::query!( + "DELETE FROM owner_rels + WHERE + cid = $1 AND + NOT (oid = ANY($2))", + crate_id.0, + &oids[..], + ) + .execute(&mut *conn) + .await?; + + Ok(()) +} + +/// Add the compression algorithms used for this crate to the database +async fn add_compression_into_database( + conn: &mut sqlx::PgConnection, + algorithms: I, + release_id: ReleaseId, +) -> Result<()> +where + I: Iterator, +{ + for alg in algorithms { + sqlx::query!( + "INSERT INTO compression_rels (release, algorithm) + VALUES ($1, $2) + ON CONFLICT DO NOTHING;", + release_id.0, + &(alg as i32) + ) + .execute(&mut *conn) + .await?; + } + Ok(()) +} + +// #[cfg(test)] +// mod test { +// use super::*; +// use crate::registry_api::OwnerKind; +// use crate::test::*; +// use crate::utils::CargoMetadata; +// use chrono::NaiveDate; +// use std::slice; +// use test_case::test_case; + +// #[test] +// fn test_set_build_to_error() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; +// let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; +// let build_id = initialize_build(&mut conn, release_id).await?; + +// update_build_with_error(&mut conn, build_id, Some("error message")).await?; + +// let row = sqlx::query!( +// r#"SELECT +// rustc_version, +// docsrs_version, +// build_started, +// build_status as "build_status: BuildStatus", +// errors +// FROM builds +// WHERE id = $1"#, +// build_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert!(row.rustc_version.is_none()); +// assert!(row.docsrs_version.is_none()); +// assert!(row.build_started.is_some()); +// assert_eq!(row.build_status, BuildStatus::Failure); +// assert_eq!(row.errors, Some("error message".into())); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_finish_build_success_valid_rustc_date() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; +// let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; +// let build_id = initialize_build(&mut conn, release_id).await?; + +// finish_build( +// &mut conn, +// build_id, +// "rustc 1.84.0-nightly (e7c0d2750 2024-10-15)", +// "docsrs_version", +// BuildStatus::Success, +// None, +// None, +// ) +// .await?; + +// let row = sqlx::query!( +// r#"SELECT +// rustc_version, +// docsrs_version, +// build_status as "build_status: BuildStatus", +// errors, +// rustc_nightly_date +// FROM builds +// WHERE id = $1"#, +// build_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!( +// row.rustc_version, +// Some("rustc 1.84.0-nightly (e7c0d2750 2024-10-15)".into()) +// ); +// assert_eq!(row.docsrs_version, Some("docsrs_version".into())); +// assert_eq!(row.build_status, BuildStatus::Success); +// assert_eq!( +// row.rustc_nightly_date, +// Some(NaiveDate::from_ymd_opt(2024, 10, 15).unwrap()) +// ); +// assert!(row.errors.is_none()); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_finish_build_success_invalid_rustc_date() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; +// let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; +// let build_id = initialize_build(&mut conn, release_id).await?; + +// finish_build( +// &mut conn, +// build_id, +// "rustc_version", +// "docsrs_version", +// BuildStatus::Success, +// Some(42), +// None, +// ) +// .await?; + +// let row = sqlx::query!( +// r#"SELECT +// rustc_version, +// docsrs_version, +// build_status as "build_status: BuildStatus", +// documentation_size, +// errors, +// rustc_nightly_date +// FROM builds +// WHERE id = $1"#, +// build_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(row.rustc_version, Some("rustc_version".into())); +// assert_eq!(row.docsrs_version, Some("docsrs_version".into())); +// assert_eq!(row.build_status, BuildStatus::Success); +// assert_eq!(row.documentation_size, Some(42)); +// assert!(row.rustc_nightly_date.is_none()); +// assert!(row.errors.is_none()); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_finish_build_error() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; +// let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; +// let build_id = initialize_build(&mut conn, release_id).await?; + +// finish_build( +// &mut conn, +// build_id, +// "rustc_version", +// "docsrs_version", +// BuildStatus::Failure, +// None, +// Some("error message"), +// ) +// .await?; + +// let row = sqlx::query!( +// r#"SELECT +// rustc_version, +// docsrs_version, +// build_status as "build_status: BuildStatus", +// documentation_size, +// errors +// FROM builds +// WHERE id = $1"#, +// build_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(row.rustc_version, Some("rustc_version".into())); +// assert_eq!(row.docsrs_version, Some("docsrs_version".into())); +// assert_eq!(row.build_status, BuildStatus::Failure); +// assert_eq!(row.errors, Some("error message".into())); +// assert!(row.documentation_size.is_none()); + +// Ok(()) +// }) +// } + +// #[test] +// fn new_keywords() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// let release_id = env +// .fake_release() +// .await +// .name("dummy") +// .version(V0_1) +// .keywords(vec!["kw 1".into(), "kw 2".into()]) +// .create() +// .await?; + +// let kw_r = sqlx::query!( +// r#"SELECT +// kw.name as "name!", +// kw.slug as "slug!" +// FROM keywords as kw +// INNER JOIN keyword_rels as kwr on kw.id = kwr.kid +// WHERE kwr.rid = $1 +// ORDER BY kw.name,kw.slug"#, +// release_id.0 +// ) +// .fetch_all(&mut *conn) +// .await? +// .into_iter() +// .map(|row| (row.name, row.slug)) +// .collect::>(); + +// assert_eq!(kw_r[0], ("kw 1".into(), "kw-1".into())); +// assert_eq!(kw_r[1], ("kw 2".into(), "kw-2".into())); + +// let all_kw = sqlx::query!("SELECT slug FROM keywords ORDER BY slug") +// .fetch_all(&mut *conn) +// .await? +// .into_iter() +// .map(|row| row.slug) +// .collect::>(); + +// assert_eq!(all_kw, vec![String::from("kw-1"), "kw-2".into()]); + +// Ok(()) +// }) +// } + +// #[test] +// fn keyword_conflict_when_rebuilding_release() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version(V0_1) +// .keywords(vec!["kw 3".into(), "kw 4".into()]) +// .create() +// .await?; + +// // same version so we have the same release +// env.fake_release() +// .await +// .name("dummy") +// .version(V0_1) +// .keywords(vec!["kw 3".into(), "kw 4".into()]) +// .create() +// .await?; + +// Ok(()) +// }) +// } + +// #[test] +// fn updated_keywords() { +// async_wrapper(|env| async move { +// env.fake_release() +// .await +// .name("dummy") +// .version(V1) +// .keywords(vec!["kw 3".into(), "kw 4".into()]) +// .create() +// .await?; + +// let release_id = env +// .fake_release() +// .await +// .name("dummy") +// .version(V1) +// .keywords(vec!["kw 1".into(), "kw 2".into()]) +// .create() +// .await?; + +// let mut conn = env.async_db().async_conn().await; +// let kw_r = sqlx::query!( +// r#"SELECT +// kw.name as "name!", +// kw.slug as "slug!" +// FROM keywords as kw +// INNER JOIN keyword_rels as kwr on kw.id = kwr.kid +// WHERE kwr.rid = $1 +// ORDER BY kw.name,kw.slug"#, +// release_id.0 +// ) +// .fetch_all(&mut *conn) +// .await? +// .into_iter() +// .map(|row| (row.name, row.slug)) +// .collect::>(); + +// assert_eq!(kw_r[0], ("kw 1".into(), "kw-1".into())); +// assert_eq!(kw_r[1], ("kw 2".into(), "kw-2".into())); + +// let all_kw = sqlx::query!("SELECT slug FROM keywords ORDER BY slug") +// .fetch_all(&mut *conn) +// .await? +// .into_iter() +// .map(|row| row.slug) +// .collect::>(); + +// assert_eq!( +// all_kw, +// vec![ +// String::from("kw-1"), +// "kw-2".into(), +// "kw-3".into(), +// "kw-4".into(), +// ] +// ); + +// Ok(()) +// }) +// } + +// #[test] +// fn new_owner_long_avatar() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; + +// let owner1 = CrateOwner { +// avatar: "avatar".repeat(100), +// login: "login".into(), +// kind: OwnerKind::User, +// }; + +// update_owners_in_database(&mut conn, slice::from_ref(&owner1), crate_id).await?; + +// let owner_def = sqlx::query!( +// r#"SELECT login, avatar, kind as "kind: OwnerKind" +// FROM owners"# +// ) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!(owner_def.login, owner1.login); +// assert_eq!(owner_def.avatar, owner1.avatar); +// assert_eq!(owner_def.kind, owner1.kind); + +// let owner_rel = sqlx::query!( +// "SELECT o.login +// FROM owners o, owner_rels r +// WHERE +// o.id = r.oid AND +// r.cid = $1", +// crate_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!(owner_rel.login, owner1.login); + +// Ok(()) +// }) +// } + +// #[test] +// fn new_owners() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; + +// let owner1 = CrateOwner { +// avatar: "avatar".into(), +// login: "login".into(), +// kind: OwnerKind::User, +// }; + +// update_owners_in_database(&mut conn, slice::from_ref(&owner1), crate_id).await?; + +// let owner_def = sqlx::query!( +// r#"SELECT login, avatar, kind as "kind: OwnerKind" +// FROM owners"# +// ) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!(owner_def.login, owner1.login); +// assert_eq!(owner_def.avatar, owner1.avatar); +// assert_eq!(owner_def.kind, owner1.kind); + +// let owner_rel = sqlx::query!( +// "SELECT o.login +// FROM owners o, owner_rels r +// WHERE +// o.id = r.oid AND +// r.cid = $1", +// crate_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!(owner_rel.login, owner1.login); + +// Ok(()) +// }) +// } + +// #[test] +// fn update_owner_details() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; + +// // set initial owner details +// update_owners_in_database( +// &mut conn, +// &[CrateOwner { +// login: "login".into(), +// avatar: "avatar".into(), +// kind: OwnerKind::User, +// }], +// crate_id, +// ) +// .await?; + +// let updated_owner = CrateOwner { +// login: "login".into(), +// avatar: "avatar2".into(), +// kind: OwnerKind::Team, +// }; +// update_owners_in_database(&mut conn, slice::from_ref(&updated_owner), crate_id).await?; + +// let owner_def = +// sqlx::query!(r#"SELECT login, avatar, kind as "kind: OwnerKind" FROM owners"#) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!(owner_def.login, updated_owner.login); +// assert_eq!(owner_def.avatar, updated_owner.avatar); +// assert_eq!(owner_def.kind, updated_owner.kind); + +// let owner_rel = sqlx::query!( +// "SELECT o.login +// FROM owners o, owner_rels r +// WHERE +// o.id = r.oid AND +// r.cid = $1", +// crate_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; +// assert_eq!(owner_rel.login, updated_owner.login); + +// Ok(()) +// }) +// } + +// #[test] +// fn add_new_owners_and_delete_old() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, "krate").await?; + +// // set initial owner details +// update_owners_in_database( +// &mut conn, +// &[CrateOwner { +// login: "login".into(), +// avatar: "avatar".into(), +// kind: OwnerKind::User, +// }], +// crate_id, +// ) +// .await?; + +// let new_owners: Vec = (1..5) +// .map(|i| CrateOwner { +// login: format!("login{i}"), +// avatar: format!("avatar{i}"), +// kind: OwnerKind::User, +// }) +// .collect(); + +// update_owners_in_database(&mut conn, &new_owners, crate_id).await?; + +// let all_owners: Vec = sqlx::query!("SELECT login FROM owners order by login") +// .fetch(&mut *conn) +// .map_ok(|row| row.login) +// .try_collect() +// .await?; + +// // we still have all owners in the database. +// assert_eq!( +// all_owners, +// vec!["login", "login1", "login2", "login3", "login4"] +// ); + +// let crate_owners: Vec = sqlx::query!( +// "SELECT o.login +// FROM owners o, owner_rels r +// WHERE +// o.id = r.oid AND +// r.cid = $1", +// crate_id.0, +// ) +// .fetch(&mut *conn) +// .map_ok(|row| row.login) +// .try_collect() +// .await?; + +// // the owner-rel is deleted +// assert_eq!(crate_owners, vec!["login1", "login2", "login3", "login4"]); + +// Ok(()) +// }) +// } + +// #[test_case("", [])] +// #[test_case( +// r#" +// [features] +// bar = [] +// "#, +// [Feature::new("bar".into(), vec![])] +// )] +// #[test_case( +// r#" +// [dependencies] +// bar = { optional = true, path = "bar" } +// "#, +// [Feature::new("bar".into(), vec!["dep:bar".into()])] +// )] +// #[test_case( +// r#" +// [dependencies] +// bar = { optional = true, path = "bar" } +// [features] +// not-bar = ["dep:bar"] +// "#, +// [Feature::new("not-bar".into(), vec!["dep:bar".into()])] +// )] +// fn test_get_features(extra: &str, expected: impl AsRef<[Feature]>) -> Result<()> { +// let dir = tempfile::tempdir()?; + +// std::fs::create_dir(dir.path().join("src"))?; +// std::fs::write(dir.path().join("src/lib.rs"), "")?; + +// std::fs::create_dir(dir.path().join("bar"))?; +// std::fs::create_dir(dir.path().join("bar/src"))?; +// std::fs::write(dir.path().join("bar/src/lib.rs"), "")?; + +// std::fs::write( +// dir.path().join("bar/Cargo.toml"), +// r#" +// [package] +// name = "bar" +// version = "0.0.0" +// "#, +// )?; + +// let base = r#" +// [package] +// name = "foo" +// version = "0.0.0" +// "#; + +// std::fs::write(dir.path().join("Cargo.toml"), [base, extra].concat())?; +// let metadata = CargoMetadata::load_from_host_path(dir.path())?; +// let features = super::get_features(metadata.root()); +// assert_eq!(features, expected.as_ref()); + +// Ok(()) +// } + +// #[test] +// fn test_initialize_crate() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// let name = "krate"; +// let crate_id = initialize_crate(&mut conn, name).await?; + +// let id = sqlx::query_scalar!( +// r#"SELECT id as "id: CrateId" FROM crates WHERE name = $1"#, +// name +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(crate_id, id); + +// let same_crate_id = initialize_crate(&mut conn, name).await?; +// assert_eq!(crate_id, same_crate_id); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_initialize_release() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let name = "krate"; +// let crate_id = initialize_crate(&mut conn, name).await?; + +// let release_id = initialize_release(&mut conn, crate_id, &V1).await?; + +// let id = sqlx::query_scalar!( +// r#"SELECT id as "id: ReleaseId" FROM releases WHERE crate_id = $1 and version = $2"#, +// crate_id.0, +// V1 as _, +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(release_id, id); + +// let same_release_id = initialize_release(&mut conn, crate_id, &V1).await?; +// assert_eq!(release_id, same_release_id); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_initialize_build() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; +// let name = "krate"; +// let crate_id = initialize_crate(&mut conn, name).await?; +// let release_id = initialize_release(&mut conn, crate_id, &V1).await?; + +// let build_id = initialize_build(&mut conn, release_id).await?; + +// let id = sqlx::query_scalar!( +// r#"SELECT id as "id: BuildId" FROM builds WHERE rid = $1"#, +// release_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(build_id, id); + +// let another_build_id = initialize_build(&mut conn, release_id).await?; +// assert_ne!(build_id, another_build_id); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_long_crate_name() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// let name: String = "krate".repeat(100); +// let crate_id = initialize_crate(&mut conn, &name).await?; + +// let db_name = sqlx::query_scalar!("SELECT name FROM crates WHERE id = $1", crate_id.0) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(db_name, name); + +// Ok(()) +// }) +// } + +// #[test] +// fn test_long_release_version() { +// async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// let crate_id = initialize_crate(&mut conn, "krate").await?; +// let version = Version::parse(&format!( +// "1.2.3-{}+{}", +// "prerelease".repeat(100), +// "build".repeat(100) +// ))?; +// let release_id = initialize_release(&mut conn, crate_id, &version).await?; + +// let db_version = sqlx::query_scalar!( +// r#" +// SELECT +// version as "version: Version" +// FROM releases +// WHERE id = $1"#, +// release_id.0 +// ) +// .fetch_one(&mut *conn) +// .await?; + +// assert_eq!(db_version, version); + +// Ok(()) +// }) +// } +// } diff --git a/crates/docs_rs_builder/src/db/blacklist.rs b/crates/docs_rs_builder/src/db/blacklist.rs new file mode 100644 index 000000000..0887f7a06 --- /dev/null +++ b/crates/docs_rs_builder/src/db/blacklist.rs @@ -0,0 +1,123 @@ +use anyhow::Result; +use futures_util::stream::TryStreamExt; + +#[derive(Debug, thiserror::Error)] +enum BlacklistError { + #[error("crate {0} is already on the blacklist")] + CrateAlreadyOnBlacklist(String), + + #[error("crate {0} is not on the blacklist")] + CrateNotOnBlacklist(String), +} + +/// Returns whether the given name is blacklisted. +pub async fn is_blacklisted(conn: &mut sqlx::PgConnection, name: &str) -> Result { + Ok(sqlx::query_scalar!( + r#"SELECT COUNT(*) as "count!" FROM blacklisted_crates WHERE crate_name = $1;"#, + name + ) + .fetch_one(conn) + .await? + != 0) +} + +/// Returns the crate names on the blacklist, sorted ascending. +pub async fn list_crates(conn: &mut sqlx::PgConnection) -> Result> { + Ok( + sqlx::query!("SELECT crate_name FROM blacklisted_crates ORDER BY crate_name asc;") + .fetch(conn) + .map_ok(|row| row.crate_name) + .try_collect() + .await?, + ) +} + +/// Adds a crate to the blacklist. +pub async fn add_crate(conn: &mut sqlx::PgConnection, name: &str) -> Result<()> { + if is_blacklisted(&mut *conn, name).await? { + return Err(BlacklistError::CrateAlreadyOnBlacklist(name.into()).into()); + } + + sqlx::query!( + "INSERT INTO blacklisted_crates (crate_name) VALUES ($1);", + name + ) + .execute(conn) + .await?; + + Ok(()) +} + +/// Removes a crate from the blacklist. +pub async fn remove_crate(conn: &mut sqlx::PgConnection, name: &str) -> Result<()> { + if !is_blacklisted(conn, name).await? { + return Err(BlacklistError::CrateNotOnBlacklist(name.into()).into()); + } + + sqlx::query!( + "DELETE FROM blacklisted_crates WHERE crate_name = $1;", + name + ) + .execute(conn) + .await?; + + Ok(()) +} + +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn test_list_blacklist() { +// crate::test::async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// // crates are added out of order to verify sorting +// add_crate(&mut conn, "crate A").await?; +// add_crate(&mut conn, "crate C").await?; +// add_crate(&mut conn, "crate B").await?; + +// assert!(list_crates(&mut conn).await? == vec!["crate A", "crate B", "crate C"]); +// Ok(()) +// }); +// } + +// #[test] +// fn test_add_to_and_remove_from_blacklist() { +// crate::test::async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// assert!(!is_blacklisted(&mut conn, "crate foo").await?); +// add_crate(&mut conn, "crate foo").await?; +// assert!(is_blacklisted(&mut conn, "crate foo").await?); +// remove_crate(&mut conn, "crate foo").await?; +// assert!(!is_blacklisted(&mut conn, "crate foo").await?); +// Ok(()) +// }); +// } + +// #[test] +// fn test_add_twice_to_blacklist() { +// crate::test::async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// add_crate(&mut conn, "crate foo").await?; +// assert!(add_crate(&mut conn, "crate foo").await.is_err()); +// add_crate(&mut conn, "crate bar").await?; + +// Ok(()) +// }); +// } + +// #[test] +// fn test_remove_non_existing_crate() { +// crate::test::async_wrapper(|env| async move { +// let mut conn = env.async_db().async_conn().await; + +// assert!(remove_crate(&mut conn, "crate foo").await.is_err()); + +// Ok(()) +// }); +// } +// } diff --git a/crates/docs_rs_builder/src/db/mod.rs b/crates/docs_rs_builder/src/db/mod.rs new file mode 100644 index 000000000..aaa1e81f0 --- /dev/null +++ b/crates/docs_rs_builder/src/db/mod.rs @@ -0,0 +1,3 @@ +//! Database operations +pub(crate) mod add_package; +pub(crate) mod blacklist; diff --git a/crates/docs_rs_builder/src/docbuilder/mod.rs b/crates/docs_rs_builder/src/docbuilder/mod.rs new file mode 100644 index 000000000..7e5e0ff7a --- /dev/null +++ b/crates/docs_rs_builder/src/docbuilder/mod.rs @@ -0,0 +1 @@ +pub(crate) mod rustwide_builder; diff --git a/src/docbuilder/rustwide_builder.rs b/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs similarity index 97% rename from src/docbuilder/rustwide_builder.rs rename to crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs index 2bff7dce0..1b00f3eeb 100644 --- a/src/docbuilder/rustwide_builder.rs +++ b/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -1,30 +1,34 @@ use crate::{ - Config, Context, RUSTDOC_STATIC_STORAGE_PREFIX, RegistryApi, + config::Config, db::{ - add_doc_coverage, blacklist::is_blacklisted, finish_build, finish_release, - initialize_build, initialize_crate, initialize_release, types::BuildStatus, - update_build_with_error, update_crate_data_in_database, + add_package::{ + add_doc_coverage, finish_build, finish_release, initialize_build, initialize_crate, + initialize_release, update_build_with_error, update_crate_data_in_database, + }, + blacklist::is_blacklisted, }, - docbuilder::Limits, - error::Result, metrics::{BUILD_TIME_HISTOGRAM_BUCKETS, DOCUMENTATION_SIZE_BUCKETS}, - utils::{copy_dir_all, parse_rustc_version, report_error}, + utils::copy::copy_dir_all, }; -use anyhow::{Context as _, Error, anyhow, bail}; +use anyhow::{Context as _, Error, Result, anyhow, bail}; +use docs_rs_build_utils::limits::Limits; use docs_rs_cargo_metadata::{CargoMetadata, Package as MetadataPackage}; +use docs_rs_context::Context; use docs_rs_database::{ Pool, service_config::{ConfigName, get_config, set_config}, - types::{BuildId, CrateId, ReleaseId, version::Version}, + types::{BuildId, BuildStatus, CrateId, ReleaseId, version::Version}, }; use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_registry_api::RegistryApi; use docs_rs_storage::{ AsyncStorage, RustdocJsonFormatVersion, Storage, compress, compression::CompressionAlgorithm, file::{add_path_into_database, add_path_into_remote_archive, file_list_to_json}, get_file_list, rustdoc_archive_path, rustdoc_json_path, source_archive_path, }; -use docs_rs_utils::{BUILD_VERSION, retry}; +use docs_rs_utils::rustc_version::parse_rustc_version; +use docs_rs_utils::{BUILD_VERSION, RUSTDOC_STATIC_STORAGE_PREFIX, retry}; use docs_rs_watcher::repositories::RepositoryStatsUpdater; use docsrs_metadata::{BuildTargets, DEFAULT_TARGETS, HOST_TARGET, Metadata}; use itertools::Itertools as _; @@ -107,10 +111,10 @@ async fn get_configured_toolchain(conn: &mut sqlx::PgConnection) -> Result Result { - let mut builder = WorkspaceBuilder::new(&context.config.rustwide_workspace, USER_AGENT) - .running_inside_docker(context.config.inside_docker); - if let Some(custom_image) = &context.config.docker_image { +fn build_workspace(config: &Config) -> Result { + let mut builder = WorkspaceBuilder::new(&config.rustwide_workspace, USER_AGENT) + .running_inside_docker(config.inside_docker); + if let Some(custom_image) = &config.docker_image { let image = match SandboxImage::local(custom_image) { Ok(i) => i, Err(CommandError::SandboxImageMissing(_)) => SandboxImage::remote(custom_image)?, @@ -195,32 +199,32 @@ pub struct RustwideBuilder { } impl RustwideBuilder { - pub fn init(context: &Context) -> Result { - let toolchain = context.runtime.block_on(async { - let mut conn = context.pool.get_async().await?; + pub fn init(config: Config, context: &Context) -> Result { + let toolchain = context.runtime().block_on(async { + let mut conn = context.pool()?.get_async().await?; get_configured_toolchain(&mut conn).await })?; Ok(RustwideBuilder { - workspace: build_workspace(context)?, + workspace: build_workspace(&config)?, toolchain, - config: context.config.clone(), - db: context.pool.clone(), - runtime: context.runtime.clone(), - storage: context.storage.clone(), - async_storage: context.async_storage.clone(), - registry_api: context.registry_api.clone(), + config: config.into(), + db: context.pool()?.clone(), + runtime: context.runtime().clone(), + storage: context.blocking_storage()?, + async_storage: context.storage()?, + registry_api: RegistryApi::from_environment()?.into(), repository_stats_updater: context.repository_stats_updater.clone(), workspace_initialize_time: Instant::now(), - builder_metrics: BuilderMetrics::new(&context.meter_provider).into(), + builder_metrics: BuilderMetrics::new(context.meter_provider()).into(), }) } - pub fn reinitialize_workspace_if_interval_passed(&mut self, context: &Context) -> Result<()> { - let interval = context.config.build_workspace_reinitialization_interval; + pub fn reinitialize_workspace_if_interval_passed(&mut self) -> Result<()> { + let interval = self.config.build_workspace_reinitialization_interval; if self.workspace_initialize_time.elapsed() >= interval { info!("start reinitialize workspace again"); - self.workspace = build_workspace(context)?; + self.workspace = build_workspace(&self.config)?; self.workspace_initialize_time = Instant::now(); } @@ -522,13 +526,13 @@ impl RustwideBuilder { let static_files = dest.as_ref().join("static.files"); if static_files.try_exists()? { self.runtime.block_on(add_path_into_database( - &self.async_storage, + &self.storage, RUSTDOC_STATIC_STORAGE_PREFIX, &static_files, ))?; } else { self.runtime.block_on(add_path_into_database( - &self.async_storage, + &self.storage, RUSTDOC_STATIC_STORAGE_PREFIX, &dest, ))?; @@ -694,7 +698,7 @@ impl RustwideBuilder { let files_list = { let (files_list, new_alg) = self.runtime.block_on(add_path_into_remote_archive( - &self.async_storage, + &self.storage, &source_archive_path(name, version), build.host_source_dir(), ))?; @@ -789,7 +793,7 @@ impl RustwideBuilder { } let (file_list, new_alg) = self.runtime.block_on(add_path_into_remote_archive( - &self.async_storage, + &self.storage, &rustdoc_archive_path(name, version), local_storage.path(), ))?; diff --git a/crates/docs_rs_builder/src/lib.rs b/crates/docs_rs_builder/src/lib.rs index d30159c71..dab6ef783 100644 --- a/crates/docs_rs_builder/src/lib.rs +++ b/crates/docs_rs_builder/src/lib.rs @@ -1,3 +1,6 @@ mod config; -pub mod limits; -mod overrides; +mod metrics; + +mod db; +mod docbuilder; +mod utils; diff --git a/src/metrics/mod.rs b/crates/docs_rs_builder/src/metrics.rs similarity index 60% rename from src/metrics/mod.rs rename to crates/docs_rs_builder/src/metrics.rs index 667365871..356f059ba 100644 --- a/src/metrics/mod.rs +++ b/crates/docs_rs_builder/src/metrics.rs @@ -1,26 +1,4 @@ -/// the measured times from cdn invalidations, meaning: -/// * how long an invalidation took, or -/// * how long the invalidation was queued -/// -/// will be put into these buckets (seconds, -/// each entry is the upper bound). -/// Prometheus only gets the counts per bucket in a certain -/// time range, no exact durations. -pub const CDN_INVALIDATION_HISTOGRAM_BUCKETS: &[f64; 11] = &[ - 60.0, // 1 - 120.0, // 2 - 300.0, // 5 - 600.0, // 10 - 900.0, // 15 - 1200.0, // 20 - 1800.0, // 30 - 2700.0, // 45 - 6000.0, // 100 - 12000.0, // 200 - 24000.0, // 400 -]; - -/// buckets for documentation size, in MiB +//// buckets for documentation size, in MiB /// Base for some estimates: /// * `itertools` docs is an 8.2 MB archive with 144 MB of docs /// * the biggest doc archive know of (`stm32ral`) is an 1.8 GiB archive, diff --git a/src/utils/copy.rs b/crates/docs_rs_builder/src/utils/copy.rs similarity index 100% rename from src/utils/copy.rs rename to crates/docs_rs_builder/src/utils/copy.rs diff --git a/src/utils/mod.rs b/crates/docs_rs_builder/src/utils/mod.rs similarity index 71% rename from src/utils/mod.rs rename to crates/docs_rs_builder/src/utils/mod.rs index 72669d995..c6de243c8 100644 --- a/src/utils/mod.rs +++ b/crates/docs_rs_builder/src/utils/mod.rs @@ -1,35 +1,7 @@ //! Various utilities for docs.rs -pub(crate) use self::{ - copy::copy_dir_all, - html::rewrite_rustdoc_html_stream, - rustc_version::{get_correct_docsrs_style_file, parse_rustc_version}, -}; -pub use self::{ - daemon::start_daemon, - // queue::{ - // get_crate_pattern_and_priority, get_crate_priority, list_crate_priorities, - // remove_crate_priority, set_crate_priority, - // }, - queue_builder::queue_builder, -}; - -mod copy; -mod html; +pub(crate) mod copy; pub(crate) mod queue_builder; -pub(crate) mod rustc_version; - -use tracing::error; - -pub(crate) fn report_error(err: &anyhow::Error) { - // Debug-format for anyhow errors includes context & backtrace - if std::env::var("SENTRY_DSN").is_ok() { - sentry::integrations::anyhow::capture_anyhow(err); - error!(reported_to_sentry = true, "{err:?}"); - } else { - error!("{err:?}"); - } -} // #[cfg(test)] // mod tests { diff --git a/crates/docs_rs_builder/src/utils/queue_builder.rs b/crates/docs_rs_builder/src/utils/queue_builder.rs new file mode 100644 index 000000000..4afb1c70b --- /dev/null +++ b/crates/docs_rs_builder/src/utils/queue_builder.rs @@ -0,0 +1,75 @@ +use std::path::Path; + +use crate::config::Config; +use crate::docbuilder::rustwide_builder::RustwideBuilder; +use anyhow::{Context as _, Result}; +use docs_rs_context::Context; +use std::time::Duration; +use std::{fs, io, thread}; +use tracing::{error, warn}; + +/// the main build-server loop +pub fn queue_builder( + config: &Config, + context: &Context, + mut builder: RustwideBuilder, +) -> Result<()> { + loop { + let temp_dir = &config.temp_dir; + if temp_dir.exists() + && let Err(e) = remove_tempdirs(temp_dir) + { + error!(?e, temp_dir=%temp_dir.display(), "failed to clean temporary directory"); + } + + let build_queue = &context.blocking_build_queue()?; + + // check lock file + match build_queue.is_locked().context("could not get queue lock") { + Ok(true) => { + warn!("Build queue is locked, skipping building new crates"); + thread::sleep(Duration::from_secs(60)); + continue; + } + Ok(false) => {} + Err(err) => { + error!(?err, "could not get queue lock"); + thread::sleep(Duration::from_secs(60)); + continue; + } + } + + //TODO: Build this with a new way + + // // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. + // debug!("Checking build queue"); + // let res = catch_unwind(AssertUnwindSafe(|| { + // match build_queue.build_next_queue_package(context, &mut builder) { + // Ok(true) => {} + // Ok(false) => { + // debug!("Queue is empty, going back to sleep"); + // thread::sleep(Duration::from_secs(60)); + // } + // Err(e) => { + // report_error(&e.context("Failed to build crate from queue")); + // } + // } + // })); + + // if let Err(e) = res { + // error!("GRAVE ERROR Building new crates panicked: {:?}", e); + // thread::sleep(Duration::from_secs(60)); + // continue; + // } + return Ok(()); + } +} + +/// Sometimes, when the server hits a hard crash or a build thread panics, +/// rustwide_builder won't actually remove the temporary directories it creates. +/// Remove them now to avoid running out of disk space. +fn remove_tempdirs>(path: P) -> Result<(), io::Error> { + fs::remove_dir_all(&path)?; + fs::create_dir_all(&path)?; + Ok(()) +} diff --git a/src/utils/version.rs b/crates/docs_rs_builder/src/utils/version.rs similarity index 100% rename from src/utils/version.rs rename to crates/docs_rs_builder/src/utils/version.rs diff --git a/crates/docs_rs_cargo_metadata/src/db.rs b/crates/docs_rs_cargo_metadata/src/db.rs index 116686db6..636a4aa0f 100644 --- a/crates/docs_rs_cargo_metadata/src/db.rs +++ b/crates/docs_rs_cargo_metadata/src/db.rs @@ -7,7 +7,7 @@ const DEFAULT_KIND: &str = "normal"; /// A crate dependency in our internal representation for releases.dependencies json. #[derive(Debug, Clone, PartialEq, Deref)] -pub(crate) struct ReleaseDependency(Dependency); +pub struct ReleaseDependency(Dependency); impl<'de> Deserialize<'de> for ReleaseDependency { fn deserialize(deserializer: D) -> Result @@ -67,60 +67,60 @@ impl From for Dependency { } } -pub(crate) type ReleaseDependencyList = Vec; +pub type ReleaseDependencyList = Vec; -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - use test_case::test_case; +// #[cfg(test)] +// mod tests { +// use super::*; +// use anyhow::Result; +// use test_case::test_case; - #[test_case("[]", "[]"; "empty")] - #[test_case( - r#"[["vec_map", "^0.0.1"]]"#, - r#"[["vec_map","^0.0.1","normal",false]]"#; - "2-tuple" - )] - #[test_case( - r#"[["vec_map", "^0.0.1", "normal" ]]"#, - r#"[["vec_map","^0.0.1","normal",false]]"#; - "3-tuple" - )] - #[test_case( - r#"[["rand", "^0.9", "normal", false], ["sdl3", "^0.16", "normal", false]]"#, - r#"[["rand","^0.9","normal",false],["sdl3","^0.16","normal",false]]"#; - "4-tuple" - )] - #[test_case( - r#"[["byteorder", "^0.5", "normal", false],["clippy", "^0", "normal", true]]"#, - r#"[["byteorder","^0.5","normal",false],["clippy","^0","normal",true]]"#; - "with optional" - )] - fn test_parse_release_dependency_json(input: &str, output: &str) -> Result<()> { - let deps: ReleaseDependencyList = serde_json::from_str(input)?; +// #[test_case("[]", "[]"; "empty")] +// #[test_case( +// r#"[["vec_map", "^0.0.1"]]"#, +// r#"[["vec_map","^0.0.1","normal",false]]"#; +// "2-tuple" +// )] +// #[test_case( +// r#"[["vec_map", "^0.0.1", "normal" ]]"#, +// r#"[["vec_map","^0.0.1","normal",false]]"#; +// "3-tuple" +// )] +// #[test_case( +// r#"[["rand", "^0.9", "normal", false], ["sdl3", "^0.16", "normal", false]]"#, +// r#"[["rand","^0.9","normal",false],["sdl3","^0.16","normal",false]]"#; +// "4-tuple" +// )] +// #[test_case( +// r#"[["byteorder", "^0.5", "normal", false],["clippy", "^0", "normal", true]]"#, +// r#"[["byteorder","^0.5","normal",false],["clippy","^0","normal",true]]"#; +// "with optional" +// )] +// fn test_parse_release_dependency_json(input: &str, output: &str) -> Result<()> { +// let deps: ReleaseDependencyList = serde_json::from_str(input)?; - assert_eq!(serde_json::to_string(&deps)?, output); - Ok(()) - } +// assert_eq!(serde_json::to_string(&deps)?, output); +// Ok(()) +// } - #[test_case(r#"[["vec_map", "^0.0.1"]]"#, "normal", false)] - #[test_case(r#"[["vec_map", "^0.0.1", "dev" ]]"#, "dev", false)] - #[test_case(r#"[["vec_map", "^0.0.1", "dev", true ]]"#, "dev", true)] - fn test_parse_dependency( - input: &str, - expected_kind: &str, - expected_optional: bool, - ) -> Result<()> { - let deps: ReleaseDependencyList = serde_json::from_str(input)?; - let [dep] = deps.as_slice() else { - panic!("expected exactly one dependency"); - }; +// #[test_case(r#"[["vec_map", "^0.0.1"]]"#, "normal", false)] +// #[test_case(r#"[["vec_map", "^0.0.1", "dev" ]]"#, "dev", false)] +// #[test_case(r#"[["vec_map", "^0.0.1", "dev", true ]]"#, "dev", true)] +// fn test_parse_dependency( +// input: &str, +// expected_kind: &str, +// expected_optional: bool, +// ) -> Result<()> { +// let deps: ReleaseDependencyList = serde_json::from_str(input)?; +// let [dep] = deps.as_slice() else { +// panic!("expected exactly one dependency"); +// }; - assert_eq!(dep.name, "vec_map"); - assert_eq!(dep.req, VersionReq::parse("^0.0.1")?); - assert_eq!(dep.kind.as_deref(), Some(expected_kind)); - assert_eq!(dep.optional, expected_optional); +// assert_eq!(dep.name, "vec_map"); +// assert_eq!(dep.req, VersionReq::parse("^0.0.1")?); +// assert_eq!(dep.kind.as_deref(), Some(expected_kind)); +// assert_eq!(dep.optional, expected_optional); - Ok(()) - } -} +// Ok(()) +// } +// } diff --git a/crates/docs_rs_cargo_metadata/src/lib.rs b/crates/docs_rs_cargo_metadata/src/lib.rs index e3c173118..02b63effc 100644 --- a/crates/docs_rs_cargo_metadata/src/lib.rs +++ b/crates/docs_rs_cargo_metadata/src/lib.rs @@ -1,4 +1,4 @@ -mod db; +pub mod db; use anyhow::{Context, Result, bail}; use docs_rs_database::types::version::Version; diff --git a/crates/docs_rs_context/Cargo.toml b/crates/docs_rs_context/Cargo.toml index 54ed64cfc..cd493c627 100644 --- a/crates/docs_rs_context/Cargo.toml +++ b/crates/docs_rs_context/Cargo.toml @@ -8,3 +8,5 @@ anyhow = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } +tokio = { workspace = true } +docs_rs_storage = { path = "../docs_rs_storage" } diff --git a/crates/docs_rs_context/src/lib.rs b/crates/docs_rs_context/src/lib.rs index 1517663c3..295f6c642 100644 --- a/crates/docs_rs_context/src/lib.rs +++ b/crates/docs_rs_context/src/lib.rs @@ -1,23 +1,54 @@ use anyhow::{Result, anyhow}; -use docs_rs_build_queue::AsyncBuildQueue; +use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; use docs_rs_database::Pool; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; +use docs_rs_storage::{AsyncStorage, Storage}; use std::sync::Arc; +use tokio::runtime::Handle; + +#[derive(Debug, Default)] +pub struct Config { + opentelemetry: Option>, + build_queue: Option>, + database: Option>, + storage: Option>, +} pub struct Context { meter_provider: AnyMeterProvider, + pool: Option, + build_queue: Option>, + blocking_build_queue: Option>, + + storage: Option>, + blocking_storage: Option>, + + runtime: Handle, + config: Config, } // builder impl Context { pub fn new() -> Result { + Self::new_with_runtime(Handle::try_current()?) + } + + pub fn new_with_runtime(runtime: Handle) -> Result { let config = docs_rs_opentelemetry::Config::from_environment()?; Ok(Context { meter_provider: get_meter_provider(&config)?, + runtime, + config: Config { + opentelemetry: Some(Arc::new(config)), + ..Default::default() + }, pool: None, build_queue: None, + blocking_build_queue: None, + storage: None, + blocking_storage: None, }) } @@ -28,6 +59,7 @@ impl Context { let config = docs_rs_database::Config::from_environment()?; let pool = Pool::new(&config, &self.meter_provider).await?; + self.config.database = Some(Arc::new(config)); self.pool = Some(pool); Ok(self) } @@ -42,8 +74,29 @@ impl Context { let pool = self.pool()?; let config = docs_rs_build_queue::Config::from_environment()?; - let build_queue = AsyncBuildQueue::new(pool, &config, &self.meter_provider); - self.build_queue = Some(Arc::new(build_queue)); + let build_queue = Arc::new(AsyncBuildQueue::new(pool, &config, &self.meter_provider)); + let blocking_build_queue = + Arc::new(BuildQueue::new(self.runtime.clone(), build_queue.clone())); + + self.config.build_queue = Some(Arc::new(config)); + self.build_queue = Some(build_queue); + self.blocking_build_queue = Some(blocking_build_queue); + Ok(self) + } + + pub async fn with_storage(mut self) -> Result { + if self.storage.is_some() { + return Ok(self); + } + + self = self.with_pool().await?; + let pool = self.pool()?; + + let config = Arc::new(docs_rs_storage::Config::from_environment()?); + let storage = + Arc::new(AsyncStorage::new(pool, config.clone(), &self.meter_provider).await?); + self.config.storage = Some(config); + self.storage = Some(storage); Ok(self) } } @@ -54,6 +107,10 @@ impl Context { &self.meter_provider } + pub fn runtime(&self) -> &Handle { + &self.runtime + } + pub fn pool(&self) -> Result { if let Some(ref pool) = self.pool { Ok(pool.clone()) @@ -62,6 +119,22 @@ impl Context { } } + pub fn storage(&self) -> Result> { + if let Some(ref storage) = self.storage { + Ok(storage.clone()) + } else { + Err(anyhow!("Storage is not initialized")) + } + } + + pub fn blocking_storage(&self) -> Result> { + if let Some(ref storage) = self.blocking_storage { + Ok(storage.clone()) + } else { + Err(anyhow!("blocking Storage is not initialized")) + } + } + pub fn build_queue(&self) -> Result> { if let Some(ref build_queue) = self.build_queue { Ok(build_queue.clone()) @@ -69,4 +142,12 @@ impl Context { Err(anyhow!("Build queue is not initialized")) } } + + pub fn blocking_build_queue(&self) -> Result> { + if let Some(ref build_queue) = self.blocking_build_queue { + Ok(build_queue.clone()) + } else { + Err(anyhow!("blocking Build queue is not initialized")) + } + } } diff --git a/crates/docs_rs_database/Cargo.toml b/crates/docs_rs_database/Cargo.toml index 1f9e33d94..80c4a2d9c 100644 --- a/crates/docs_rs_database/Cargo.toml +++ b/crates/docs_rs_database/Cargo.toml @@ -11,6 +11,7 @@ derive_more = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } futures-util = { workspace = true } +hex = "0.4.3" mime = { workspace = true } mime_guess = "2" opentelemetry = { workspace = true } diff --git a/crates/docs_rs_database/src/lib.rs b/crates/docs_rs_database/src/lib.rs index 89a6c76dd..4ffe6f76b 100644 --- a/crates/docs_rs_database/src/lib.rs +++ b/crates/docs_rs_database/src/lib.rs @@ -1,4 +1,5 @@ mod config; +pub(crate) mod migrate; pub mod mimes; pub mod service_config; pub mod types; diff --git a/src/db/mod.rs b/crates/docs_rs_database/src/migrate.rs similarity index 83% rename from src/db/mod.rs rename to crates/docs_rs_database/src/migrate.rs index 62ac3baf8..09d5eb98f 100644 --- a/src/db/mod.rs +++ b/crates/docs_rs_database/src/migrate.rs @@ -1,22 +1,7 @@ -//! Database operations use anyhow::Result; +use hex; use sqlx::migrate::{Migrate, Migrator}; -pub use self::add_package::update_latest_version_id; -pub(crate) use self::add_package::{ - add_doc_coverage, finish_build, finish_release, initialize_build, initialize_crate, - initialize_release, update_build_with_error, -}; -pub use self::{ - add_package::{update_build_status, update_crate_data_in_database}, - overrides::Overrides, -}; - -mod add_package; -pub mod blacklist; -mod overrides; -pub mod types; - static MIGRATOR: Migrator = sqlx::migrate!(); pub async fn migrate(conn: &mut sqlx::PgConnection, target: Option) -> Result<()> { diff --git a/crates/docs_rs_registry_api/Cargo.toml b/crates/docs_rs_registry_api/Cargo.toml index ed0e2477f..d07d25bec 100644 --- a/crates/docs_rs_registry_api/Cargo.toml +++ b/crates/docs_rs_registry_api/Cargo.toml @@ -14,3 +14,4 @@ tracing = { workspace = true } url = { workspace = true } sqlx = { workspace = true } bincode = { workspace = true } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } diff --git a/crates/docs_rs_registry_api/src/config.rs b/crates/docs_rs_registry_api/src/config.rs new file mode 100644 index 000000000..69eb4b786 --- /dev/null +++ b/crates/docs_rs_registry_api/src/config.rs @@ -0,0 +1,22 @@ +use docs_rs_env_vars::env; +use url::Url; + +#[derive(Debug)] +pub struct Config { + pub registry_api_host: Url, + + // amount of retries for external API calls, mostly crates.io + pub crates_io_api_call_retries: u32, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + crates_io_api_call_retries: env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?, + registry_api_host: env( + "DOCSRS_REGISTRY_API_HOST", + "https://crates.io".parse().unwrap(), + )?, + }) + } +} diff --git a/crates/docs_rs_registry_api/src/lib.rs b/crates/docs_rs_registry_api/src/lib.rs index 7b1e0871a..3435be4e2 100644 --- a/crates/docs_rs_registry_api/src/lib.rs +++ b/crates/docs_rs_registry_api/src/lib.rs @@ -1,3 +1,6 @@ +mod config; + +use crate::config::Config; use anyhow::{Context, Result, anyhow, bail}; use chrono::{DateTime, Utc}; use docs_rs_database::types::version::Version; @@ -17,14 +20,14 @@ pub struct RegistryApi { #[derive(Debug)] pub struct CrateData { - pub(crate) owners: Vec, + pub owners: Vec, } #[derive(Debug)] -pub(crate) struct ReleaseData { - pub(crate) release_time: DateTime, - pub(crate) yanked: bool, - pub(crate) downloads: i32, +pub struct ReleaseData { + pub release_time: DateTime, + pub yanked: bool, + pub downloads: i32, } impl Default for ReleaseData { @@ -39,9 +42,9 @@ impl Default for ReleaseData { #[derive(Debug, Clone)] pub struct CrateOwner { - pub(crate) avatar: String, - pub(crate) login: String, - pub(crate) kind: OwnerKind, + pub avatar: String, + pub login: String, + pub kind: OwnerKind, } #[derive( @@ -75,24 +78,35 @@ impl fmt::Display for OwnerKind { #[derive(Deserialize, Debug)] -pub(crate) struct SearchCrate { - pub(crate) name: String, +pub struct SearchCrate { + pub name: String, } #[derive(Deserialize, Debug)] -pub(crate) struct SearchMeta { - pub(crate) next_page: Option, - pub(crate) prev_page: Option, +pub struct SearchMeta { + pub next_page: Option, + pub prev_page: Option, } #[derive(Deserialize, Debug)] -pub(crate) struct Search { - pub(crate) crates: Vec, - pub(crate) meta: SearchMeta, +pub struct Search { + pub crates: Vec, + pub meta: SearchMeta, } impl RegistryApi { + pub fn from_environment() -> Result { + Self::from_config(&Config::from_environment()?) + } + + pub fn from_config(config: &config::Config) -> Result { + Self::new( + config.registry_api_host.clone(), + config.crates_io_api_call_retries, + ) + } + pub fn new(api_base: Url, max_retries: u32) -> Result { let headers = vec![ (USER_AGENT, HeaderValue::from_static(APP_USER_AGENT)), @@ -123,11 +137,7 @@ impl RegistryApi { } #[instrument(skip(self))] - pub(crate) async fn get_release_data( - &self, - name: &str, - version: &Version, - ) -> Result { + pub async fn get_release_data(&self, name: &str, version: &Version) -> Result { let (release_time, yanked, downloads) = self .get_release_time_yanked_downloads(name, version) .await @@ -255,7 +265,7 @@ impl RegistryApi { } /// Fetch crates from the registry's API - pub(crate) async fn search(&self, query_params: &str) -> Result { + pub async fn search(&self, query_params: &str) -> Result { #[derive(Deserialize, Debug)] struct SearchError { detail: String, diff --git a/crates/docs_rs_utils/Cargo.toml b/crates/docs_rs_utils/Cargo.toml index 9176844af..5dce8b6d9 100644 --- a/crates/docs_rs_utils/Cargo.toml +++ b/crates/docs_rs_utils/Cargo.toml @@ -8,6 +8,8 @@ build = "build.rs" anyhow = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +chrono = { workspace = true } +regex = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/crates/docs_rs_utils/src/lib.rs b/crates/docs_rs_utils/src/lib.rs index 90c8f780c..e665134ab 100644 --- a/crates/docs_rs_utils/src/lib.rs +++ b/crates/docs_rs_utils/src/lib.rs @@ -1,3 +1,5 @@ +pub mod rustc_version; + use anyhow::Result; use std::{panic, thread, time::Duration}; use tokio::runtime; diff --git a/crates/docs_rs_utils/src/rustc_version.rs b/crates/docs_rs_utils/src/rustc_version.rs new file mode 100644 index 000000000..0f46fb76d --- /dev/null +++ b/crates/docs_rs_utils/src/rustc_version.rs @@ -0,0 +1,82 @@ +use anyhow::{Context as _, Result, anyhow}; +use chrono::prelude::*; +use regex::Regex; +use std::sync::LazyLock; + +/// Parses rustc commit hash from rustc version string +pub fn parse_rustc_version>(version: S) -> Result { + let version_regex = Regex::new(r" ([\w.-]+) \((\w+) (\d+)-(\d+)-(\d+)\)")?; + let captures = version_regex + .captures(version.as_ref()) + .with_context(|| anyhow!("Failed to parse rustc version '{}'", version.as_ref()))?; + + Ok(format!( + "{}{}{}-{}-{}", + captures.get(3).unwrap().as_str(), + captures.get(4).unwrap().as_str(), + captures.get(5).unwrap().as_str(), + captures.get(1).unwrap().as_str(), + captures.get(2).unwrap().as_str() + )) +} + +pub fn parse_rustc_date>(version: S) -> Result { + static RE: LazyLock = LazyLock::new(|| Regex::new(r" (\d+)-(\d+)-(\d+)\)$").unwrap()); + + let cap = RE + .captures(version.as_ref()) + .with_context(|| anyhow!("Failed to parse rustc date"))?; + + let year = cap.get(1).unwrap().as_str(); + let month = cap.get(2).unwrap().as_str(); + let day = cap.get(3).unwrap().as_str(); + + NaiveDate::from_ymd_opt( + year.parse::().unwrap(), + month.parse::().unwrap(), + day.parse::().unwrap(), + ) + .ok_or_else(|| anyhow!("date out of range")) +} + +/// Picks the correct "rustdoc.css" static file depending on which rustdoc version was used to +/// generate this version of this crate. +pub fn get_correct_docsrs_style_file(version: &str) -> Result { + let date = parse_rustc_date(version)?; + // This is the date where https://github.com/rust-lang/rust/pull/144476 was merged. + if NaiveDate::from_ymd_opt(2025, 8, 20).unwrap() < date { + Ok("rustdoc-2025-08-20.css".to_owned()) + // This is the date where https://github.com/rust-lang/rust/pull/91356 was merged. + } else if NaiveDate::from_ymd_opt(2021, 12, 5).unwrap() < date { + // If this is the new rustdoc layout, we need the newer docs.rs CSS file. + Ok("rustdoc-2021-12-05.css".to_owned()) + } else { + // By default, we return the old docs.rs CSS file. + Ok("rustdoc.css".to_owned()) + } +} + +#[test] +fn test_parse_rustc_version() { + assert_eq!( + parse_rustc_version("rustc 1.10.0-nightly (57ef01513 2016-05-23)").unwrap(), + "20160523-1.10.0-nightly-57ef01513" + ); + assert_eq!( + parse_rustc_version("docsrs 0.2.0 (ba9ae23 2016-05-26)").unwrap(), + "20160526-0.2.0-ba9ae23" + ); +} + +#[test] +fn test_get_correct_docsrs_style_file() { + assert_eq!( + get_correct_docsrs_style_file("rustc 1.10.0-nightly (57ef01513 2016-05-23)").unwrap(), + "rustdoc.css" + ); + assert_eq!( + get_correct_docsrs_style_file("docsrs 0.2.0 (ba9ae23 2022-05-26)").unwrap(), + "rustdoc-2021-12-05.css" + ); + assert!(get_correct_docsrs_style_file("docsrs 0.2.0").is_err(),); +} diff --git a/crates/docs_rs_web/src/lib.rs b/crates/docs_rs_web/src/lib.rs index 8a2d1e9bd..6f26da711 100644 --- a/crates/docs_rs_web/src/lib.rs +++ b/crates/docs_rs_web/src/lib.rs @@ -16,6 +16,7 @@ pub use font_awesome_as_a_crate::icons; use anyhow::{Context as _, Result, anyhow, bail}; use askama::Template; use axum_extra::middleware::option_layer; +use docs_rs_context::Context; use docs_rs_database::types::{CrateId, krate_name::KrateName, version::Version}; use serde::Serialize; use serde_json::Value; @@ -473,7 +474,7 @@ async fn set_sentry_transaction_name_from_axum_route( async fn apply_middleware( router: AxumRouter, - // context: &Context, + context: &Context, template_data: Option>, ) -> Result { let has_templates = template_data.is_some(); diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 1187d133a..000000000 --- a/src/config.rs +++ /dev/null @@ -1,123 +0,0 @@ -// use anyhow::{Result, bail}; -// use docs_rs_env_vars::{env, maybe_env, require_env}; -// use std::{path::PathBuf, sync::Arc, time::Duration}; -// use url::Url; - -// #[derive(Debug)] -// pub struct Config { -// pub prefix: PathBuf, - -// pub database: Arc, -// pub watcher: Arc, -// pub build_queue: Arc, -// pub storage: Arc, - -// // Access token for APIs for crates.io (careful: use -// // constant_time_eq for comparisons!) -// pub(crate) cratesio_token: Option, - -// // amount of retries for external API calls, mostly crates.io -// pub crates_io_api_call_retries: u32, - -// // request timeout in seconds -// pub(crate) request_timeout: Option, -// pub(crate) report_request_timeouts: bool, - -// // The most memory that can be used to parse an HTML file -// pub(crate) max_parse_memory: usize, - -// /// amount of threads for CPU intensive rendering -// pub(crate) render_threads: usize, - -// // random crate search generates a number of random IDs to -// // efficiently find a random crate with > 100 GH stars. -// // The amount depends on the ratio of crates with >100 stars -// // to the count of all crates. -// // At the time of creating this setting, it is set to -// // `500` for a ratio of 7249 over 54k crates. -// // For unit-tests the number has to be higher. -// pub(crate) random_crate_search_view_size: u32, - -// // Where to collect metrics for the metrics initiative. -// // When empty, we won't collect metrics. -// pub(crate) compiler_metrics_collection_path: Option, - -// // Content Security Policy -// pub(crate) csp_report_only: bool, - -// // Cache-Control header, for versioned URLs. -// // If both are absent, don't generate the header. If only one is present, -// // generate just that directive. Values are in seconds. -// pub(crate) cache_control_stale_while_revalidate: Option, - -// // Activate full page caching. -// // When disabled, we still cache static assets. -// // This only affects pages that depend on invalidations to work. -// pub(crate) cache_invalidatable_responses: bool, - -// pub(crate) build_workspace_reinitialization_interval: Duration, - -// // Build params -// pub(crate) build_attempts: u16, -// pub(crate) delay_between_build_attempts: Duration, -// pub(crate) rustwide_workspace: PathBuf, -// pub(crate) temp_dir: PathBuf, -// pub(crate) inside_docker: bool, -// pub(crate) docker_image: Option, -// pub(crate) build_cpu_limit: Option, -// pub(crate) build_default_memory_limit: Option, -// pub(crate) include_default_targets: bool, -// pub(crate) disable_memory_limit: bool, - -// pub(crate) opentelemetry: docs_rs_opentelemetry::Config, -// } - -// impl Config { -// pub fn from_env() -> Result { -// let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; -// let temp_dir = prefix.join("tmp"); - -// Ok(Self { -// prefix, -// database: docs_rs_database::Config::from_environment()?.into(), -// watcher: docs_rs_watcher::Config::from_environment()?.into(), -// build_queue: docs_rs_build_queue::Config::from_environment()?.into(), -// storage: docs_rs_storage::Config::from_environment()?.into(), -// opentelemetry: docs_rs_opentelemetry::Config::from_environment()?.into(), - -// build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, -// delay_between_build_attempts: Duration::from_secs(env::( -// "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", -// 60, -// )?), -// crates_io_api_call_retries: env("DOCSRS_CRATESIO_API_CALL_RETRIES", 3u32)?, -// cratesio_token: maybe_env("DOCSRS_CRATESIO_TOKEN")?, -// // LOL HTML only uses as much memory as the size of the start tag! -// // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 -// max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?, -// render_threads: env("DOCSRS_RENDER_THREADS", num_cpus::get())?, -// request_timeout: maybe_env::("DOCSRS_REQUEST_TIMEOUT")?.map(Duration::from_secs), -// report_request_timeouts: env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?, -// random_crate_search_view_size: env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?, -// csp_report_only: env("DOCSRS_CSP_REPORT_ONLY", false)?, -// cache_control_stale_while_revalidate: maybe_env( -// "CACHE_CONTROL_STALE_WHILE_REVALIDATE", -// )?, -// cache_invalidatable_responses: env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?, -// compiler_metrics_collection_path: maybe_env("DOCSRS_COMPILER_METRICS_PATH")?, -// temp_dir: temp_dir, -// rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, -// inside_docker: env("DOCSRS_DOCKER", false)?, -// docker_image: maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")? -// .or(maybe_env("DOCSRS_DOCKER_IMAGE")?), -// build_cpu_limit: maybe_env("DOCSRS_BUILD_CPU_LIMIT")?, -// build_default_memory_limit: maybe_env("DOCSRS_BUILD_DEFAULT_MEMORY_LIMIT")?, -// include_default_targets: env("DOCSRS_INCLUDE_DEFAULT_TARGETS", true)?, -// disable_memory_limit: env("DOCSRS_DISABLE_MEMORY_LIMIT", false)?, -// build_workspace_reinitialization_interval: Duration::from_secs(env( -// "DOCSRS_BUILD_WORKSPACE_REINITIALIZATION_INTERVAL", -// 86400, -// )?), -// }) -// } -// } diff --git a/src/context.rs b/src/context.rs deleted file mode 100644 index f909b6be1..000000000 --- a/src/context.rs +++ /dev/null @@ -1,84 +0,0 @@ -use crate::{Config, RegistryApi}; -use anyhow::Result; -use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; -use docs_rs_database::Pool; -use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; -use docs_rs_storage::{AsyncStorage, Storage}; -use docs_rs_watcher::repositories::RepositoryStatsUpdater; -use std::sync::Arc; -use tokio::runtime; - -pub struct Context { - pub config: Arc, - pub async_build_queue: Arc, - pub build_queue: Arc, - pub storage: Arc, - pub async_storage: Arc, - pub pool: Pool, - pub registry_api: Arc, - pub repository_stats_updater: Arc, - pub runtime: runtime::Handle, - pub meter_provider: AnyMeterProvider, -} - -impl Context { - /// Create a new context environment from the given configuration. - pub async fn from_config(config: Config) -> Result { - let meter_provider = get_meter_provider(&config.opentelemetry)?; - let pool = Pool::new(&config.database, &meter_provider).await?; - Self::from_config_with_metrics_and_pool(config, meter_provider, pool).await - } - - /// Create a new context environment from the given configuration, for running tests. - #[cfg(test)] - pub async fn from_test_config( - config: Config, - meter_provider: AnyMeterProvider, - pool: Pool, - ) -> Result { - Self::from_config_with_metrics_and_pool(config, meter_provider, pool).await - } - - /// private function for context environment generation, allows passing in a - /// preconfigured instance metrics & pool from the database. - /// Mostly so we can support test environments with their db - async fn from_config_with_metrics_and_pool( - config: Config, - meter_provider: AnyMeterProvider, - pool: Pool, - ) -> Result { - let config = Arc::new(config); - - let async_storage = Arc::new( - AsyncStorage::new(pool.clone(), config.storage.clone(), &meter_provider).await?, - ); - - let async_build_queue = Arc::new(AsyncBuildQueue::new( - pool.clone(), - &config.build_queue, - &meter_provider, - )); - - let runtime = runtime::Handle::current(); - - // sync wrappers around build-queue & storage async resources - let build_queue = Arc::new(BuildQueue::new(runtime.clone(), async_build_queue.clone())); - let storage = Arc::new(Storage::new(async_storage.clone(), runtime.clone())); - - Ok(Self { - async_build_queue, - build_queue, - storage, - async_storage, - pool: pool.clone(), - registry_api: Arc::new(RegistryApi::new( - config.watcher.registry_api_host.clone(), - config.crates_io_api_call_retries, - )?), - repository_stats_updater: Arc::new(RepositoryStatsUpdater::new(&config.watcher, pool)), - runtime, - config, - meter_provider, - }) - } -} diff --git a/src/db/add_package.rs b/src/db/add_package.rs deleted file mode 100644 index 3f6ad421c..000000000 --- a/src/db/add_package.rs +++ /dev/null @@ -1,1303 +0,0 @@ -use crate::{ - db::types::{BuildStatus, Feature, dependencies::ReleaseDependencyList}, - docbuilder::DocCoverage, - error::Result, - registry_api::{CrateData, CrateOwner, ReleaseData}, - utils::rustc_version::parse_rustc_date, - web::crate_details::{latest_release, releases_for_crate}, -}; -use anyhow::{Context, anyhow}; -use docs_rs_cargo_metadata::Package as MetadataPackage; -use docs_rs_database::types::{BuildId, CrateId, ReleaseId, version::Version}; -use docs_rs_storage::CompressionAlgorithm; -use futures_util::stream::TryStreamExt; -use serde_json::Value; -use slug::slugify; -use std::{ - collections::{HashMap, HashSet}, - fs, - io::{BufRead, BufReader}, - path::Path, -}; -use tracing::{debug, error, info, instrument}; - -/// Adds a package into database. -/// -/// Package must be built first. -/// -/// NOTE: `source_files` refers to the files originally in the crate, -/// not the files generated by rustdoc. -#[allow(clippy::too_many_arguments)] -#[instrument(skip(conn, compression_algorithms))] -pub(crate) async fn finish_release( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, - release_id: ReleaseId, - metadata_pkg: &MetadataPackage, - source_dir: &Path, - default_target: &str, - source_files: Value, - doc_targets: Vec, - registry_data: &ReleaseData, - has_docs: bool, - has_examples: bool, - compression_algorithms: impl IntoIterator, - repository_id: Option, - archive_storage: bool, - source_size: u64, -) -> Result<()> { - debug!("updating release data"); - let dependencies: ReleaseDependencyList = metadata_pkg - .dependencies - .iter() - .cloned() - .map(Into::into) - .collect(); - let rustdoc = get_rustdoc(metadata_pkg, source_dir).unwrap_or(None); - let readme = get_readme(metadata_pkg, source_dir).unwrap_or(None); - let features = get_features(metadata_pkg); - let is_library = metadata_pkg.is_library(); - - let result = sqlx::query!( - r#"UPDATE releases - SET release_time = $2, - dependencies = $3, - target_name = $4, - yanked = $5, - rustdoc_status = $6, - test_status = $7, - license = $8, - repository_url = $9, - homepage_url = $10, - description = $11, - description_long = $12, - readme = $13, - keywords = $14, - have_examples = $15, - downloads = $16, - files = $17, - doc_targets = $18, - is_library = $19, - documentation_url = $20, - default_target = $21, - features = $22, - repository_id = $23, - archive_storage = $24, - source_size = $25 - WHERE id = $1"#, - release_id.0, - registry_data.release_time, - serde_json::to_value(&dependencies)?, - metadata_pkg.package_name(), - registry_data.yanked, - has_docs, - false, // TODO: Add test status somehow - metadata_pkg.license, - metadata_pkg.repository, - metadata_pkg.homepage, - metadata_pkg.description, - rustdoc, - readme, - serde_json::to_value(&metadata_pkg.keywords)?, - has_examples, - registry_data.downloads, - source_files, - serde_json::to_value(doc_targets)?, - is_library, - metadata_pkg.documentation, - default_target, - features as Vec, - repository_id, - archive_storage, - source_size as i64, - ) - .execute(&mut *conn) - .await?; - - if result.rows_affected() < 1 { - return Err(anyhow!("Failed to update release")); - } - - add_keywords_into_database(conn, metadata_pkg, release_id).await?; - add_compression_into_database(conn, compression_algorithms.into_iter(), release_id).await?; - - update_latest_version_id(&mut *conn, crate_id) - .await - .context("couldn't update latest version id")?; - - update_build_status(conn, release_id).await?; - - Ok(()) -} - -pub async fn update_latest_version_id( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, -) -> Result<()> { - let releases = releases_for_crate(conn, crate_id).await?; - - sqlx::query!( - "UPDATE crates - SET latest_version_id = $2 - WHERE id = $1", - crate_id.0, - latest_release(&releases).map(|release| release.id.0), - ) - .execute(&mut *conn) - .await?; - - Ok(()) -} - -pub async fn update_build_status( - conn: &mut sqlx::PgConnection, - release_id: ReleaseId, -) -> Result<()> { - sqlx::query!( - "INSERT INTO release_build_status(rid, last_build_time, build_status) - SELECT - summary.id, - summary.last_build_time, - CASE - WHEN summary.success_count > 0 THEN 'success'::build_status - WHEN summary.failure_count > 0 THEN 'failure'::build_status - ELSE 'in_progress'::build_status - END as build_status - - FROM ( - SELECT - r.id, - MAX(b.build_finished) as last_build_time, - SUM(CASE WHEN b.build_status = 'success' THEN 1 ELSE 0 END) as success_count, - SUM(CASE WHEN b.build_status = 'failure' THEN 1 ELSE 0 END) as failure_count - FROM - releases as r - LEFT OUTER JOIN builds AS b on b.rid = r.id - WHERE - r.id = $1 - GROUP BY r.id - ) as summary - - ON CONFLICT (rid) DO UPDATE - SET - last_build_time = EXCLUDED.last_build_time, - build_status=EXCLUDED.build_status", - release_id.0, - ) - .execute(&mut *conn) - .await?; - - let crate_id = crate_id_from_release_id(&mut *conn, release_id).await?; - update_latest_version_id(&mut *conn, crate_id) - .await - .context("couldn't update latest version id")?; - - Ok(()) -} - -async fn crate_id_from_release_id( - conn: &mut sqlx::PgConnection, - release_id: ReleaseId, -) -> Result { - Ok(sqlx::query_scalar!( - r#" - SELECT crate_id as "crate_id: CrateId" - FROM releases - WHERE id = $1"#, - release_id.0, - ) - .fetch_one(&mut *conn) - .await?) -} - -#[instrument(skip(conn))] -pub(crate) async fn add_doc_coverage( - conn: &mut sqlx::PgConnection, - release_id: ReleaseId, - doc_coverage: DocCoverage, -) -> Result { - debug!("Adding doc coverage into database"); - Ok(sqlx::query_scalar!( - "INSERT INTO doc_coverage ( - release_id, total_items, documented_items, - total_items_needing_examples, items_with_examples - ) - VALUES ($1, $2, $3, $4, $5) - ON CONFLICT (release_id) DO UPDATE - SET - total_items = $2, - documented_items = $3, - total_items_needing_examples = $4, - items_with_examples = $5 - RETURNING release_id", - release_id.0, - &doc_coverage.total_items, - &doc_coverage.documented_items, - &doc_coverage.total_items_needing_examples, - &doc_coverage.items_with_examples, - ) - .fetch_one(&mut *conn) - .await?) -} - -/// Adds a build into database -#[instrument(skip(conn))] -pub(crate) async fn finish_build( - conn: &mut sqlx::PgConnection, - build_id: BuildId, - rustc_version: &str, - docsrs_version: &str, - build_status: BuildStatus, - documentation_size: Option, - errors: Option<&str>, -) -> Result<()> { - debug!("updating build after finishing"); - let hostname = hostname::get()?; - - let rustc_date = match parse_rustc_date(rustc_version) { - Ok(date) => Some(date), - Err(err) => { - // in the database we see cases where the rustc version is missing - // in the builds-table. In this case & if we can't parse the version - // we just want to log an error, but still finish the build. - error!( - "Failed to parse date from rustc version \"{}\": {:?}", - rustc_version, err - ); - None - } - }; - - let release_id = sqlx::query_scalar!( - r#"UPDATE builds - SET - rustc_version = $1, - docsrs_version = $2, - build_status = $3, - build_server = $4, - errors = $5, - documentation_size = $6, - rustc_nightly_date = $7, - build_finished = NOW() - WHERE - id = $8 - RETURNING rid as "rid: ReleaseId" "#, - rustc_version, - docsrs_version, - build_status as BuildStatus, - hostname.to_str().unwrap_or(""), - errors, - documentation_size.map(|v| v as i64), - rustc_date, - build_id.0, - ) - .fetch_one(&mut *conn) - .await?; - - update_build_status(conn, release_id).await?; - - Ok(()) -} - -#[instrument(skip(conn))] -pub(crate) async fn update_build_with_error( - conn: &mut sqlx::PgConnection, - build_id: BuildId, - errors: Option<&str>, -) -> Result { - debug!("updating build with error"); - let release_id = sqlx::query_scalar!( - r#"UPDATE builds - SET - build_status = $1, - errors = $2 - WHERE id = $3 - RETURNING rid as "rid: ReleaseId" "#, - BuildStatus::Failure as BuildStatus, - errors, - build_id.0, - ) - .fetch_one(&mut *conn) - .await?; - - update_build_status(conn, release_id).await?; - - Ok(build_id) -} - -pub(crate) async fn initialize_crate(conn: &mut sqlx::PgConnection, name: &str) -> Result { - sqlx::query_scalar!( - "INSERT INTO crates (name) - VALUES ($1) - ON CONFLICT (name) DO UPDATE - SET -- this `SET` is needed so the id is always returned. - name = EXCLUDED.name - RETURNING id", - name - ) - .fetch_one(&mut *conn) - .await - .map_err(Into::into) - .map(CrateId) -} - -pub(crate) async fn initialize_release( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, - version: &Version, -) -> Result { - let release_id = sqlx::query_scalar!( - r#"INSERT INTO releases (crate_id, version, archive_storage) - VALUES ($1, $2, TRUE) - ON CONFLICT (crate_id, version) DO UPDATE - SET -- this `SET` is needed so the id is always returned. - version = EXCLUDED.version - RETURNING id as "id: ReleaseId" "#, - crate_id.0, - version as _, - ) - .fetch_one(&mut *conn) - .await?; - - update_build_status(conn, release_id).await?; - - Ok(release_id) -} - -pub(crate) async fn initialize_build( - conn: &mut sqlx::PgConnection, - release_id: ReleaseId, -) -> Result { - let hostname = hostname::get()?; - - let build_id = sqlx::query_scalar!( - r#"INSERT INTO builds(rid, build_status, build_server, build_started) - VALUES ($1, $2, $3, NOW()) - RETURNING id as "id: BuildId" "#, - release_id.0, - BuildStatus::InProgress as BuildStatus, - hostname.to_str().unwrap_or(""), - ) - .fetch_one(&mut *conn) - .await?; - - update_build_status(conn, release_id).await?; - - Ok(build_id) -} - -/// Reads features and converts them to Vec with default being first -fn get_features(pkg: &MetadataPackage) -> Vec { - let mut features = Vec::with_capacity(pkg.features.len()); - if let Some(subfeatures) = pkg.features.get("default") { - features.push(Feature::new("default".into(), subfeatures.clone())); - }; - features.extend( - pkg.features - .iter() - .filter(|(name, _)| *name != "default") - .map(|(name, subfeatures)| Feature::new(name.clone(), subfeatures.clone())), - ); - features -} - -/// Reads readme if there is any read defined in Cargo.toml of a Package -fn get_readme(pkg: &MetadataPackage, source_dir: &Path) -> Result> { - let readme_path = source_dir.join(pkg.readme.as_deref().unwrap_or("README.md")); - - if !readme_path.exists() { - return Ok(None); - } - - let readme = fs::read_to_string(readme_path)?; - - if readme.is_empty() { - Ok(None) - } else if readme.len() > 51200 { - Ok(Some(format!( - "(Readme ignored due to being too long. ({} > 51200))", - readme.len() - ))) - } else { - Ok(Some(readme)) - } -} - -fn get_rustdoc(pkg: &MetadataPackage, source_dir: &Path) -> Result> { - if let Some(src_path) = &pkg.targets.first().and_then(|t| t.src_path.as_ref()) { - let src_path = Path::new(src_path); - if src_path.is_absolute() { - read_rust_doc(src_path) - } else { - read_rust_doc(&source_dir.join(src_path)) - } - } else { - // FIXME: should we care about metabuild targets? - Ok(None) - } -} - -/// Reads rustdoc from library -fn read_rust_doc(file_path: &Path) -> Result> { - let reader = fs::File::open(file_path).map(BufReader::new)?; - let mut rustdoc = String::new(); - - for line in reader.lines() { - let line = line?; - if line.starts_with("//!") { - // some lines may or may not have a space between the `//!` and the start of the text - let mut line = line.trim_start_matches("//!"); - if line.starts_with(' ') { - line = &line[1..]; - } - if !line.is_empty() { - rustdoc.push_str(line); - } - rustdoc.push('\n'); - } - } - - if rustdoc.is_empty() { - Ok(None) - } else if rustdoc.len() > 51200 { - Ok(Some(format!( - "(Library doc comment ignored due to being too long. ({} > 51200))", - rustdoc.len() - ))) - } else { - Ok(Some(rustdoc)) - } -} - -/// Adds keywords into database -async fn add_keywords_into_database( - conn: &mut sqlx::PgConnection, - pkg: &MetadataPackage, - release_id: ReleaseId, -) -> Result<()> { - let wanted_keywords: HashMap = pkg - .keywords - .iter() - .map(|kw| (slugify(kw), kw.clone())) - .collect(); - - let existing_keyword_slugs: HashSet = sqlx::query!( - "SELECT slug FROM keywords WHERE slug = ANY($1)", - &wanted_keywords.keys().cloned().collect::>()[..], - ) - .fetch(&mut *conn) - .map_ok(|row| row.slug) - .try_collect() - .await?; - - // we create new keywords one-by-one, since most of the time we already have them, - // and because support for multi-record inserts is a mess without adding a new - // library - for (slug, name) in wanted_keywords - .iter() - .filter(|(k, _)| !(existing_keyword_slugs.contains(*k))) - { - sqlx::query!( - "INSERT INTO keywords (name, slug) VALUES ($1, $2)", - name, - slug - ) - .execute(&mut *conn) - .await?; - } - - sqlx::query!( - "INSERT INTO keyword_rels (rid, kid) - SELECT $1 as rid, id as kid - FROM keywords - WHERE slug = ANY($2) - ON CONFLICT DO NOTHING;", - release_id.0, - &wanted_keywords.keys().cloned().collect::>()[..], - ) - .execute(&mut *conn) - .await?; - - Ok(()) -} - -#[instrument(skip(conn))] -pub async fn update_crate_data_in_database( - conn: &mut sqlx::PgConnection, - name: &str, - registry_data: &CrateData, -) -> Result<()> { - info!("Updating crate data for {}", name); - let crate_id = sqlx::query_scalar!( - r#"SELECT id as "id: CrateId" FROM crates WHERE crates.name = $1"#, - name - ) - .fetch_one(&mut *conn) - .await?; - - update_owners_in_database(conn, ®istry_data.owners, crate_id).await?; - - Ok(()) -} - -/// Adds owners into database -async fn update_owners_in_database( - conn: &mut sqlx::PgConnection, - owners: &[CrateOwner], - crate_id: CrateId, -) -> Result<()> { - // Update any existing owner data since it is mutable and could have changed since last - // time we pulled it - - let mut oids: Vec = Vec::new(); - - for owner in owners { - oids.push( - sqlx::query_scalar!( - "INSERT INTO owners (login, avatar, kind) - VALUES ($1, $2, $3) - ON CONFLICT (login) DO UPDATE - SET - avatar = EXCLUDED.avatar, - kind = EXCLUDED.kind - RETURNING id", - owner.login, - owner.avatar, - owner.kind as _, - ) - .fetch_one(&mut *conn) - .await?, - ); - } - - sqlx::query!( - "INSERT INTO owner_rels (cid, oid) - SELECT $1,oid - FROM UNNEST($2::int[]) as oid - ON CONFLICT (cid,oid) - DO NOTHING", - crate_id.0, - &oids[..] - ) - .execute(&mut *conn) - .await?; - - sqlx::query!( - "DELETE FROM owner_rels - WHERE - cid = $1 AND - NOT (oid = ANY($2))", - crate_id.0, - &oids[..], - ) - .execute(&mut *conn) - .await?; - - Ok(()) -} - -/// Add the compression algorithms used for this crate to the database -async fn add_compression_into_database( - conn: &mut sqlx::PgConnection, - algorithms: I, - release_id: ReleaseId, -) -> Result<()> -where - I: Iterator, -{ - for alg in algorithms { - sqlx::query!( - "INSERT INTO compression_rels (release, algorithm) - VALUES ($1, $2) - ON CONFLICT DO NOTHING;", - release_id.0, - &(alg as i32) - ) - .execute(&mut *conn) - .await?; - } - Ok(()) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::registry_api::OwnerKind; - use crate::test::*; - use crate::utils::CargoMetadata; - use chrono::NaiveDate; - use std::slice; - use test_case::test_case; - - #[test] - fn test_set_build_to_error() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; - let build_id = initialize_build(&mut conn, release_id).await?; - - update_build_with_error(&mut conn, build_id, Some("error message")).await?; - - let row = sqlx::query!( - r#"SELECT - rustc_version, - docsrs_version, - build_started, - build_status as "build_status: BuildStatus", - errors - FROM builds - WHERE id = $1"#, - build_id.0 - ) - .fetch_one(&mut *conn) - .await?; - - assert!(row.rustc_version.is_none()); - assert!(row.docsrs_version.is_none()); - assert!(row.build_started.is_some()); - assert_eq!(row.build_status, BuildStatus::Failure); - assert_eq!(row.errors, Some("error message".into())); - - Ok(()) - }) - } - - #[test] - fn test_finish_build_success_valid_rustc_date() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; - let build_id = initialize_build(&mut conn, release_id).await?; - - finish_build( - &mut conn, - build_id, - "rustc 1.84.0-nightly (e7c0d2750 2024-10-15)", - "docsrs_version", - BuildStatus::Success, - None, - None, - ) - .await?; - - let row = sqlx::query!( - r#"SELECT - rustc_version, - docsrs_version, - build_status as "build_status: BuildStatus", - errors, - rustc_nightly_date - FROM builds - WHERE id = $1"#, - build_id.0 - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!( - row.rustc_version, - Some("rustc 1.84.0-nightly (e7c0d2750 2024-10-15)".into()) - ); - assert_eq!(row.docsrs_version, Some("docsrs_version".into())); - assert_eq!(row.build_status, BuildStatus::Success); - assert_eq!( - row.rustc_nightly_date, - Some(NaiveDate::from_ymd_opt(2024, 10, 15).unwrap()) - ); - assert!(row.errors.is_none()); - - Ok(()) - }) - } - - #[test] - fn test_finish_build_success_invalid_rustc_date() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; - let build_id = initialize_build(&mut conn, release_id).await?; - - finish_build( - &mut conn, - build_id, - "rustc_version", - "docsrs_version", - BuildStatus::Success, - Some(42), - None, - ) - .await?; - - let row = sqlx::query!( - r#"SELECT - rustc_version, - docsrs_version, - build_status as "build_status: BuildStatus", - documentation_size, - errors, - rustc_nightly_date - FROM builds - WHERE id = $1"#, - build_id.0 - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(row.rustc_version, Some("rustc_version".into())); - assert_eq!(row.docsrs_version, Some("docsrs_version".into())); - assert_eq!(row.build_status, BuildStatus::Success); - assert_eq!(row.documentation_size, Some(42)); - assert!(row.rustc_nightly_date.is_none()); - assert!(row.errors.is_none()); - - Ok(()) - }) - } - - #[test] - fn test_finish_build_error() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - let release_id = initialize_release(&mut conn, crate_id, &V0_1).await?; - let build_id = initialize_build(&mut conn, release_id).await?; - - finish_build( - &mut conn, - build_id, - "rustc_version", - "docsrs_version", - BuildStatus::Failure, - None, - Some("error message"), - ) - .await?; - - let row = sqlx::query!( - r#"SELECT - rustc_version, - docsrs_version, - build_status as "build_status: BuildStatus", - documentation_size, - errors - FROM builds - WHERE id = $1"#, - build_id.0 - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(row.rustc_version, Some("rustc_version".into())); - assert_eq!(row.docsrs_version, Some("docsrs_version".into())); - assert_eq!(row.build_status, BuildStatus::Failure); - assert_eq!(row.errors, Some("error message".into())); - assert!(row.documentation_size.is_none()); - - Ok(()) - }) - } - - #[test] - fn new_keywords() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - let release_id = env - .fake_release() - .await - .name("dummy") - .version(V0_1) - .keywords(vec!["kw 1".into(), "kw 2".into()]) - .create() - .await?; - - let kw_r = sqlx::query!( - r#"SELECT - kw.name as "name!", - kw.slug as "slug!" - FROM keywords as kw - INNER JOIN keyword_rels as kwr on kw.id = kwr.kid - WHERE kwr.rid = $1 - ORDER BY kw.name,kw.slug"#, - release_id.0 - ) - .fetch_all(&mut *conn) - .await? - .into_iter() - .map(|row| (row.name, row.slug)) - .collect::>(); - - assert_eq!(kw_r[0], ("kw 1".into(), "kw-1".into())); - assert_eq!(kw_r[1], ("kw 2".into(), "kw-2".into())); - - let all_kw = sqlx::query!("SELECT slug FROM keywords ORDER BY slug") - .fetch_all(&mut *conn) - .await? - .into_iter() - .map(|row| row.slug) - .collect::>(); - - assert_eq!(all_kw, vec![String::from("kw-1"), "kw-2".into()]); - - Ok(()) - }) - } - - #[test] - fn keyword_conflict_when_rebuilding_release() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version(V0_1) - .keywords(vec!["kw 3".into(), "kw 4".into()]) - .create() - .await?; - - // same version so we have the same release - env.fake_release() - .await - .name("dummy") - .version(V0_1) - .keywords(vec!["kw 3".into(), "kw 4".into()]) - .create() - .await?; - - Ok(()) - }) - } - - #[test] - fn updated_keywords() { - async_wrapper(|env| async move { - env.fake_release() - .await - .name("dummy") - .version(V1) - .keywords(vec!["kw 3".into(), "kw 4".into()]) - .create() - .await?; - - let release_id = env - .fake_release() - .await - .name("dummy") - .version(V1) - .keywords(vec!["kw 1".into(), "kw 2".into()]) - .create() - .await?; - - let mut conn = env.async_db().async_conn().await; - let kw_r = sqlx::query!( - r#"SELECT - kw.name as "name!", - kw.slug as "slug!" - FROM keywords as kw - INNER JOIN keyword_rels as kwr on kw.id = kwr.kid - WHERE kwr.rid = $1 - ORDER BY kw.name,kw.slug"#, - release_id.0 - ) - .fetch_all(&mut *conn) - .await? - .into_iter() - .map(|row| (row.name, row.slug)) - .collect::>(); - - assert_eq!(kw_r[0], ("kw 1".into(), "kw-1".into())); - assert_eq!(kw_r[1], ("kw 2".into(), "kw-2".into())); - - let all_kw = sqlx::query!("SELECT slug FROM keywords ORDER BY slug") - .fetch_all(&mut *conn) - .await? - .into_iter() - .map(|row| row.slug) - .collect::>(); - - assert_eq!( - all_kw, - vec![ - String::from("kw-1"), - "kw-2".into(), - "kw-3".into(), - "kw-4".into(), - ] - ); - - Ok(()) - }) - } - - #[test] - fn new_owner_long_avatar() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - - let owner1 = CrateOwner { - avatar: "avatar".repeat(100), - login: "login".into(), - kind: OwnerKind::User, - }; - - update_owners_in_database(&mut conn, slice::from_ref(&owner1), crate_id).await?; - - let owner_def = sqlx::query!( - r#"SELECT login, avatar, kind as "kind: OwnerKind" - FROM owners"# - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!(owner_def.login, owner1.login); - assert_eq!(owner_def.avatar, owner1.avatar); - assert_eq!(owner_def.kind, owner1.kind); - - let owner_rel = sqlx::query!( - "SELECT o.login - FROM owners o, owner_rels r - WHERE - o.id = r.oid AND - r.cid = $1", - crate_id.0 - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!(owner_rel.login, owner1.login); - - Ok(()) - }) - } - - #[test] - fn new_owners() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - - let owner1 = CrateOwner { - avatar: "avatar".into(), - login: "login".into(), - kind: OwnerKind::User, - }; - - update_owners_in_database(&mut conn, slice::from_ref(&owner1), crate_id).await?; - - let owner_def = sqlx::query!( - r#"SELECT login, avatar, kind as "kind: OwnerKind" - FROM owners"# - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!(owner_def.login, owner1.login); - assert_eq!(owner_def.avatar, owner1.avatar); - assert_eq!(owner_def.kind, owner1.kind); - - let owner_rel = sqlx::query!( - "SELECT o.login - FROM owners o, owner_rels r - WHERE - o.id = r.oid AND - r.cid = $1", - crate_id.0 - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!(owner_rel.login, owner1.login); - - Ok(()) - }) - } - - #[test] - fn update_owner_details() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - - // set initial owner details - update_owners_in_database( - &mut conn, - &[CrateOwner { - login: "login".into(), - avatar: "avatar".into(), - kind: OwnerKind::User, - }], - crate_id, - ) - .await?; - - let updated_owner = CrateOwner { - login: "login".into(), - avatar: "avatar2".into(), - kind: OwnerKind::Team, - }; - update_owners_in_database(&mut conn, slice::from_ref(&updated_owner), crate_id).await?; - - let owner_def = - sqlx::query!(r#"SELECT login, avatar, kind as "kind: OwnerKind" FROM owners"#) - .fetch_one(&mut *conn) - .await?; - assert_eq!(owner_def.login, updated_owner.login); - assert_eq!(owner_def.avatar, updated_owner.avatar); - assert_eq!(owner_def.kind, updated_owner.kind); - - let owner_rel = sqlx::query!( - "SELECT o.login - FROM owners o, owner_rels r - WHERE - o.id = r.oid AND - r.cid = $1", - crate_id.0 - ) - .fetch_one(&mut *conn) - .await?; - assert_eq!(owner_rel.login, updated_owner.login); - - Ok(()) - }) - } - - #[test] - fn add_new_owners_and_delete_old() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, "krate").await?; - - // set initial owner details - update_owners_in_database( - &mut conn, - &[CrateOwner { - login: "login".into(), - avatar: "avatar".into(), - kind: OwnerKind::User, - }], - crate_id, - ) - .await?; - - let new_owners: Vec = (1..5) - .map(|i| CrateOwner { - login: format!("login{i}"), - avatar: format!("avatar{i}"), - kind: OwnerKind::User, - }) - .collect(); - - update_owners_in_database(&mut conn, &new_owners, crate_id).await?; - - let all_owners: Vec = sqlx::query!("SELECT login FROM owners order by login") - .fetch(&mut *conn) - .map_ok(|row| row.login) - .try_collect() - .await?; - - // we still have all owners in the database. - assert_eq!( - all_owners, - vec!["login", "login1", "login2", "login3", "login4"] - ); - - let crate_owners: Vec = sqlx::query!( - "SELECT o.login - FROM owners o, owner_rels r - WHERE - o.id = r.oid AND - r.cid = $1", - crate_id.0, - ) - .fetch(&mut *conn) - .map_ok(|row| row.login) - .try_collect() - .await?; - - // the owner-rel is deleted - assert_eq!(crate_owners, vec!["login1", "login2", "login3", "login4"]); - - Ok(()) - }) - } - - #[test_case("", [])] - #[test_case( - r#" - [features] - bar = [] - "#, - [Feature::new("bar".into(), vec![])] - )] - #[test_case( - r#" - [dependencies] - bar = { optional = true, path = "bar" } - "#, - [Feature::new("bar".into(), vec!["dep:bar".into()])] - )] - #[test_case( - r#" - [dependencies] - bar = { optional = true, path = "bar" } - [features] - not-bar = ["dep:bar"] - "#, - [Feature::new("not-bar".into(), vec!["dep:bar".into()])] - )] - fn test_get_features(extra: &str, expected: impl AsRef<[Feature]>) -> Result<()> { - let dir = tempfile::tempdir()?; - - std::fs::create_dir(dir.path().join("src"))?; - std::fs::write(dir.path().join("src/lib.rs"), "")?; - - std::fs::create_dir(dir.path().join("bar"))?; - std::fs::create_dir(dir.path().join("bar/src"))?; - std::fs::write(dir.path().join("bar/src/lib.rs"), "")?; - - std::fs::write( - dir.path().join("bar/Cargo.toml"), - r#" - [package] - name = "bar" - version = "0.0.0" - "#, - )?; - - let base = r#" - [package] - name = "foo" - version = "0.0.0" - "#; - - std::fs::write(dir.path().join("Cargo.toml"), [base, extra].concat())?; - let metadata = CargoMetadata::load_from_host_path(dir.path())?; - let features = super::get_features(metadata.root()); - assert_eq!(features, expected.as_ref()); - - Ok(()) - } - - #[test] - fn test_initialize_crate() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - let name = "krate"; - let crate_id = initialize_crate(&mut conn, name).await?; - - let id = sqlx::query_scalar!( - r#"SELECT id as "id: CrateId" FROM crates WHERE name = $1"#, - name - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(crate_id, id); - - let same_crate_id = initialize_crate(&mut conn, name).await?; - assert_eq!(crate_id, same_crate_id); - - Ok(()) - }) - } - - #[test] - fn test_initialize_release() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let name = "krate"; - let crate_id = initialize_crate(&mut conn, name).await?; - - let release_id = initialize_release(&mut conn, crate_id, &V1).await?; - - let id = sqlx::query_scalar!( - r#"SELECT id as "id: ReleaseId" FROM releases WHERE crate_id = $1 and version = $2"#, - crate_id.0, - V1 as _, - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(release_id, id); - - let same_release_id = initialize_release(&mut conn, crate_id, &V1).await?; - assert_eq!(release_id, same_release_id); - - Ok(()) - }) - } - - #[test] - fn test_initialize_build() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - let name = "krate"; - let crate_id = initialize_crate(&mut conn, name).await?; - let release_id = initialize_release(&mut conn, crate_id, &V1).await?; - - let build_id = initialize_build(&mut conn, release_id).await?; - - let id = sqlx::query_scalar!( - r#"SELECT id as "id: BuildId" FROM builds WHERE rid = $1"#, - release_id.0 - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(build_id, id); - - let another_build_id = initialize_build(&mut conn, release_id).await?; - assert_ne!(build_id, another_build_id); - - Ok(()) - }) - } - - #[test] - fn test_long_crate_name() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - let name: String = "krate".repeat(100); - let crate_id = initialize_crate(&mut conn, &name).await?; - - let db_name = sqlx::query_scalar!("SELECT name FROM crates WHERE id = $1", crate_id.0) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(db_name, name); - - Ok(()) - }) - } - - #[test] - fn test_long_release_version() { - async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - let crate_id = initialize_crate(&mut conn, "krate").await?; - let version = Version::parse(&format!( - "1.2.3-{}+{}", - "prerelease".repeat(100), - "build".repeat(100) - ))?; - let release_id = initialize_release(&mut conn, crate_id, &version).await?; - - let db_version = sqlx::query_scalar!( - r#" - SELECT - version as "version: Version" - FROM releases - WHERE id = $1"#, - release_id.0 - ) - .fetch_one(&mut *conn) - .await?; - - assert_eq!(db_version, version); - - Ok(()) - }) - } -} diff --git a/src/db/blacklist.rs b/src/db/blacklist.rs deleted file mode 100644 index c4200c893..000000000 --- a/src/db/blacklist.rs +++ /dev/null @@ -1,123 +0,0 @@ -use crate::error::Result; -use futures_util::stream::TryStreamExt; - -#[derive(Debug, thiserror::Error)] -enum BlacklistError { - #[error("crate {0} is already on the blacklist")] - CrateAlreadyOnBlacklist(String), - - #[error("crate {0} is not on the blacklist")] - CrateNotOnBlacklist(String), -} - -/// Returns whether the given name is blacklisted. -pub async fn is_blacklisted(conn: &mut sqlx::PgConnection, name: &str) -> Result { - Ok(sqlx::query_scalar!( - r#"SELECT COUNT(*) as "count!" FROM blacklisted_crates WHERE crate_name = $1;"#, - name - ) - .fetch_one(conn) - .await? - != 0) -} - -/// Returns the crate names on the blacklist, sorted ascending. -pub async fn list_crates(conn: &mut sqlx::PgConnection) -> Result> { - Ok( - sqlx::query!("SELECT crate_name FROM blacklisted_crates ORDER BY crate_name asc;") - .fetch(conn) - .map_ok(|row| row.crate_name) - .try_collect() - .await?, - ) -} - -/// Adds a crate to the blacklist. -pub async fn add_crate(conn: &mut sqlx::PgConnection, name: &str) -> Result<()> { - if is_blacklisted(&mut *conn, name).await? { - return Err(BlacklistError::CrateAlreadyOnBlacklist(name.into()).into()); - } - - sqlx::query!( - "INSERT INTO blacklisted_crates (crate_name) VALUES ($1);", - name - ) - .execute(conn) - .await?; - - Ok(()) -} - -/// Removes a crate from the blacklist. -pub async fn remove_crate(conn: &mut sqlx::PgConnection, name: &str) -> Result<()> { - if !is_blacklisted(conn, name).await? { - return Err(BlacklistError::CrateNotOnBlacklist(name.into()).into()); - } - - sqlx::query!( - "DELETE FROM blacklisted_crates WHERE crate_name = $1;", - name - ) - .execute(conn) - .await?; - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_list_blacklist() { - crate::test::async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - // crates are added out of order to verify sorting - add_crate(&mut conn, "crate A").await?; - add_crate(&mut conn, "crate C").await?; - add_crate(&mut conn, "crate B").await?; - - assert!(list_crates(&mut conn).await? == vec!["crate A", "crate B", "crate C"]); - Ok(()) - }); - } - - #[test] - fn test_add_to_and_remove_from_blacklist() { - crate::test::async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - assert!(!is_blacklisted(&mut conn, "crate foo").await?); - add_crate(&mut conn, "crate foo").await?; - assert!(is_blacklisted(&mut conn, "crate foo").await?); - remove_crate(&mut conn, "crate foo").await?; - assert!(!is_blacklisted(&mut conn, "crate foo").await?); - Ok(()) - }); - } - - #[test] - fn test_add_twice_to_blacklist() { - crate::test::async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - add_crate(&mut conn, "crate foo").await?; - assert!(add_crate(&mut conn, "crate foo").await.is_err()); - add_crate(&mut conn, "crate bar").await?; - - Ok(()) - }); - } - - #[test] - fn test_remove_non_existing_crate() { - crate::test::async_wrapper(|env| async move { - let mut conn = env.async_db().async_conn().await; - - assert!(remove_crate(&mut conn, "crate foo").await.is_err()); - - Ok(()) - }); - } -} diff --git a/src/docbuilder/mod.rs b/src/docbuilder/mod.rs deleted file mode 100644 index f0d3fdb03..000000000 --- a/src/docbuilder/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -mod limits; -mod rustwide_builder; - -pub(crate) use self::limits::Limits; -pub(crate) use self::rustwide_builder::DocCoverage; -pub use self::rustwide_builder::{ - BuildPackageSummary, BuilderMetrics, PackageKind, RustwideBuilder, -}; - -#[cfg(test)] -pub use self::rustwide_builder::{ - RUSTDOC_JSON_COMPRESSION_ALGORITHMS, read_format_version_from_rustdoc_json, -}; diff --git a/src/lib.rs b/src/lib.rs index b467fc4c1..e69de29bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +0,0 @@ -//! [Docs.rs](https://docs.rs) (formerly cratesfyi) is an open source project to host -//! documentation of crates for the Rust Programming Language. -#![allow(clippy::cognitive_complexity)] - -pub use self::config::Config; -pub use self::context::Context; -pub use self::docbuilder::PackageKind; -pub use self::docbuilder::{BuildPackageSummary, RustwideBuilder}; -pub use self::web::start_web_server; - -pub use font_awesome_as_a_crate::icons; - -mod config; -mod context; -pub mod db; -mod docbuilder; -pub mod metrics; -#[cfg(test)] -mod test; -pub mod utils; -mod web; diff --git a/src/test/fakes.rs b/src/test/fakes.rs deleted file mode 100644 index 12c6b2d50..000000000 --- a/src/test/fakes.rs +++ /dev/null @@ -1,743 +0,0 @@ -use super::TestDatabase; -use crate::{ - db::{ - BuildId, ReleaseId, - file::{FileEntry, file_list_to_json}, - initialize_build, initialize_crate, initialize_release, - types::{BuildStatus, version::Version}, - update_build_status, - }, - docbuilder::{DocCoverage, RUSTDOC_JSON_COMPRESSION_ALGORITHMS}, - error::Result, - registry_api::{CrateData, CrateOwner, ReleaseData}, - storage::{ - AsyncStorage, CompressionAlgorithm, RustdocJsonFormatVersion, compress, - rustdoc_archive_path, rustdoc_json_path, source_archive_path, - }, - utils::{Dependency, MetadataPackage, cargo_metadata::Target}, -}; -use anyhow::{Context, bail}; -use base64::{Engine, engine::general_purpose::STANDARD as b64}; -use chrono::{DateTime, Utc}; -use std::{collections::HashMap, fmt, iter, sync::Arc}; -use tracing::debug; - -/// Create a fake release in the database that failed before the build. -/// This is a temporary small factory function only until we refactored the -/// `FakeRelease` and `FakeBuild` factories to be more flexible. -pub(crate) async fn fake_release_that_failed_before_build( - conn: &mut sqlx::PgConnection, - name: &str, - version: V, - errors: &str, -) -> Result<(ReleaseId, BuildId)> -where - V: TryInto, - V::Error: std::error::Error + Send + Sync + 'static, -{ - let version = version.try_into()?; - let crate_id = initialize_crate(&mut *conn, name).await?; - let release_id = initialize_release(&mut *conn, crate_id, &version).await?; - let build_id = initialize_build(&mut *conn, release_id).await?; - - sqlx::query_scalar!( - "UPDATE builds - SET - build_status = 'failure', - errors = $2 - WHERE id = $1", - build_id.0, - errors, - ) - .execute(&mut *conn) - .await?; - - update_build_status(conn, release_id).await?; - - Ok((release_id, build_id)) -} - -#[must_use = "FakeRelease does nothing until you call .create()"] -pub(crate) struct FakeRelease<'a> { - db: &'a TestDatabase, - storage: Arc, - package: MetadataPackage, - builds: Option>, - /// name, content - source_files: Vec<(&'a str, &'a [u8])>, - /// name, content - rustdoc_files: Vec<(&'a str, &'a [u8])>, - doc_targets: Vec, - default_target: Option<&'a str>, - registry_crate_data: CrateData, - registry_release_data: ReleaseData, - has_docs: bool, - has_examples: bool, - archive_storage: bool, - /// This stores the content, while `package.readme` stores the filename - readme: Option<&'a str>, - github_stats: Option, - doc_coverage: Option, - no_cargo_toml: bool, -} - -pub(crate) struct FakeBuild { - s3_build_log: Option, - other_build_logs: HashMap, - db_build_log: Option, - rustc_version: String, - docsrs_version: String, - build_status: BuildStatus, -} - -const DEFAULT_CONTENT: &[u8] = - b"default content for test/fakes"; - -impl<'a> FakeRelease<'a> { - pub(super) fn new(db: &'a TestDatabase, storage: Arc) -> Self { - FakeRelease { - db, - storage, - package: MetadataPackage { - id: "fake-package-id".into(), - name: "fake-package".into(), - version: Version::new(1, 0, 0), - license: Some("MIT".into()), - repository: Some("https://git.example.com".into()), - homepage: Some("https://www.example.com".into()), - description: Some("Fake package".into()), - documentation: Some("https://docs.example.com".into()), - dependencies: vec![Dependency { - name: "fake-dependency".into(), - req: semver::VersionReq::parse("^1.0.0").unwrap(), - kind: None, - rename: None, - optional: false, - }], - targets: vec![Target::dummy_lib("fake_package".into(), None)], - readme: None, - keywords: vec!["fake".into(), "package".into()], - features: [ - ("default".into(), vec!["feature1".into(), "feature3".into()]), - ("feature1".into(), Vec::new()), - ("feature2".into(), vec!["feature1".into()]), - ("feature3".into(), Vec::new()), - ] - .iter() - .cloned() - .collect::>>(), - }, - builds: None, - source_files: Vec::new(), - rustdoc_files: Vec::new(), - doc_targets: Vec::new(), - default_target: None, - registry_crate_data: CrateData { owners: Vec::new() }, - registry_release_data: ReleaseData { - release_time: Utc::now(), - yanked: false, - downloads: 0, - }, - has_docs: true, - has_examples: false, - readme: None, - github_stats: None, - doc_coverage: None, - archive_storage: false, - no_cargo_toml: false, - } - } - - pub(crate) fn description(mut self, new: impl Into) -> Self { - self.package.description = Some(new.into()); - self - } - - pub(crate) fn add_dependency(mut self, dependency: Dependency) -> Self { - self.package.dependencies.push(dependency); - self - } - - pub(crate) fn release_time(mut self, new: DateTime) -> Self { - self.registry_release_data.release_time = new; - self - } - - pub(crate) fn name(mut self, new: &str) -> Self { - self.package.name = new.into(); - self.package.id = format!("{new}-id"); - self.package.targets[0].name = new.into(); - self - } - - pub(crate) fn version(mut self, new: V) -> Self - where - V: TryInto, - V::Error: fmt::Debug, - { - self.package.version = new.try_into().expect("invalid version"); - self - } - - pub(crate) fn repo(mut self, repo: impl Into) -> Self { - self.package.repository = Some(repo.into()); - self - } - - /// Shortcut to add a single unsuccessful build with default data - // TODO: How should `has_docs` actually be handled? - pub(crate) fn build_result_failed(self) -> Self { - assert!( - self.builds.is_none(), - "cannot use custom builds with build_result_failed" - ); - Self { - has_docs: false, - builds: Some(vec![FakeBuild::default().successful(false)]), - ..self - } - } - - pub(crate) fn builds(self, builds: Vec) -> Self { - assert!(self.builds.is_none()); - assert!(!builds.is_empty()); - Self { - builds: Some(builds), - ..self - } - } - - pub(crate) fn no_builds(self) -> Self { - assert!(self.builds.is_none()); - Self { - builds: Some(vec![]), - ..self - } - } - - pub(crate) fn yanked(mut self, new: bool) -> Self { - self.registry_release_data.yanked = new; - self - } - - pub(crate) fn archive_storage(mut self, new: bool) -> Self { - self.archive_storage = new; - self - } - - /// Since we switched to LOL HTML, all data must have a valid and . - /// To avoid duplicating them in every test, this just makes up some content. - pub(crate) fn rustdoc_file(mut self, path: &'a str) -> Self { - self.rustdoc_files.push((path, DEFAULT_CONTENT)); - self - } - - pub(crate) fn rustdoc_file_with(mut self, path: &'a str, data: &'a [u8]) -> Self { - self.rustdoc_files.push((path, data)); - self - } - - pub(crate) fn source_file(mut self, path: &'a str, data: &'a [u8]) -> Self { - self.source_files.push((path, data)); - self - } - - pub(crate) fn target_source(mut self, path: &'a str) -> Self { - if let Some(target) = self.package.targets.first_mut() { - target.src_path = Some(path.into()); - } - self - } - - pub(crate) fn no_cargo_toml(mut self) -> Self { - self.no_cargo_toml = true; - self - } - - pub(crate) fn default_target(mut self, target: &'a str) -> Self { - self = self.add_target(target); - self.default_target = Some(target); - self - } - - pub(crate) fn add_target(mut self, target: &str) -> Self { - self.doc_targets.push(target.into()); - self - } - - pub(crate) fn binary(mut self, bin: bool) -> Self { - self.has_docs = !bin; - if bin { - for target in self.package.targets.iter_mut() { - target.crate_types = vec!["bin".into()]; - } - } - self - } - - pub(crate) fn keywords(mut self, keywords: Vec) -> Self { - self.package.keywords = keywords; - self - } - - pub(crate) fn add_platform>(mut self, platform: S) -> Self { - let platform = platform.into(); - let name = self.package.targets[0].name.clone(); - let target = Target::dummy_lib(name, Some(platform.clone())); - self.package.targets.push(target); - self.doc_targets.push(platform); - self - } - - /// NOTE: this should be markdown. It will be rendered as HTML when served. - pub(crate) fn readme(mut self, content: &'a str) -> Self { - self.readme = Some(content); - self.source_file("README.md", content.as_bytes()) - } - - /// NOTE: this should be markdown. It will be rendered as HTML when served. - pub(crate) fn readme_only_database(mut self, content: &'a str) -> Self { - self.readme = Some(content); - self - } - - pub(crate) fn add_owner(mut self, owner: CrateOwner) -> Self { - self.registry_crate_data.owners.push(owner); - self - } - - pub(crate) fn doc_coverage(self, doc_coverage: DocCoverage) -> Self { - Self { - doc_coverage: Some(doc_coverage), - ..self - } - } - - pub(crate) fn features(mut self, features: HashMap>) -> Self { - self.package.features = features; - self - } - - pub(crate) fn github_stats( - mut self, - repo: impl Into, - stars: i32, - forks: i32, - issues: i32, - ) -> Self { - self.github_stats = Some(FakeGithubStats { - repo: repo.into(), - stars, - forks, - issues, - }); - self - } - - pub(crate) fn documentation_url(mut self, documentation_url: Option) -> Self { - self.package.documentation = documentation_url; - self - } - - /// Returns the release_id - pub(crate) async fn create(mut self) -> Result { - use std::fs; - use std::path::Path; - - let package = self.package; - let db = self.db; - let mut rustdoc_files = self.rustdoc_files; - let storage = self.storage; - let archive_storage = self.archive_storage; - - // Upload all source files as rustdoc files - // In real life, these would be highlighted HTML, but for testing we just use the files themselves. - for (source_path, data) in &self.source_files { - if let Some(src) = source_path.strip_prefix("src/") { - let mut updated = ["src", &package.name, src].join("/"); - updated += ".html"; - let source_html = format!( - "{}", - std::str::from_utf8(data).expect("invalid utf8") - ); - rustdoc_files.push(( - Box::leak(Box::new(updated)), - Box::leak(source_html.into_bytes().into_boxed_slice()), - )); - } - } - - #[derive(Debug)] - enum FileKind { - Rustdoc, - Sources, - } - - let create_temp_dir = || { - tempfile::Builder::new() - .prefix("docs.rs-fake") - .tempdir() - .unwrap() - }; - - let store_files_into = |files: &[(&str, &[u8])], base_path: &Path| { - for (path, data) in files { - if path.starts_with('/') { - anyhow::bail!("absolute paths not supported"); - } - // allow `src/main.rs` - if let Some(parent) = Path::new(path).parent() { - let path = base_path.join(parent); - fs::create_dir_all(&path) - .with_context(|| format!("failed to create {}", path.display()))?; - } - let file = base_path.join(path); - debug!("writing file {}", file.display()); - fs::write(file, data)?; - } - Ok(()) - }; - - async fn upload_files( - kind: FileKind, - source_directory: &Path, - archive_storage: bool, - package: &MetadataPackage, - storage: &AsyncStorage, - ) -> Result<(Vec, CompressionAlgorithm)> { - debug!( - "adding directory {:?} from {}", - kind, - source_directory.display() - ); - if archive_storage { - let archive = match kind { - FileKind::Rustdoc => rustdoc_archive_path(&package.name, &package.version), - FileKind::Sources => source_archive_path(&package.name, &package.version), - }; - debug!("store in archive: {:?}", archive); - let (files_list, new_alg) = - crate::db::add_path_into_remote_archive(storage, &archive, source_directory) - .await?; - Ok((files_list, new_alg)) - } else { - let prefix = match kind { - FileKind::Rustdoc => "rustdoc", - FileKind::Sources => "sources", - }; - crate::db::add_path_into_database( - storage, - format!("{}/{}/{}/", prefix, package.name, package.version), - source_directory, - ) - .await - } - } - - debug!("before upload source"); - let source_tmp = create_temp_dir(); - store_files_into(&self.source_files, source_tmp.path())?; - - if !self.no_cargo_toml - && !self - .source_files - .iter() - .any(|&(path, _)| path == "Cargo.toml") - { - let MetadataPackage { name, version, .. } = &package; - let content = format!( - r#" - [package] - name = "{name}" - version = "{version}" - "# - ); - store_files_into(&[("Cargo.toml", content.as_bytes())], source_tmp.path())?; - } - - let (source_meta, algs) = upload_files( - FileKind::Sources, - source_tmp.path(), - archive_storage, - &package, - &storage, - ) - .await?; - debug!(?source_meta, "added source files"); - - // If the test didn't add custom builds, inject a default one - let builds = self.builds.unwrap_or_else(|| vec![FakeBuild::default()]); - - if builds.last().map(|b| b.build_status) == Some(BuildStatus::Success) { - let index = [&package.name, "index.html"].join("/"); - if package.is_library() && !rustdoc_files.iter().any(|(path, _)| path == &index) { - rustdoc_files.push((&index, DEFAULT_CONTENT)); - } - - let rustdoc_tmp = create_temp_dir(); - let rustdoc_path = rustdoc_tmp.path(); - - // store default target files - store_files_into(&rustdoc_files, rustdoc_path)?; - debug!("added rustdoc files"); - - for target in &package.targets[1..] { - let platform = target.src_path.as_ref().unwrap(); - let platform_dir = rustdoc_path.join(platform); - fs::create_dir(&platform_dir)?; - - store_files_into(&rustdoc_files, &platform_dir)?; - debug!("added platform files for {}", platform); - } - - let (files, _) = upload_files( - FileKind::Rustdoc, - rustdoc_path, - archive_storage, - &package, - &storage, - ) - .await?; - debug!(?files, "uploaded rustdoc files"); - } - - let mut async_conn = db.async_conn().await; - - let repository = match self.github_stats { - Some(stats) => Some(stats.create(&mut async_conn).await?), - None => None, - }; - - let crate_tmp = create_temp_dir(); - let crate_dir = crate_tmp.path(); - if let Some(markdown) = self.readme { - fs::write(crate_dir.join("README.md"), markdown)?; - } - store_files_into(&self.source_files, crate_dir)?; - - let default_target = self.default_target.unwrap_or("x86_64-unknown-linux-gnu"); - if !self.doc_targets.iter().any(|t| t == default_target) { - self.doc_targets.insert(0, default_target.to_owned()); - } - - for target in &self.doc_targets { - let dummy_rustdoc_json_content = serde_json::to_vec(&serde_json::json!({ - "format_version": 42 - }))?; - - for alg in RUSTDOC_JSON_COMPRESSION_ALGORITHMS { - let compressed_json: Vec = compress(&*dummy_rustdoc_json_content, *alg)?; - - for format_version in [ - RustdocJsonFormatVersion::Version(42), - RustdocJsonFormatVersion::Latest, - ] { - storage - .store_one_uncompressed( - &rustdoc_json_path( - &package.name, - &package.version, - target, - format_version, - Some(*alg), - ), - compressed_json.clone(), - ) - .await?; - } - } - } - - // Many tests rely on the default-target being linux, so it should not - // be set to docsrs_metadata::HOST_TARGET, because then tests fail on all - // non-linux platforms. - let mut async_conn = db.async_conn().await; - let crate_id = initialize_crate(&mut async_conn, &package.name).await?; - let release_id = initialize_release(&mut async_conn, crate_id, &package.version).await?; - - crate::db::finish_release( - &mut async_conn, - crate_id, - release_id, - &package, - crate_dir, - default_target, - file_list_to_json(source_meta), - self.doc_targets, - &self.registry_release_data, - self.has_docs, - self.has_examples, - iter::once(algs), - repository, - archive_storage, - 24, - ) - .await?; - crate::db::update_crate_data_in_database( - &mut async_conn, - &package.name, - &self.registry_crate_data, - ) - .await?; - for build in builds { - build - .create(&mut async_conn, &storage, release_id, default_target) - .await?; - } - if let Some(coverage) = self.doc_coverage { - crate::db::add_doc_coverage(&mut async_conn, release_id, coverage).await?; - } - - Ok(release_id) - } -} - -struct FakeGithubStats { - repo: String, - stars: i32, - forks: i32, - issues: i32, -} - -impl FakeGithubStats { - async fn create(&self, conn: &mut sqlx::PgConnection) -> Result { - let existing_count: i64 = sqlx::query_scalar!("SELECT COUNT(*) FROM repositories") - .fetch_one(&mut *conn) - .await? - .unwrap(); - let host_id = b64.encode(format!("FAKE ID {existing_count}")); - - let id = sqlx::query_scalar!( - "INSERT INTO repositories (host, host_id, name, description, last_commit, stars, forks, issues, updated_at) - VALUES ('github.com', $1, $2, 'Fake description!', NOW(), $3, $4, $5, NOW()) - RETURNING id", - host_id, self.repo, self.stars, self.forks, self.issues, - ).fetch_one(&mut *conn).await?; - - Ok(id) - } -} - -impl FakeBuild { - pub(crate) fn rustc_version(self, rustc_version: impl Into) -> Self { - Self { - rustc_version: rustc_version.into(), - ..self - } - } - - pub(crate) fn docsrs_version(self, docsrs_version: impl Into) -> Self { - Self { - docsrs_version: docsrs_version.into(), - ..self - } - } - - pub(crate) fn s3_build_log(self, build_log: impl Into) -> Self { - Self { - s3_build_log: Some(build_log.into()), - ..self - } - } - - pub(crate) fn build_log_for_other_target( - mut self, - target: impl Into, - build_log: impl Into, - ) -> Self { - self.other_build_logs - .insert(target.into(), build_log.into()); - self - } - - pub(crate) fn db_build_log(self, build_log: impl Into) -> Self { - Self { - db_build_log: Some(build_log.into()), - ..self - } - } - - pub(crate) fn no_s3_build_log(self) -> Self { - Self { - s3_build_log: None, - ..self - } - } - - pub(crate) fn successful(self, successful: bool) -> Self { - self.build_status(if successful { - BuildStatus::Success - } else { - BuildStatus::Failure - }) - } - - pub(crate) fn build_status(self, build_status: BuildStatus) -> Self { - Self { - build_status, - ..self - } - } - - async fn create( - &self, - conn: &mut sqlx::PgConnection, - storage: &AsyncStorage, - release_id: ReleaseId, - default_target: &str, - ) -> Result<()> { - let build_id = crate::db::initialize_build(&mut *conn, release_id).await?; - - crate::db::finish_build( - &mut *conn, - build_id, - &self.rustc_version, - &self.docsrs_version, - self.build_status, - Some(42), - None, - ) - .await?; - - if let Some(db_build_log) = self.db_build_log.as_deref() { - sqlx::query!( - "UPDATE builds SET output = $2 WHERE id = $1", - build_id.0, - db_build_log - ) - .execute(&mut *conn) - .await?; - } - - let prefix = format!("build-logs/{build_id}/"); - - if let Some(s3_build_log) = self.s3_build_log.as_deref() { - let path = format!("{prefix}{default_target}.txt"); - storage.store_one(path, s3_build_log).await?; - } - - for (target, log) in &self.other_build_logs { - if target == default_target { - bail!("build log for default target has to be set via `s3_build_log`"); - } - let path = format!("{prefix}{target}.txt"); - storage.store_one(path, log.as_str()).await?; - } - - Ok(()) - } -} - -impl Default for FakeBuild { - /// create a default fake _finished_ build - fn default() -> Self { - Self { - s3_build_log: Some("It works!".into()), - db_build_log: None, - other_build_logs: HashMap::new(), - rustc_version: "rustc 2.0.0-nightly (000000000 1970-01-01)".into(), - docsrs_version: "docs.rs 1.0.0 (000000000 1970-01-01)".into(), - build_status: BuildStatus::Success, - } - } -} diff --git a/src/test/headers.rs b/src/test/headers.rs deleted file mode 100644 index e67c1a57f..000000000 --- a/src/test/headers.rs +++ /dev/null @@ -1,24 +0,0 @@ -use axum_extra::headers::{self, Header, HeaderMapExt}; -use http::{HeaderMap, HeaderValue}; - -pub(crate) fn test_typed_decode(value: V) -> Result, headers::Error> -where - H: Header, - V: TryInto, - >::Error: std::fmt::Debug, -{ - let mut map = HeaderMap::new(); - map.append( - H::name(), - // this `.try_into` only generates the `HeaderValue` items. - value.try_into().unwrap(), - ); - // parsing errors from the typed header end up here. - map.typed_try_get() -} - -pub(crate) fn test_typed_encode(header: H) -> HeaderValue { - let mut map = HeaderMap::new(); - map.typed_insert(header); - map.get(H::name()).cloned().unwrap() -} diff --git a/src/test/mod.rs b/src/test/mod.rs deleted file mode 100644 index 0c52e654b..000000000 --- a/src/test/mod.rs +++ /dev/null @@ -1,664 +0,0 @@ -mod fakes; -pub(crate) mod headers; -mod test_metrics; - -pub(crate) use self::{ - fakes::{FakeBuild, fake_release_that_failed_before_build}, - test_metrics::setup_test_meter_provider, -}; -use crate::{ - AsyncBuildQueue, BuildQueue, Config, Context, - config::ConfigBuilder, - db::{self, AsyncPoolClient, Pool, types::version::Version}, - error::Result, - metrics::otel::AnyMeterProvider, - storage::{AsyncStorage, Storage, StorageKind}, - test::test_metrics::CollectedMetrics, - web::{ - build_axum_app, - cache::{self}, - headers::{IfNoneMatch, SURROGATE_CONTROL}, - page::TemplateData, - }, -}; -use anyhow::{Context as _, anyhow}; -use axum::body::Bytes; -use axum::{Router, body::Body, http::Request, response::Response as AxumResponse}; -use axum_extra::headers::{ETag, HeaderMapExt as _}; -use futures_util::stream::TryStreamExt; -use http::{ - HeaderMap, HeaderName, HeaderValue, StatusCode, - header::{CACHE_CONTROL, CONTENT_TYPE}, -}; -use http_body_util::BodyExt; -use opentelemetry_sdk::metrics::InMemoryMetricExporter; -use serde::de::DeserializeOwned; -use sqlx::Connection as _; -use std::{collections::HashMap, fs, future::Future, panic, rc::Rc, str::FromStr, sync::Arc}; -use tokio::{runtime, task::block_in_place}; -use tower::ServiceExt; -use tracing::error; - -// testing krate name constants -pub(crate) const KRATE: &str = "krate"; -// some versions as constants for tests -pub(crate) const V0_1: Version = Version::new(0, 1, 0); -pub(crate) const V1: Version = Version::new(1, 0, 0); -pub(crate) const V2: Version = Version::new(2, 0, 0); -pub(crate) const V3: Version = Version::new(3, 0, 0); - -pub(crate) fn async_wrapper(f: F) -where - F: FnOnce(Rc) -> Fut, - Fut: Future>, -{ - let env = Rc::new( - TestEnvironment::with_config_and_runtime(TestEnvironment::base_config().build().unwrap()) - .unwrap(), - ); - - env.runtime().block_on(f(env.clone())).expect("test failed"); -} - -pub(crate) fn assert_cache_headers_eq( - response: &axum::response::Response, - expected_headers: &cache::ResponseCacheHeaders, -) { - assert_eq!( - expected_headers.cache_control.as_ref(), - response.headers().get(CACHE_CONTROL), - "cache control header mismatch" - ); - assert_eq!( - expected_headers.surrogate_control.as_ref(), - response.headers().get(&SURROGATE_CONTROL), - "surrogate control header mismatch" - ); -} - -pub(crate) trait AxumResponseTestExt { - async fn text(self) -> Result; - async fn bytes(self) -> Result; - async fn json(self) -> Result; - fn redirect_target(&self) -> Option<&str>; - fn assert_cache_control(&self, cache_policy: cache::CachePolicy, config: &Config); - fn error_for_status(self) -> Result - where - Self: Sized; -} - -impl AxumResponseTestExt for axum::response::Response { - async fn text(self) -> Result { - Ok(String::from_utf8_lossy(&(self.bytes().await?)).to_string()) - } - async fn bytes(self) -> Result { - Ok(self.into_body().collect().await?.to_bytes()) - } - async fn json(self) -> Result { - let body = self.text().await?; - Ok(serde_json::from_str(&body)?) - } - fn redirect_target(&self) -> Option<&str> { - self.headers().get("Location")?.to_str().ok() - } - fn assert_cache_control(&self, cache_policy: cache::CachePolicy, config: &Config) { - assert!(config.cache_control_stale_while_revalidate.is_some()); - - // This method is only about asserting if the handler did set the right _policy_. - assert_cache_headers_eq(self, &cache_policy.render(config)); - } - - fn error_for_status(self) -> Result - where - Self: Sized, - { - let status = self.status(); - if status.is_client_error() || status.is_server_error() { - anyhow::bail!("got status code {}", status); - } else { - Ok(self) - } - } -} - -pub(crate) trait AxumRouterTestExt { - async fn get_with_headers(&self, path: &str, f: F) -> Result - where - F: FnOnce(&mut HeaderMap); - async fn get_and_follow_redirects(&self, path: &str) -> Result; - async fn assert_redirect_cached_unchecked( - &self, - path: &str, - expected_target: &str, - cache_policy: cache::CachePolicy, - config: &Config, - ) -> Result; - async fn assert_not_found(&self, path: &str) -> Result<()>; - async fn assert_conditional_get( - &self, - initial_path: &str, - uncached_response: &AxumResponse, - ) -> Result<()>; - async fn assert_success_and_conditional_get(&self, path: &str) -> Result<()>; - - async fn assert_success_cached( - &self, - path: &str, - cache_policy: cache::CachePolicy, - config: &Config, - ) -> Result; - async fn assert_success(&self, path: &str) -> Result; - async fn get(&self, path: &str) -> Result; - async fn post(&self, path: &str) -> Result; - async fn assert_redirect_common( - &self, - path: &str, - expected_target: &str, - ) -> Result; - async fn assert_redirect(&self, path: &str, expected_target: &str) -> Result; - async fn assert_redirect_unchecked( - &self, - path: &str, - expected_target: &str, - ) -> Result; - async fn assert_redirect_cached( - &self, - path: &str, - expected_target: &str, - cache_policy: cache::CachePolicy, - config: &Config, - ) -> Result; -} - -impl AxumRouterTestExt for axum::Router { - /// Make sure that a URL returns a status code between 200-299 - async fn assert_success(&self, path: &str) -> Result { - let response = self.get(path).await?; - - let status = response.status(); - if status.is_redirection() { - panic!( - "expected success response from {path}, got redirect ({status}) to {:?}", - response.redirect_target() - ); - } - assert!(status.is_success(), "failed to GET {path}: {status}"); - Ok(response) - } - - async fn assert_conditional_get( - &self, - initial_path: &str, - uncached_response: &AxumResponse, - ) -> Result<()> { - let etag: ETag = uncached_response - .headers() - .typed_get() - .ok_or_else(|| anyhow!("missing ETag header"))?; - - let if_none_match = IfNoneMatch::from(etag.clone()); - - // general rule: - // - // if a header influences how any client or intermediate proxy should treat the response, - // it should be repeated on the 304 response. - // - // This logic assumes _all_ headers have to be repeated, except for a few known ones. - const NON_CACHE_HEADERS: &[&HeaderName] = &[&CONTENT_TYPE]; - - // store original headers, to assert that they are repeated on the 304 response. - let original_headers: HashMap = uncached_response - .headers() - .iter() - .filter(|(k, _)| !NON_CACHE_HEADERS.contains(k)) - .map(|(k, v)| (k.clone(), v.clone())) - .collect(); - - { - let cached_response = self - .get_with_headers(initial_path, |headers| { - headers.typed_insert(if_none_match); - }) - .await?; - assert_eq!(cached_response.status(), StatusCode::NOT_MODIFIED); - - // most headers are repeated on the 304 response. - let cached_response_headers: HashMap = cached_response - .headers() - .iter() - .filter_map(|(k, v)| { - if original_headers.contains_key(k) { - Some((k.clone(), v.clone())) - } else { - None - } - }) - .collect(); - - assert_eq!(original_headers, cached_response_headers); - } - Ok(()) - } - - async fn assert_success_and_conditional_get(&self, path: &str) -> Result<()> { - self.assert_conditional_get(path, &self.assert_success(path).await?) - .await - } - - async fn assert_not_found(&self, path: &str) -> Result<()> { - let response = self.get(path).await?; - - // for now, 404s should always have `no-cache` - assert_cache_headers_eq(&response, &cache::NO_CACHING); - - assert_eq!(response.status(), 404, "GET {path} should have been a 404"); - Ok(()) - } - - async fn assert_success_cached( - &self, - path: &str, - cache_policy: cache::CachePolicy, - config: &Config, - ) -> Result { - let response = self.get(path).await?; - let status = response.status(); - assert!( - status.is_success(), - "failed to GET {path}: {status} (redirect: {})", - response.redirect_target().unwrap_or_default() - ); - response.assert_cache_control(cache_policy, config); - Ok(response) - } - - async fn get(&self, path: &str) -> Result { - Ok(self - .clone() - .oneshot(Request::builder().uri(path).body(Body::empty()).unwrap()) - .await?) - } - - async fn get_with_headers(&self, path: &str, f: F) -> Result - where - F: FnOnce(&mut HeaderMap), - { - let mut builder = Request::builder().uri(path); - f(builder.headers_mut().unwrap()); - - Ok(self - .clone() - .oneshot(builder.body(Body::empty()).unwrap()) - .await?) - } - - async fn get_and_follow_redirects(&self, path: &str) -> Result { - let mut path = path.to_owned(); - for _ in 0..=10 { - let response = self.get(&path).await?; - if response.status().is_redirection() - && let Some(target) = response.redirect_target() - { - path = target.to_owned(); - continue; - } - return Ok(response); - } - panic!("redirect loop"); - } - - async fn post(&self, path: &str) -> Result { - Ok(self - .clone() - .oneshot( - Request::builder() - .method("POST") - .uri(path) - .body(Body::empty()) - .unwrap(), - ) - .await?) - } - - async fn assert_redirect_common( - &self, - path: &str, - expected_target: &str, - ) -> Result { - let response = self.get(path).await?; - let status = response.status(); - if !status.is_redirection() { - anyhow::bail!("non-redirect from GET {path}: {status}"); - } - - let redirect_target = response - .redirect_target() - .context("missing 'Location' header")?; - - // FIXME: not sure we need this - // if !expected_target.starts_with("http") { - // // TODO: Should be able to use Url::make_relative, - // // but https://github.com/servo/rust-url/issues/766 - // let base = format!("http://{}", web.server_addr()); - // redirect_target = redirect_target - // .strip_prefix(&base) - // .unwrap_or(redirect_target); - // } - - if redirect_target != expected_target { - anyhow::bail!( - "got redirect to `{redirect_target}`, expected redirect to `{expected_target}`", - ); - } - - Ok(response) - } - - #[context("expected redirect from {path} to {expected_target}")] - async fn assert_redirect(&self, path: &str, expected_target: &str) -> Result { - let redirect_response = self.assert_redirect_common(path, expected_target).await?; - - let response = self.get(expected_target).await?; - let status = response.status(); - if !status.is_success() { - anyhow::bail!("failed to GET {expected_target}: {status}"); - } - - Ok(redirect_response) - } - - async fn assert_redirect_unchecked( - &self, - path: &str, - expected_target: &str, - ) -> Result { - self.assert_redirect_common(path, expected_target).await - } - - async fn assert_redirect_cached( - &self, - path: &str, - expected_target: &str, - cache_policy: cache::CachePolicy, - config: &Config, - ) -> Result { - let redirect_response = self.assert_redirect_common(path, expected_target).await?; - redirect_response.assert_cache_control(cache_policy, config); - - let response = self.get(expected_target).await?; - let status = response.status(); - if !status.is_success() { - anyhow::bail!("failed to GET {expected_target}: {status}"); - } - - Ok(redirect_response) - } - - async fn assert_redirect_cached_unchecked( - &self, - path: &str, - expected_target: &str, - cache_policy: cache::CachePolicy, - config: &Config, - ) -> Result { - let redirect_response = self.assert_redirect_common(path, expected_target).await?; - redirect_response.assert_cache_control(cache_policy, config); - Ok(redirect_response) - } -} - -pub(crate) struct TestEnvironment { - // NOTE: the database has to come before the context, - // otherwise it can happen that we can't cleanup the test database - // because the tokio runtime from the context is gone. - db: TestDatabase, - pub context: Context, - owned_runtime: Option>, - collected_metrics: InMemoryMetricExporter, -} - -pub(crate) fn init_logger() { - use tracing_subscriber::{EnvFilter, filter::Directive}; - - rustwide::logging::init_with(tracing_log::LogTracer::new()); - let subscriber = tracing_subscriber::FmtSubscriber::builder() - .with_env_filter( - EnvFilter::builder() - .with_default_directive(Directive::from_str("docs_rs=info").unwrap()) - .with_env_var("DOCSRS_LOG") - .from_env_lossy(), - ) - .with_test_writer() - .finish(); - let _ = tracing::subscriber::set_global_default(subscriber); -} - -impl TestEnvironment { - pub(crate) fn new_with_runtime() -> Result { - Self::with_config_and_runtime(Self::base_config().build()?) - } - - pub(crate) async fn new() -> Result { - Self::with_config(Self::base_config().build()?).await - } - - pub(crate) fn with_config_and_runtime(config: Config) -> Result { - let runtime = Arc::new( - runtime::Builder::new_multi_thread() - .enable_all() - .build() - .context("failed to initialize runtime")?, - ); - let mut env = runtime.block_on(Self::with_config(config))?; - env.owned_runtime = Some(runtime); - Ok(env) - } - - pub(crate) async fn with_config(config: Config) -> Result { - init_logger(); - - // create index directory - fs::create_dir_all(config.registry_index_path.clone())?; - - let (metric_exporter, meter_provider) = setup_test_meter_provider(); - - let test_db = TestDatabase::new(&config, &meter_provider) - .await - .context("can't initialize test database")?; - - Ok(Self { - context: Context::from_test_config(config, meter_provider, test_db.pool().clone()) - .await?, - db: test_db, - owned_runtime: None, - collected_metrics: metric_exporter, - }) - } - - pub(crate) fn base_config() -> ConfigBuilder { - Config::from_env() - .expect("can't load base config from environment") - // Use less connections for each test compared to production. - .max_pool_size(8) - .min_pool_idle(2) - // Use the database for storage, as it's faster than S3. - .storage_backend(StorageKind::Database) - // Use a temporary S3 bucket. - .s3_bucket(format!("docsrs-test-bucket-{}", rand::random::())) - .s3_bucket_is_temporary(true) - .local_archive_cache_path( - std::env::temp_dir().join(format!("docsrs-test-index-{}", rand::random::())), - ) - // set stale content serving so Cache::ForeverInCdn and Cache::ForeverInCdnAndStaleInBrowser - // are actually different. - .cache_control_stale_while_revalidate(Some(86400)) - .include_default_targets(true) - } - - pub(crate) fn async_build_queue(&self) -> &AsyncBuildQueue { - &self.context.async_build_queue - } - - pub(crate) fn build_queue(&self) -> &BuildQueue { - &self.context.build_queue - } - - pub(crate) fn config(&self) -> &Config { - &self.context.config - } - - pub(crate) fn async_storage(&self) -> &AsyncStorage { - &self.context.async_storage - } - - pub(crate) fn storage(&self) -> &Storage { - &self.context.storage - } - - pub(crate) fn runtime(&self) -> &runtime::Handle { - &self.context.runtime - } - - pub(crate) fn async_db(&self) -> &TestDatabase { - &self.db - } - - pub(crate) fn collected_metrics(&self) -> CollectedMetrics { - self.context.meter_provider.force_flush().unwrap(); - CollectedMetrics(self.collected_metrics.get_finished_metrics().unwrap()) - } - - pub(crate) async fn web_app(&self) -> Router { - let template_data = Arc::new(TemplateData::new(1).unwrap()); - build_axum_app(&self.context, template_data) - .await - .expect("could not build axum app") - } - - pub(crate) async fn fake_release(&self) -> fakes::FakeRelease<'_> { - fakes::FakeRelease::new(self.async_db(), self.context.async_storage.clone()) - } -} - -impl Drop for TestEnvironment { - fn drop(&mut self) { - let storage = self.context.storage.clone(); - let runtime = self.runtime(); - - block_in_place(move || { - runtime.block_on(async move { - storage - .cleanup_after_test() - .await - .expect("failed to cleanup after tests"); - }); - }); - - if self.context.config.local_archive_cache_path.exists() { - fs::remove_dir_all(&self.context.config.local_archive_cache_path).unwrap(); - } - } -} - -#[derive(Debug)] -pub(crate) struct TestDatabase { - pool: Pool, - schema: String, - runtime: runtime::Handle, -} - -impl TestDatabase { - async fn new(config: &Config, otel_meter_provider: &AnyMeterProvider) -> Result { - // A random schema name is generated and used for the current connection. This allows each - // test to create a fresh instance of the database to run within. - let schema = format!("docs_rs_test_schema_{}", rand::random::()); - - let pool = Pool::new_with_schema(config, &schema, otel_meter_provider).await?; - - let mut conn = sqlx::PgConnection::connect(&config.database_url).await?; - sqlx::query(&format!("CREATE SCHEMA {schema}")) - .execute(&mut conn) - .await - .context("error creating schema")?; - sqlx::query(&format!("SET search_path TO {schema}, public")) - .execute(&mut conn) - .await - .context("error setting search path")?; - db::migrate(&mut conn, None) - .await - .context("error running migrations")?; - - // Move all sequence start positions 10000 apart to avoid overlapping primary keys - let sequence_names: Vec<_> = sqlx::query!( - "SELECT relname - FROM pg_class - INNER JOIN pg_namespace ON - pg_class.relnamespace = pg_namespace.oid - WHERE pg_class.relkind = 'S' - AND pg_namespace.nspname = $1 - ", - schema, - ) - .fetch(&mut conn) - .map_ok(|row| row.relname) - .try_collect() - .await?; - - for (i, sequence) in sequence_names.into_iter().enumerate() { - let offset = (i + 1) * 10000; - sqlx::query(&format!( - r#"ALTER SEQUENCE "{sequence}" RESTART WITH {offset};"# - )) - .execute(&mut conn) - .await?; - } - - Ok(TestDatabase { - pool, - schema, - runtime: runtime::Handle::current(), - }) - } - - pub(crate) fn pool(&self) -> &Pool { - &self.pool - } - - pub(crate) async fn async_conn(&self) -> AsyncPoolClient { - self.pool - .get_async() - .await - .expect("failed to get a connection out of the pool") - } -} - -impl Drop for TestDatabase { - fn drop(&mut self) { - let pool = self.pool.clone(); - let schema = self.schema.clone(); - let runtime = self.runtime.clone(); - - block_in_place(move || { - runtime.block_on(async move { - let Ok(mut conn) = pool.get_async().await else { - error!("error in drop impl"); - return; - }; - - let migration_result = db::migrate(&mut conn, Some(0)).await; - - if let Err(e) = sqlx::query(format!("DROP SCHEMA {} CASCADE;", schema).as_str()) - .execute(&mut *conn) - .await - { - error!("failed to drop test schema {}: {}", schema, e); - return; - } - - if let Err(err) = migration_result { - error!(?err, "error reverting migrations"); - } - }) - }); - } -} diff --git a/src/test/test_metrics.rs b/src/test/test_metrics.rs deleted file mode 100644 index be5663aba..000000000 --- a/src/test/test_metrics.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::sync::Arc; - -use anyhow::{Result, anyhow}; -use derive_more::Deref; -use opentelemetry_sdk::metrics::{ - InMemoryMetricExporter, PeriodicReader, - data::{ - AggregatedMetrics, HistogramDataPoint, Metric, MetricData, ResourceMetrics, SumDataPoint, - }, -}; - -use crate::metrics::otel::AnyMeterProvider; - -/// set up a standalone InMemoryMetricExporter and MeterProvider for testing purposes. -/// For when you want to collect metrics, and then inspect what was collected. -pub(crate) fn setup_test_meter_provider() -> (InMemoryMetricExporter, AnyMeterProvider) { - let metric_exporter = InMemoryMetricExporter::default(); - - ( - metric_exporter.clone(), - Arc::new( - opentelemetry_sdk::metrics::SdkMeterProvider::builder() - .with_reader(PeriodicReader::builder(metric_exporter.clone()).build()) - .build(), - ), - ) -} - -/// small wrapper around the collected result of the InMemoryMetricExporter. -/// For convenience in tests. -#[derive(Debug)] -pub(crate) struct CollectedMetrics(pub(crate) Vec); - -impl CollectedMetrics { - pub(crate) fn get_metric<'a>( - &'a self, - scope: impl AsRef, - name: impl AsRef, - ) -> Result> { - let scope = scope.as_ref(); - let name = name.as_ref(); - - let scope_metrics = self - .0 - .iter() - .flat_map(|rm| rm.scope_metrics()) - .find(|sm| sm.scope().name() == scope) - .ok_or_else(|| { - anyhow!( - "Scope '{}' not found in collected metrics: {:?}", - scope, - self.0 - ) - })?; - - Ok(CollectedMetric( - scope_metrics - .metrics() - .find(|m| m.name() == name) - .ok_or_else(|| { - anyhow!( - "Metric '{}' not found in scope '{}': {:?}", - name, - scope, - scope_metrics, - ) - })?, - )) - } -} - -#[derive(Debug, Deref)] -pub(crate) struct CollectedMetric<'a>(&'a Metric); - -impl<'a> CollectedMetric<'a> { - pub(crate) fn get_u64_counter(&'a self) -> &'a SumDataPoint { - let AggregatedMetrics::U64(metric_data) = self.data() else { - panic!("Expected U64 metric data, got: {:?}", self.data()); - }; - - let MetricData::Sum(sum) = metric_data else { - panic!("Expected sum metric data, got: {:?}", metric_data); - }; - - let mut data_points = sum.data_points(); - - let result = data_points - .next() - .expect("Expected at least one data point"); - - debug_assert!(data_points.next().is_none(), "Expected only one data point"); - - result - } - - pub(crate) fn get_f64_histogram(&'a self) -> &'a HistogramDataPoint { - let AggregatedMetrics::F64(metric_data) = self.data() else { - panic!("Expected F64 metric data, got: {:?}", self.data()); - }; - - let MetricData::Histogram(histogram) = metric_data else { - panic!("Expected Histogram metric data, got: {:?}", metric_data); - }; - - let mut data_points = histogram.data_points(); - - let result = data_points - .next() - .expect("Expected at least one data point"); - - debug_assert!(data_points.next().is_none(), "Expected only one data point"); - - result - } -} diff --git a/src/utils/queue_builder.rs b/src/utils/queue_builder.rs deleted file mode 100644 index 21034c2cd..000000000 --- a/src/utils/queue_builder.rs +++ /dev/null @@ -1,70 +0,0 @@ -use crate::Context; -use crate::{docbuilder::RustwideBuilder, utils::report_error}; -use anyhow::{Context as _, Error}; -use std::panic::{AssertUnwindSafe, catch_unwind}; -use std::time::Duration; -use std::{fs, io, path::Path, thread}; -use tracing::{debug, error, warn}; - -/// the main build-server loop -pub fn queue_builder(context: &Context, mut builder: RustwideBuilder) -> Result<(), Error> { - todo!(); - // loop { - // let temp_dir = &context.config.temp_dir; - // if temp_dir.exists() - // && let Err(e) = remove_tempdirs(temp_dir) - // { - // report_error(&anyhow::anyhow!(e).context(format!( - // "failed to clean temporary directory {:?}", - // temp_dir - // ))); - // } - - // let build_queue = &context.build_queue; - - // // check lock file - // match build_queue.is_locked().context("could not get queue lock") { - // Ok(true) => { - // warn!("Build queue is locked, skipping building new crates"); - // thread::sleep(Duration::from_secs(60)); - // continue; - // } - // Ok(false) => {} - // Err(err) => { - // report_error(&err); - // thread::sleep(Duration::from_secs(60)); - // continue; - // } - // } - - // // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. - // debug!("Checking build queue"); - // let res = catch_unwind(AssertUnwindSafe(|| { - // match build_queue.build_next_queue_package(context, &mut builder) { - // Ok(true) => {} - // Ok(false) => { - // debug!("Queue is empty, going back to sleep"); - // thread::sleep(Duration::from_secs(60)); - // } - // Err(e) => { - // report_error(&e.context("Failed to build crate from queue")); - // } - // } - // })); - - // if let Err(e) = res { - // error!("GRAVE ERROR Building new crates panicked: {:?}", e); - // thread::sleep(Duration::from_secs(60)); - // continue; - // } - // } -} - -/// Sometimes, when the server hits a hard crash or a build thread panics, -/// rustwide_builder won't actually remove the temporary directories it creates. -/// Remove them now to avoid running out of disk space. -fn remove_tempdirs>(path: P) -> Result<(), io::Error> { - fs::remove_dir_all(&path)?; - fs::create_dir_all(&path)?; - Ok(()) -} From 95e7e9ce079f609f53d1e568eca01be0e6d4ead4 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 11:26:25 +0100 Subject: [PATCH 23/46] more --- Cargo.lock | 26 +- Cargo.toml | 50 ++-- crates/docs_rs_builder/Cargo.toml | 3 +- .../src/docbuilder/rustwide_builder.rs | 4 +- crates/docs_rs_cargo_metadata/Cargo.toml | 1 - crates/docs_rs_cargo_metadata/src/lib.rs | 20 +- crates/docs_rs_database/src/migrate.rs | 108 ++++---- crates/docs_rs_registry_api/Cargo.toml | 7 +- crates/docs_rs_repository_stats/Cargo.toml | 21 ++ crates/docs_rs_repository_stats/src/config.rs | 21 ++ .../src}/github.rs | 242 +++++++++--------- .../src}/gitlab.rs | 196 +++++++------- .../src/lib.rs} | 2 + crates/docs_rs_repository_stats/src/mod.rs | 0 .../src}/updater.rs | 6 +- crates/docs_rs_watcher/Cargo.toml | 2 +- crates/docs_rs_watcher/src/config.rs | 13 +- crates/docs_rs_watcher/src/lib.rs | 1 - crates/docs_rs_watcher/src/main.rs | 6 +- 19 files changed, 384 insertions(+), 345 deletions(-) create mode 100644 crates/docs_rs_repository_stats/Cargo.toml create mode 100644 crates/docs_rs_repository_stats/src/config.rs rename crates/{docs_rs_watcher/src/repositories => docs_rs_repository_stats/src}/github.rs (62%) rename crates/{docs_rs_watcher/src/repositories => docs_rs_repository_stats/src}/gitlab.rs (68%) rename crates/{docs_rs_watcher/src/repositories/mod.rs => docs_rs_repository_stats/src/lib.rs} (84%) create mode 100644 crates/docs_rs_repository_stats/src/mod.rs rename crates/{docs_rs_watcher/src/repositories => docs_rs_repository_stats/src}/updater.rs (98%) diff --git a/Cargo.lock b/Cargo.lock index d4038f06b..e2fc8b820 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1919,7 +1919,6 @@ dependencies = [ "rayon", "regex", "reqwest", - "rustwide", "semver", "sentry", "serde", @@ -1982,6 +1981,7 @@ dependencies = [ "docs_rs_env_vars", "docs_rs_opentelemetry", "docs_rs_registry_api", + "docs_rs_repository_stats", "docs_rs_storage", "docs_rs_utils", "docs_rs_watcher", @@ -2130,6 +2130,7 @@ name = "docs_rs_registry_api" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", "bincode 2.0.1", "chrono", "docs_rs_database", @@ -2142,6 +2143,27 @@ dependencies = [ "url", ] +[[package]] +name = "docs_rs_repository_stats" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "docs_rs_cargo_metadata", + "docs_rs_database", + "docs_rs_env_vars", + "docs_rs_utils", + "futures-util", + "regex", + "reqwest", + "serde", + "serde_json", + "sqlx", + "thiserror 2.0.17", + "tracing", +] + [[package]] name = "docs_rs_storage" version = "0.1.0" @@ -2197,7 +2219,6 @@ name = "docs_rs_watcher" version = "0.1.0" dependencies = [ "anyhow", - "async-trait", "chrono", "clap", "crates-index", @@ -2210,6 +2231,7 @@ dependencies = [ "docs_rs_fastly", "docs_rs_logging", "docs_rs_opentelemetry", + "docs_rs_repository_stats", "docs_rs_storage", "docs_rs_utils", "futures-util", diff --git a/Cargo.toml b/Cargo.toml index ff0cb3d0e..2f0fd6a94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,43 +13,40 @@ members = ["crates/*"] [workspace.dependencies] anyhow = { version = "1.0.42", features = ["backtrace"]} +askama = "0.14.0" +async-stream = "0.3.5" bincode = "2.0.1" chrono = { version = "0.4.11", default-features = false, features = ["clock", "serde"] } +clap = { version = "4.0.22", features = [ "derive" ] } derive_more = { version = "2.0.0", features = ["display", "deref", "from", "into", "from_str"] } futures-util = "0.3.5" +http = "1.0.0" +itertools = { version = "0.14.0" } +mime = "0.3.16" +mockito = "1.0.2" opentelemetry = "0.31.0" opentelemetry-otlp = { version = "0.31.0", features = ["grpc-tonic", "metrics"] } opentelemetry-resource-detectors = "0.10.0" opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } +rayon = "1.6.1" +regex = "1" +reqwest = { version = "0.12", features = ["json", "gzip"] } semver = { version = "1.0.4", features = ["serde"] } +sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_with = "3.4.0" -itertools = { version = "0.14.0" } +slug = "0.1.1" sqlx = { version = "0.8", features = [ "runtime-tokio", "postgres", "sqlite", "chrono" ] } strum = { version = "0.27.0", features = ["derive"] } +tempfile = "3.1.0" +test-case = "3.0.0" thiserror = "2.0.3" tokio = { version = "1.0", features = ["rt-multi-thread", "signal", "macros", "process", "sync"] } -walkdir = "2" -regex = "1" +toml = "0.9.2" tracing = "0.1.37" url = { version = "2.1.1", features = ["serde"] } -rayon = "1.6.1" -mime = "0.3.16" -async-stream = "0.3.5" -tempfile = "3.1.0" -http = "1.0.0" -reqwest = { version = "0.12", features = ["json", "gzip"] } -rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } -sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } -clap = { version = "4.0.22", features = [ "derive" ] } -askama = "0.14.0" -toml = "0.9.2" -slug = "0.1.1" - -# dev dependencies -test-case = "3.0.0" -mockito = "1.0.2" +walkdir = "2" [dependencies] anyhow = { workspace = true } @@ -58,19 +55,19 @@ bincode = { workspace = true } chrono = { workspace = true } clap = { workspace = true } derive_more = { workspace = true } -docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } -docs_rs_utils = { path = "crates/docs_rs_utils" } -docs_rs_database = { path = "crates/docs_rs_database" } docs_rs_build_queue = { path = "crates/docs_rs_build_queue" } -docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } docs_rs_cargo_metadata = { path = "crates/docs_rs_cargo_metadata" } -docs_rs_storage = { path = "crates/docs_rs_storage" } +docs_rs_database = { path = "crates/docs_rs_database" } +docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } docs_rs_headers = { path = "crates/docs_rs_headers" } -docs_rs_watcher = { path = "crates/docs_rs_watcher" } docs_rs_logging = { path = "crates/docs_rs_logging" } +docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } +docs_rs_registry_api = { path = "crates/docs_rs_registry_api" } +docs_rs_storage = { path = "crates/docs_rs_storage" } +docs_rs_utils = { path = "crates/docs_rs_utils" } +docs_rs_watcher = { path = "crates/docs_rs_watcher" } docs_rs_web_utils = { path = "crates/docs_rs_web_utils" } docsrs-metadata = { path = "crates/metadata" } -docs_rs_registry_api = { path = "crates/docs_rs_registry_api" } fn-error-context = "0.2.0" futures-util = { workspace = true } getrandom = "0.3.1" @@ -86,7 +83,6 @@ path-slash = "0.2.0" rayon = { workspace = true } regex = { workspace = true } reqwest = { workspace = true } -rustwide = { workspace = true } semver = { workspace = true } sentry = { workspace = true } serde = { workspace = true } diff --git a/crates/docs_rs_builder/Cargo.toml b/crates/docs_rs_builder/Cargo.toml index 735c884b8..28e872c15 100644 --- a/crates/docs_rs_builder/Cargo.toml +++ b/crates/docs_rs_builder/Cargo.toml @@ -14,6 +14,7 @@ docs_rs_watcher = { path = "../docs_rs_watcher" } docs_rs_registry_api = { path = "../docs_rs_registry_api" } docs_rs_build_utils = { path = "../docs_rs_build_utils" } docs_rs_context = { path = "../docs_rs_context" } +docs_rs_repository_stats = { path = "../docs_rs_repository_stats" } url = { workspace = true } anyhow = { workspace = true } serde_json = { workspace = true } @@ -24,7 +25,7 @@ docsrs-metadata = { path = "../metadata" } opentelemetry = { workspace = true } itertools = { workspace = true } regex = { workspace = true } -rustwide = { workspace = true } +rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } tracing = { workspace = true } sqlx = { workspace = true } thiserror = { workspace = true } diff --git a/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs index 1b00f3eeb..dcc820aea 100644 --- a/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -21,6 +21,7 @@ use docs_rs_database::{ }; use docs_rs_opentelemetry::AnyMeterProvider; use docs_rs_registry_api::RegistryApi; +use docs_rs_repository_stast::RepositoryStatsUpdater; use docs_rs_storage::{ AsyncStorage, RustdocJsonFormatVersion, Storage, compress, compression::CompressionAlgorithm, @@ -29,7 +30,6 @@ use docs_rs_storage::{ }; use docs_rs_utils::rustc_version::parse_rustc_version; use docs_rs_utils::{BUILD_VERSION, RUSTDOC_STATIC_STORAGE_PREFIX, retry}; -use docs_rs_watcher::repositories::RepositoryStatsUpdater; use docsrs_metadata::{BuildTargets, DEFAULT_TARGETS, HOST_TARGET, Metadata}; use itertools::Itertools as _; use opentelemetry::metrics::{Counter, Histogram}; @@ -214,7 +214,7 @@ impl RustwideBuilder { storage: context.blocking_storage()?, async_storage: context.storage()?, registry_api: RegistryApi::from_environment()?.into(), - repository_stats_updater: context.repository_stats_updater.clone(), + repository_stats_updater: RepositoryStatsUpdater::new()?.into(), workspace_initialize_time: Instant::now(), builder_metrics: BuilderMetrics::new(context.meter_provider()).into(), }) diff --git a/crates/docs_rs_cargo_metadata/Cargo.toml b/crates/docs_rs_cargo_metadata/Cargo.toml index ae2fe3900..cf9e881e3 100644 --- a/crates/docs_rs_cargo_metadata/Cargo.toml +++ b/crates/docs_rs_cargo_metadata/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" anyhow = { workspace = true } bincode = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } -# rustwide = { workspace = true } semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/docs_rs_cargo_metadata/src/lib.rs b/crates/docs_rs_cargo_metadata/src/lib.rs index 02b63effc..c2388e04e 100644 --- a/crates/docs_rs_cargo_metadata/src/lib.rs +++ b/crates/docs_rs_cargo_metadata/src/lib.rs @@ -1,34 +1,16 @@ pub mod db; -use anyhow::{Context, Result, bail}; +use anyhow::{Context, Result}; use docs_rs_database::types::version::Version; -// use rustwide::{Toolchain, Workspace, cmd::Command}; use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use std::path::Path; pub struct CargoMetadata { root: Package, } impl CargoMetadata { - // pub fn load_from_rustwide( - // workspace: &Workspace, - // toolchain: &Toolchain, - // source_dir: &Path, - // ) -> Result { - // let res = Command::new(workspace, toolchain.cargo()) - // .args(&["metadata", "--format-version", "1"]) - // .cd(source_dir) - // .log_output(false) - // .run_capture()?; - // let [metadata] = res.stdout_lines() else { - // bail!("invalid output returned by `cargo metadata`") - // }; - // Self::load_from_metadata(metadata) - // } - #[cfg(test)] pub fn load_from_host_path(source_dir: &Path) -> Result { let res = std::process::Command::new("cargo") diff --git a/crates/docs_rs_database/src/migrate.rs b/crates/docs_rs_database/src/migrate.rs index 09d5eb98f..ca73b7f63 100644 --- a/crates/docs_rs_database/src/migrate.rs +++ b/crates/docs_rs_database/src/migrate.rs @@ -1,61 +1,61 @@ -use anyhow::Result; -use hex; -use sqlx::migrate::{Migrate, Migrator}; +// use anyhow::Result; +// use hex; +// use sqlx::migrate::{Migrate, Migrator}; -static MIGRATOR: Migrator = sqlx::migrate!(); +// static MIGRATOR: Migrator = sqlx::migrate!(); -pub async fn migrate(conn: &mut sqlx::PgConnection, target: Option) -> Result<()> { - conn.ensure_migrations_table().await?; +// pub async fn migrate(conn: &mut sqlx::PgConnection, target: Option) -> Result<()> { +// conn.ensure_migrations_table().await?; - // `database_versions` is the table that tracked the old `schemamama` migrations. - // If we find the table, and it contains records, we insert a fake record - // into the `_sqlx_migrations` table so the big initial migration isn't executed. - if sqlx::query( - "SELECT table_name - FROM information_schema.tables - WHERE table_schema = 'public' AND table_name = 'database_versions'", - ) - .fetch_optional(&mut *conn) - .await? - .is_some() - { - let max_version: Option = - sqlx::query_scalar("SELECT max(version) FROM database_versions") - .fetch_one(&mut *conn) - .await?; +// // `database_versions` is the table that tracked the old `schemamama` migrations. +// // If we find the table, and it contains records, we insert a fake record +// // into the `_sqlx_migrations` table so the big initial migration isn't executed. +// if sqlx::query( +// "SELECT table_name +// FROM information_schema.tables +// WHERE table_schema = 'public' AND table_name = 'database_versions'", +// ) +// .fetch_optional(&mut *conn) +// .await? +// .is_some() +// { +// let max_version: Option = +// sqlx::query_scalar("SELECT max(version) FROM database_versions") +// .fetch_one(&mut *conn) +// .await?; - if max_version != Some(39) { - anyhow::bail!( - "database_versions table has unexpected version: {:?}", - max_version - ); - } +// if max_version != Some(39) { +// anyhow::bail!( +// "database_versions table has unexpected version: {:?}", +// max_version +// ); +// } - sqlx::query( - "INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time ) - VALUES ( $1, $2, TRUE, $3, -1 )", - ) - // the next two parameters relate to the filename of the initial migration file - .bind(20231021111635i64) - .bind("initial") - // this is the hash of the initial migration file, as sqlx requires it. - // if the initial migration file changes, this has to be updated with the new value, - // easiest to get from the `_sqlx_migrations` table when the migration was normally - // executed. - .bind(hex::decode("df802e0ec416063caadd1c06b13348cd885583c44962998886b929d5fe6ef3b70575d5101c5eb31daa989721df08d806").unwrap()) - .execute(&mut *conn) - .await?; +// sqlx::query( +// "INSERT INTO _sqlx_migrations ( version, description, success, checksum, execution_time ) +// VALUES ( $1, $2, TRUE, $3, -1 )", +// ) +// // the next two parameters relate to the filename of the initial migration file +// .bind(20231021111635i64) +// .bind("initial") +// // this is the hash of the initial migration file, as sqlx requires it. +// // if the initial migration file changes, this has to be updated with the new value, +// // easiest to get from the `_sqlx_migrations` table when the migration was normally +// // executed. +// .bind(hex::decode("df802e0ec416063caadd1c06b13348cd885583c44962998886b929d5fe6ef3b70575d5101c5eb31daa989721df08d806").unwrap()) +// .execute(&mut *conn) +// .await?; - sqlx::query("DROP TABLE database_versions") - .execute(&mut *conn) - .await?; - } +// sqlx::query("DROP TABLE database_versions") +// .execute(&mut *conn) +// .await?; +// } - // when we find records - if let Some(target) = target { - MIGRATOR.undo(conn, target).await?; - } else { - MIGRATOR.run(conn).await?; - } - Ok(()) -} +// // when we find records +// if let Some(target) = target { +// MIGRATOR.undo(conn, target).await?; +// } else { +// MIGRATOR.run(conn).await?; +// } +// Ok(()) +// } diff --git a/crates/docs_rs_registry_api/Cargo.toml b/crates/docs_rs_registry_api/Cargo.toml index d07d25bec..5162915b3 100644 --- a/crates/docs_rs_registry_api/Cargo.toml +++ b/crates/docs_rs_registry_api/Cargo.toml @@ -5,13 +5,14 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +async-trait = "0.1.83" +bincode = { workspace = true } chrono = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_utils = { path = "../docs_rs_utils" } reqwest = { workspace = true } serde = { workspace = true } +sqlx = { workspace = true } tracing = { workspace = true } url = { workspace = true } -sqlx = { workspace = true } -bincode = { workspace = true } -docs_rs_env_vars = { path = "../docs_rs_env_vars" } diff --git a/crates/docs_rs_repository_stats/Cargo.toml b/crates/docs_rs_repository_stats/Cargo.toml new file mode 100644 index 000000000..ce4018266 --- /dev/null +++ b/crates/docs_rs_repository_stats/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "docs_rs_repository_stats" +version = "0.1.0" +edition = "2024" + +[dependencies] +anyhow = { workspace = true } +async-trait = "0.1.89" +chrono = { workspace = true } +docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } +docs_rs_database = { path = "../docs_rs_database" } +docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_utils = { path = "../docs_rs_utils" } +futures-util = { workspace = true } +regex = { workspace = true } +reqwest = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sqlx = { workspace = true } +thiserror = { workspace = true } +tracing = { workspace = true } diff --git a/crates/docs_rs_repository_stats/src/config.rs b/crates/docs_rs_repository_stats/src/config.rs new file mode 100644 index 000000000..8ff361f4d --- /dev/null +++ b/crates/docs_rs_repository_stats/src/config.rs @@ -0,0 +1,21 @@ +use docs_rs_env_vars::{env, maybe_env}; + +#[derive(Debug)] +pub struct Config { + // Github authentication + pub(crate) github_accesstoken: Option, + pub(crate) github_updater_min_rate_limit: u32, + + // GitLab authentication + pub(crate) gitlab_accesstoken: Option, +} + +impl Config { + pub fn from_environment() -> anyhow::Result { + Ok(Self { + github_accesstoken: maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?, + github_updater_min_rate_limit: env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500u32)?, + gitlab_accesstoken: maybe_env("DOCSRS_GITLAB_ACCESSTOKEN")?, + }) + } +} diff --git a/crates/docs_rs_watcher/src/repositories/github.rs b/crates/docs_rs_repository_stats/src/github.rs similarity index 62% rename from crates/docs_rs_watcher/src/repositories/github.rs rename to crates/docs_rs_repository_stats/src/github.rs index 19241cd64..ab123380d 100644 --- a/crates/docs_rs_watcher/src/repositories/github.rs +++ b/crates/docs_rs_repository_stats/src/github.rs @@ -1,9 +1,7 @@ use crate::{ + RateLimitReached, config::Config, - repositories::{ - RateLimitReached, - updater::{FetchRepositoriesResult, Repository, RepositoryForge, RepositoryName}, - }, + updater::{FetchRepositoriesResult, Repository, RepositoryForge, RepositoryName}, }; use anyhow::Result; use async_trait::async_trait; @@ -266,121 +264,121 @@ struct GraphIssues { total_count: i64, } -#[cfg(test)] -mod tests { - use super::{Config, GitHub}; - use crate::repositories::RateLimitReached; - use crate::repositories::updater::{RepositoryForge, repository_name}; - use anyhow::Result; - - const TEST_TOKEN: &str = "qsjdnfqdq"; - - fn github_config() -> anyhow::Result { - let mut cfg = Config::from_environment()?; - cfg.github_accesstoken = Some(TEST_TOKEN.to_owned()); - Ok(cfg) - } - - async fn mock_server_and_github(config: &Config) -> (mockito::ServerGuard, GitHub) { - let server = mockito::Server::new_async().await; - let updater = GitHub::with_custom_endpoint(config, format!("{}/graphql", server.url())) - .expect("GitHub::new failed") - .unwrap(); - - (server, updater) - } - - #[tokio::test] - async fn test_rate_limit_fail() -> Result<()> { - let config = github_config()?; - let (mut server, updater) = mock_server_and_github(&config).await; - - let _m1 = server - .mock("POST", "/graphql") - .with_header("content-type", "application/json") - .with_body( - r#"{"errors":[{"type":"RATE_LIMITED","message":"API rate limit exceeded"}]}"#, - ) - .create(); - - match updater.fetch_repositories(&[String::new()]).await { - Err(e) if e.downcast_ref::().is_some() => {} - x => panic!("Expected Err(RateLimitReached), found: {x:?}"), - } - Ok(()) - } - - #[tokio::test] - async fn test_rate_limit_manual() -> Result<()> { - let config = github_config()?; - let (mut server, updater) = mock_server_and_github(&config).await; - - let _m1 = server - .mock("POST", "/graphql") - .with_header("content-type", "application/json") - .with_body(r#"{"data": {"nodes": [], "rateLimit": {"remaining": 0}}}"#) - .create(); - - match updater.fetch_repositories(&[String::new()]).await { - Err(e) if e.downcast_ref::().is_some() => {} - x => panic!("Expected Err(RateLimitReached), found: {x:?}"), - } - Ok(()) - } - - #[tokio::test] - async fn not_found() -> Result<()> { - let config = github_config()?; - let (mut server, updater) = mock_server_and_github(&config).await; - - let _m1 = server - .mock("POST", "/graphql") - .with_header("content-type", "application/json") - .with_body( - r#"{"data": {"nodes": [], "rateLimit": {"remaining": 100000}}, "errors": - [{"type": "NOT_FOUND", "path": ["nodes", 0], "message": "none"}]}"#, - ) - .create(); - - match updater.fetch_repositories(&[String::new()]).await { - Ok(res) => { - assert_eq!(res.missing, vec![String::new()]); - assert_eq!(res.present.len(), 0); - } - x => panic!("Failed: {x:?}"), - } - Ok(()) - } - - #[tokio::test] - async fn get_repository_info() -> Result<()> { - let config = github_config()?; - let (mut server, updater) = mock_server_and_github(&config).await; - - let _m1 = server - .mock("POST", "/graphql") - .with_header("content-type", "application/json") - .with_body( - r#"{"data": {"repository": {"id": "hello", "nameWithOwner": "foo/bar", - "description": "this is", "stargazerCount": 10, "forkCount": 11, - "issues": {"totalCount": 12}}}}"#, - ) - .create(); - - let repo = updater - .fetch_repository( - &repository_name("https://gitlab.com/foo/bar").expect("repository_name failed"), - ) - .await - .expect("fetch_repository failed") - .unwrap(); - - assert_eq!(repo.id, "hello"); - assert_eq!(repo.name_with_owner, "foo/bar"); - assert_eq!(repo.description, Some("this is".to_owned())); - assert_eq!(repo.stars, 10); - assert_eq!(repo.forks, 11); - assert_eq!(repo.issues, 12); - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::{Config, GitHub}; +// use crate::repositories::RateLimitReached; +// use crate::repositories::updater::{RepositoryForge, repository_name}; +// use anyhow::Result; + +// const TEST_TOKEN: &str = "qsjdnfqdq"; + +// fn github_config() -> anyhow::Result { +// let mut cfg = Config::from_environment()?; +// cfg.github_accesstoken = Some(TEST_TOKEN.to_owned()); +// Ok(cfg) +// } + +// async fn mock_server_and_github(config: &Config) -> (mockito::ServerGuard, GitHub) { +// let server = mockito::Server::new_async().await; +// let updater = GitHub::with_custom_endpoint(config, format!("{}/graphql", server.url())) +// .expect("GitHub::new failed") +// .unwrap(); + +// (server, updater) +// } + +// #[tokio::test] +// async fn test_rate_limit_fail() -> Result<()> { +// let config = github_config()?; +// let (mut server, updater) = mock_server_and_github(&config).await; + +// let _m1 = server +// .mock("POST", "/graphql") +// .with_header("content-type", "application/json") +// .with_body( +// r#"{"errors":[{"type":"RATE_LIMITED","message":"API rate limit exceeded"}]}"#, +// ) +// .create(); + +// match updater.fetch_repositories(&[String::new()]).await { +// Err(e) if e.downcast_ref::().is_some() => {} +// x => panic!("Expected Err(RateLimitReached), found: {x:?}"), +// } +// Ok(()) +// } + +// #[tokio::test] +// async fn test_rate_limit_manual() -> Result<()> { +// let config = github_config()?; +// let (mut server, updater) = mock_server_and_github(&config).await; + +// let _m1 = server +// .mock("POST", "/graphql") +// .with_header("content-type", "application/json") +// .with_body(r#"{"data": {"nodes": [], "rateLimit": {"remaining": 0}}}"#) +// .create(); + +// match updater.fetch_repositories(&[String::new()]).await { +// Err(e) if e.downcast_ref::().is_some() => {} +// x => panic!("Expected Err(RateLimitReached), found: {x:?}"), +// } +// Ok(()) +// } + +// #[tokio::test] +// async fn not_found() -> Result<()> { +// let config = github_config()?; +// let (mut server, updater) = mock_server_and_github(&config).await; + +// let _m1 = server +// .mock("POST", "/graphql") +// .with_header("content-type", "application/json") +// .with_body( +// r#"{"data": {"nodes": [], "rateLimit": {"remaining": 100000}}, "errors": +// [{"type": "NOT_FOUND", "path": ["nodes", 0], "message": "none"}]}"#, +// ) +// .create(); + +// match updater.fetch_repositories(&[String::new()]).await { +// Ok(res) => { +// assert_eq!(res.missing, vec![String::new()]); +// assert_eq!(res.present.len(), 0); +// } +// x => panic!("Failed: {x:?}"), +// } +// Ok(()) +// } + +// #[tokio::test] +// async fn get_repository_info() -> Result<()> { +// let config = github_config()?; +// let (mut server, updater) = mock_server_and_github(&config).await; + +// let _m1 = server +// .mock("POST", "/graphql") +// .with_header("content-type", "application/json") +// .with_body( +// r#"{"data": {"repository": {"id": "hello", "nameWithOwner": "foo/bar", +// "description": "this is", "stargazerCount": 10, "forkCount": 11, +// "issues": {"totalCount": 12}}}}"#, +// ) +// .create(); + +// let repo = updater +// .fetch_repository( +// &repository_name("https://gitlab.com/foo/bar").expect("repository_name failed"), +// ) +// .await +// .expect("fetch_repository failed") +// .unwrap(); + +// assert_eq!(repo.id, "hello"); +// assert_eq!(repo.name_with_owner, "foo/bar"); +// assert_eq!(repo.description, Some("this is".to_owned())); +// assert_eq!(repo.stars, 10); +// assert_eq!(repo.forks, 11); +// assert_eq!(repo.issues, 12); +// Ok(()) +// } +// } diff --git a/crates/docs_rs_watcher/src/repositories/gitlab.rs b/crates/docs_rs_repository_stats/src/gitlab.rs similarity index 68% rename from crates/docs_rs_watcher/src/repositories/gitlab.rs rename to crates/docs_rs_repository_stats/src/gitlab.rs index 637ad4110..45bf4da9e 100644 --- a/crates/docs_rs_watcher/src/repositories/gitlab.rs +++ b/crates/docs_rs_repository_stats/src/gitlab.rs @@ -11,7 +11,7 @@ use std::collections::HashSet; use std::str::FromStr; use tracing::warn; -use crate::repositories::{ +use crate::{ RateLimitReached, updater::{FetchRepositoriesResult, Repository, RepositoryForge, RepositoryName}, }; @@ -263,100 +263,100 @@ struct GraphProject { open_issues_count: Option, } -#[cfg(test)] -mod tests { - use super::GitLab; - use crate::repositories::RateLimitReached; - use crate::repositories::updater::{RepositoryForge, repository_name}; - use anyhow::Result; - - async fn mock_server_and_gitlab() -> (mockito::ServerGuard, GitLab) { - let server = mockito::Server::new_async().await; - let updater = GitLab::with_custom_endpoint( - "gitlab.com", - &None, - format!("{}/api/graphql", server.url()), - ) - .expect("GitLab::new failed"); - - (server, updater) - } - - #[tokio::test] - async fn test_rate_limit() -> Result<()> { - let (mut server, updater) = mock_server_and_gitlab().await; - - let _m1 = server - .mock("POST", "/api/graphql") - .with_header("content-type", "application/json") - .with_header("RateLimit-Remaining", "0") - .with_body("{}") - .create(); - - match updater - .fetch_repository( - &repository_name("https://gitlab.com/foo/bar").expect("repository_name failed"), - ) - .await - { - Err(e) if e.downcast_ref::().is_some() => {} - x => panic!("Expected Err(RateLimitReached), found: {x:?}"), - } - match updater.fetch_repositories(&[String::new()]).await { - Err(e) if e.downcast_ref::().is_some() => {} - x => panic!("Expected Err(RateLimitReached), found: {x:?}"), - } - Ok(()) - } - - #[tokio::test] - async fn not_found() -> Result<()> { - let (mut server, updater) = mock_server_and_gitlab().await; - - let _m1 = server - .mock("POST", "/api/graphql") - .with_header("content-type", "application/json") - .with_body(r#"{"data": {"projects": {"nodes": []}}}"#) - .create(); - - match updater.fetch_repositories(&[String::new()]).await { - Ok(res) => { - assert_eq!(res.missing, vec![String::new()]); - assert_eq!(res.present.len(), 0); - } - x => panic!("Failed: {x:?}"), - } - Ok(()) - } - - #[tokio::test] - async fn get_repository_info() -> Result<()> { - let (mut server, updater) = mock_server_and_gitlab().await; - - let _m1 = server - .mock("POST", "/api/graphql") - .with_header("content-type", "application/json") - .with_body( - r#"{"data": {"project": {"id": "hello", "fullPath": "foo/bar", - "description": "this is", "starCount": 10, "forksCount": 11, - "openIssuesCount": 12}}}"#, - ) - .create(); - - let repo = updater - .fetch_repository( - &repository_name("https://gitlab.com/foo/bar").expect("repository_name failed"), - ) - .await - .expect("fetch_repository failed") - .unwrap(); - - assert_eq!(repo.id, "hello"); - assert_eq!(repo.name_with_owner, "foo/bar"); - assert_eq!(repo.description, Some("this is".to_owned())); - assert_eq!(repo.stars, 10); - assert_eq!(repo.forks, 11); - assert_eq!(repo.issues, 12); - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::GitLab; +// use crate::repositories::RateLimitReached; +// use crate::repositories::updater::{RepositoryForge, repository_name}; +// use anyhow::Result; + +// async fn mock_server_and_gitlab() -> (mockito::ServerGuard, GitLab) { +// let server = mockito::Server::new_async().await; +// let updater = GitLab::with_custom_endpoint( +// "gitlab.com", +// &None, +// format!("{}/api/graphql", server.url()), +// ) +// .expect("GitLab::new failed"); + +// (server, updater) +// } + +// #[tokio::test] +// async fn test_rate_limit() -> Result<()> { +// let (mut server, updater) = mock_server_and_gitlab().await; + +// let _m1 = server +// .mock("POST", "/api/graphql") +// .with_header("content-type", "application/json") +// .with_header("RateLimit-Remaining", "0") +// .with_body("{}") +// .create(); + +// match updater +// .fetch_repository( +// &repository_name("https://gitlab.com/foo/bar").expect("repository_name failed"), +// ) +// .await +// { +// Err(e) if e.downcast_ref::().is_some() => {} +// x => panic!("Expected Err(RateLimitReached), found: {x:?}"), +// } +// match updater.fetch_repositories(&[String::new()]).await { +// Err(e) if e.downcast_ref::().is_some() => {} +// x => panic!("Expected Err(RateLimitReached), found: {x:?}"), +// } +// Ok(()) +// } + +// #[tokio::test] +// async fn not_found() -> Result<()> { +// let (mut server, updater) = mock_server_and_gitlab().await; + +// let _m1 = server +// .mock("POST", "/api/graphql") +// .with_header("content-type", "application/json") +// .with_body(r#"{"data": {"projects": {"nodes": []}}}"#) +// .create(); + +// match updater.fetch_repositories(&[String::new()]).await { +// Ok(res) => { +// assert_eq!(res.missing, vec![String::new()]); +// assert_eq!(res.present.len(), 0); +// } +// x => panic!("Failed: {x:?}"), +// } +// Ok(()) +// } + +// #[tokio::test] +// async fn get_repository_info() -> Result<()> { +// let (mut server, updater) = mock_server_and_gitlab().await; + +// let _m1 = server +// .mock("POST", "/api/graphql") +// .with_header("content-type", "application/json") +// .with_body( +// r#"{"data": {"project": {"id": "hello", "fullPath": "foo/bar", +// "description": "this is", "starCount": 10, "forksCount": 11, +// "openIssuesCount": 12}}}"#, +// ) +// .create(); + +// let repo = updater +// .fetch_repository( +// &repository_name("https://gitlab.com/foo/bar").expect("repository_name failed"), +// ) +// .await +// .expect("fetch_repository failed") +// .unwrap(); + +// assert_eq!(repo.id, "hello"); +// assert_eq!(repo.name_with_owner, "foo/bar"); +// assert_eq!(repo.description, Some("this is".to_owned())); +// assert_eq!(repo.stars, 10); +// assert_eq!(repo.forks, 11); +// assert_eq!(repo.issues, 12); +// Ok(()) +// } +// } diff --git a/crates/docs_rs_watcher/src/repositories/mod.rs b/crates/docs_rs_repository_stats/src/lib.rs similarity index 84% rename from crates/docs_rs_watcher/src/repositories/mod.rs rename to crates/docs_rs_repository_stats/src/lib.rs index 5f91bc297..6ff2cb02c 100644 --- a/crates/docs_rs_watcher/src/repositories/mod.rs +++ b/crates/docs_rs_repository_stats/src/lib.rs @@ -1,3 +1,4 @@ +pub use self::config::Config; pub use self::github::GitHub; pub use self::gitlab::GitLab; pub use self::updater::RepositoryStatsUpdater; @@ -6,6 +7,7 @@ pub use self::updater::RepositoryStatsUpdater; #[error("rate limit reached")] struct RateLimitReached; +mod config; mod github; mod gitlab; mod updater; diff --git a/crates/docs_rs_repository_stats/src/mod.rs b/crates/docs_rs_repository_stats/src/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/crates/docs_rs_watcher/src/repositories/updater.rs b/crates/docs_rs_repository_stats/src/updater.rs similarity index 98% rename from crates/docs_rs_watcher/src/repositories/updater.rs rename to crates/docs_rs_repository_stats/src/updater.rs index 08e1e5d98..b803ba16a 100644 --- a/crates/docs_rs_watcher/src/repositories/updater.rs +++ b/crates/docs_rs_repository_stats/src/updater.rs @@ -1,6 +1,6 @@ use crate::{ config::Config, - repositories::{GitHub, GitLab, RateLimitReached}, + {GitHub, GitLab, RateLimitReached}, }; use anyhow::Result; use async_trait::async_trait; @@ -70,6 +70,10 @@ impl fmt::Debug for RepositoryStatsUpdater { } impl RepositoryStatsUpdater { + pub fn from_environment(pool: Pool) -> Result { + Ok(Self::new(&Config::from_environment()?, pool)) + } + pub fn new(config: &Config, pool: Pool) -> Self { let mut updaters: Vec> = Vec::new(); if let Ok(Some(updater)) = GitHub::new(config) { diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/docs_rs_watcher/Cargo.toml index 10e5c628c..58ade204e 100644 --- a/crates/docs_rs_watcher/Cargo.toml +++ b/crates/docs_rs_watcher/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] anyhow = { workspace = true } -async-trait = "0.1.83" chrono = { workspace = true } clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } @@ -17,6 +16,7 @@ docs_rs_context = { path = "../docs_rs_context" } docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_logging = { path = "../docs_rs_logging" } +docs_rs_repository_stats = { path = "../docs_rs_repository_stats" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_storage = { path = "../docs_rs_storage" } docs_rs_utils = { path = "../docs_rs_utils" } diff --git a/crates/docs_rs_watcher/src/config.rs b/crates/docs_rs_watcher/src/config.rs index c7abd7137..fdf8603e4 100644 --- a/crates/docs_rs_watcher/src/config.rs +++ b/crates/docs_rs_watcher/src/config.rs @@ -14,15 +14,10 @@ pub struct Config { // Time between 'git gc --auto' calls in seconds pub registry_gc_interval: u64, - // Github authentication - pub github_accesstoken: Option, - pub github_updater_min_rate_limit: u32, - - // GitLab authentication - pub gitlab_accesstoken: Option, - // automatic rebuild configuration pub max_queued_rebuilds: Option, + + pub repository: docs_rs_repository_stats::Config, } impl Config { @@ -40,10 +35,8 @@ impl Config { 60, )?), registry_gc_interval: env("DOCSRS_REGISTRY_GC_INTERVAL", 60 * 60)?, - github_accesstoken: maybe_env("DOCSRS_GITHUB_ACCESSTOKEN")?, - github_updater_min_rate_limit: env("DOCSRS_GITHUB_UPDATER_MIN_RATE_LIMIT", 2500u32)?, - gitlab_accesstoken: maybe_env("DOCSRS_GITLAB_ACCESSTOKEN")?, max_queued_rebuilds: maybe_env("DOCSRS_MAX_QUEUED_REBUILDS")?, + repository: docs_rs_repository_stats::Config::from_environment()?, }) } } diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/docs_rs_watcher/src/lib.rs index d7c3f9364..5ccac0250 100644 --- a/crates/docs_rs_watcher/src/lib.rs +++ b/crates/docs_rs_watcher/src/lib.rs @@ -5,7 +5,6 @@ pub(crate) mod db; mod index; mod priorities; pub mod rebuilds; -pub mod repositories; pub mod service_metrics; mod utils; diff --git a/crates/docs_rs_watcher/src/main.rs b/crates/docs_rs_watcher/src/main.rs index f75bc385b..ae695f35a 100644 --- a/crates/docs_rs_watcher/src/main.rs +++ b/crates/docs_rs_watcher/src/main.rs @@ -3,10 +3,10 @@ use clap::Parser; use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::Pool; use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_repository_stats::RepositoryStatsUpdater; use docs_rs_utils::start_async_cron; use docs_rs_watcher::{ - Config, rebuilds::queue_rebuilds, repositories::RepositoryStatsUpdater, - service_metrics::OtelServiceMetrics, watch_registry, + Config, rebuilds::queue_rebuilds, service_metrics::OtelServiceMetrics, watch_registry, }; use std::{sync::Arc, time::Duration}; use tracing::{info, trace}; @@ -66,7 +66,7 @@ fn start_background_repository_stats_updater(config: &Config, pool: Pool) { // creating a pool or if config fails, which shouldn't happen here because this is run right at // startup. - let updater = Arc::new(RepositoryStatsUpdater::new(&config, pool)); + let updater = Arc::new(RepositoryStatsUpdater::new(&config.repository, pool)); start_async_cron( "repository stats updater", From f5ad000374ca084ba395e135f1d7076373e37ba0 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 11:38:42 +0100 Subject: [PATCH 24/46] nbuild --- crates/docs_rs_build_utils/src/config.rs | 2 +- crates/docs_rs_build_utils/src/lib.rs | 4 +++- .../src/docbuilder/rustwide_builder.rs | 19 +++++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/crates/docs_rs_build_utils/src/config.rs b/crates/docs_rs_build_utils/src/config.rs index 47ecd4446..5d5963bd8 100644 --- a/crates/docs_rs_build_utils/src/config.rs +++ b/crates/docs_rs_build_utils/src/config.rs @@ -1,4 +1,4 @@ -use docs_rs_env_vars::{env, maybe_env}; +use docs_rs_env_vars::maybe_env; #[derive(Debug)] pub struct Config { diff --git a/crates/docs_rs_build_utils/src/lib.rs b/crates/docs_rs_build_utils/src/lib.rs index e94129a29..887e82735 100644 --- a/crates/docs_rs_build_utils/src/lib.rs +++ b/crates/docs_rs_build_utils/src/lib.rs @@ -1,3 +1,5 @@ -pub(crate) mod config; +pub mod config; pub mod limits; pub mod overrides; + +pub use config::Config; diff --git a/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs index dcc820aea..ea933ed95 100644 --- a/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -21,7 +21,7 @@ use docs_rs_database::{ }; use docs_rs_opentelemetry::AnyMeterProvider; use docs_rs_registry_api::RegistryApi; -use docs_rs_repository_stast::RepositoryStatsUpdater; +use docs_rs_repository_stats::RepositoryStatsUpdater; use docs_rs_storage::{ AsyncStorage, RustdocJsonFormatVersion, Storage, compress, compression::CompressionAlgorithm, @@ -214,7 +214,10 @@ impl RustwideBuilder { storage: context.blocking_storage()?, async_storage: context.storage()?, registry_api: RegistryApi::from_environment()?.into(), - repository_stats_updater: RepositoryStatsUpdater::new()?.into(), + repository_stats_updater: RepositoryStatsUpdater::from_environment( + context.pool()?.clone(), + )? + .into(), workspace_initialize_time: Instant::now(), builder_metrics: BuilderMetrics::new(context.meter_provider()).into(), }) @@ -454,9 +457,9 @@ impl RustwideBuilder { #[instrument(skip(self))] fn get_limits(&self, krate: &str) -> Result { + let config = docs_rs_build_utils::Config::from_environment()?; self.runtime.block_on({ let db = self.db.clone(); - let config = self.config.clone(); async move { let mut conn = db.get_async().await?; Limits::for_crate(&config, &mut conn, krate).await @@ -526,13 +529,13 @@ impl RustwideBuilder { let static_files = dest.as_ref().join("static.files"); if static_files.try_exists()? { self.runtime.block_on(add_path_into_database( - &self.storage, + &self.async_storage, RUSTDOC_STATIC_STORAGE_PREFIX, &static_files, ))?; } else { self.runtime.block_on(add_path_into_database( - &self.storage, + &self.async_storage, RUSTDOC_STATIC_STORAGE_PREFIX, &dest, ))?; @@ -698,7 +701,7 @@ impl RustwideBuilder { let files_list = { let (files_list, new_alg) = self.runtime.block_on(add_path_into_remote_archive( - &self.storage, + &self.async_storage, &source_archive_path(name, version), build.host_source_dir(), ))?; @@ -793,7 +796,7 @@ impl RustwideBuilder { } let (file_list, new_alg) = self.runtime.block_on(add_path_into_remote_archive( - &self.storage, + &self.async_storage, &rustdoc_archive_path(name, version), local_storage.path(), ))?; @@ -848,7 +851,7 @@ impl RustwideBuilder { }) { Ok(data) => Some(data), Err(err) => { - report_error(&err); + error!(%name, %version, ?err, "could not fetch releases-data"); None } } From 55108d8ddb45507b588e579648ef492de956968a Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 11:42:44 +0100 Subject: [PATCH 25/46] movem --- Cargo.toml | 5 ++++- crates/{ => bin}/docs_rs_builder/Cargo.toml | 0 crates/{ => bin}/docs_rs_builder/src/config.rs | 0 .../{ => bin}/docs_rs_builder/src/db/add_package.rs | 0 .../{ => bin}/docs_rs_builder/src/db/blacklist.rs | 0 crates/{ => bin}/docs_rs_builder/src/db/mod.rs | 0 .../{ => bin}/docs_rs_builder/src/docbuilder/mod.rs | 0 .../src/docbuilder/rustwide_builder.rs | 0 crates/{ => bin}/docs_rs_builder/src/lib.rs | 0 crates/{ => bin}/docs_rs_builder/src/metrics.rs | 0 crates/{ => bin}/docs_rs_builder/src/utils/copy.rs | 0 crates/{ => bin}/docs_rs_builder/src/utils/mod.rs | 0 .../docs_rs_builder/src/utils/queue_builder.rs | 0 .../{ => bin}/docs_rs_builder/src/utils/version.rs | 0 crates/{ => bin}/docs_rs_watcher/Cargo.toml | 0 crates/{ => bin}/docs_rs_watcher/src/build_queue.rs | 0 crates/{ => bin}/docs_rs_watcher/src/config.rs | 0 .../docs_rs_watcher/src/consistency/data.rs | 0 .../{ => bin}/docs_rs_watcher/src/consistency/db.rs | 0 .../docs_rs_watcher/src/consistency/diff.rs | 0 .../docs_rs_watcher/src/consistency/index.rs | 0 .../docs_rs_watcher/src/consistency/mod.rs | 0 crates/{ => bin}/docs_rs_watcher/src/db/delete.rs | 0 crates/{ => bin}/docs_rs_watcher/src/db/mod.rs | 0 crates/{ => bin}/docs_rs_watcher/src/index.rs | 0 crates/{ => bin}/docs_rs_watcher/src/lib.rs | 0 crates/{ => bin}/docs_rs_watcher/src/main.rs | 0 crates/{ => bin}/docs_rs_watcher/src/priorities.rs | 0 crates/{ => bin}/docs_rs_watcher/src/rebuilds.rs | 0 .../docs_rs_watcher/src/service_metrics.rs | 0 crates/{ => bin}/docs_rs_watcher/src/utils.rs | 0 crates/{ => bin}/docs_rs_web/Cargo.toml | 0 .../Extras/JavaScript (Babel).sublime-syntax | 0 .../assets/syntaxes/Extras/TOML/.gitignore | 0 .../Extras/TOML/Comments.YAML-tmPreferences | 0 .../syntaxes/Extras/TOML/Comments.tmPreferences | 0 .../docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE | 0 .../assets/syntaxes/Extras/TOML/README.md | 0 .../syntaxes/Extras/TOML/Symbol List.tmPreferences | 0 .../assets/syntaxes/Extras/TOML/TOML.sublime-syntax | 0 .../syntaxes/Extras/TOML/syntax_test_toml.toml | 0 .../ISSUE_TEMPLATE/01-bug-syntax-highlighting.md | 0 .../.github/ISSUE_TEMPLATE/02-bug-file-indexing.md | 0 .../.github/ISSUE_TEMPLATE/03-supporting-files.md | 0 .../PULL_REQUEST_TEMPLATE/01-syntax-update-small.md | 0 .../02-syntax-update-significant.md | 0 .../PULL_REQUEST_TEMPLATE/03-supporting-files.md | 0 .../assets/syntaxes/Packages/.travis.yml | 0 .../assets/syntaxes/Packages/ASP/ASP.sublime-syntax | 0 .../syntaxes/Packages/ASP/Comments.tmPreferences | 0 .../syntaxes/Packages/ASP/HTML-ASP.sublime-syntax | 0 .../Packages/ASP/Indentation Rules.tmPreferences | 0 .../Packages/ASP/Indexed Symbol List.tmPreferences | 0 .../syntaxes/Packages/ASP/Symbol List.tmPreferences | 0 .../syntaxes/Packages/ASP/syntax_test_asp.asp | 0 .../ActionScript/ActionScript.sublime-build | 0 .../ActionScript/ActionScript.sublime-syntax | 0 .../Packages/ActionScript/syntax_test_as.as | 0 .../Packages/AppleScript/AppleScript.sublime-syntax | 0 .../Packages/AppleScript/Comments.tmPreferences | 0 .../Packages/Batch File/Batch File.sublime-settings | 0 .../Packages/Batch File/Batch File.sublime-syntax | 0 .../Packages/Batch File/Comments.tmPreferences | 0 .../Packages/Batch File/Symbol List.tmPreferences | 0 .../Packages/Batch File/syntax_test_batch_file.bat | 0 .../syntaxes/Packages/C#/Build.sublime-syntax | 0 .../assets/syntaxes/Packages/C#/C#.sublime-syntax | 0 .../syntaxes/Packages/C#/Comments.tmPreferences | 0 .../syntaxes/Packages/C#/Indentation.tmPreferences | 0 .../Packages/C#/Symbol List Classes.tmPreferences | 0 .../C#/Symbol List Constructors.tmPreferences | 0 .../Packages/C#/Symbol List Enums.tmPreferences | 0 .../C#/Symbol List Index Constructors.tmPreferences | 0 .../C#/Symbol List Inner Function.tmPreferences | 0 .../C#/Symbol List Interfaces.tmPreferences | 0 .../Packages/C#/Symbol List Namespace.tmPreferences | 0 .../Packages/C#/Symbol List Region.tmPreferences | 0 .../Packages/C#/Symbol List Structs.tmPreferences | 0 .../syntaxes/Packages/C#/doc_params.sublime-snippet | 0 .../syntaxes/Packages/C#/doc_see.sublime-snippet | 0 .../Packages/C#/doc_summary.sublime-snippet | 0 .../syntaxes/Packages/C#/tests/syntax_test_C#7.cs | 0 .../Packages/C#/tests/syntax_test_Comments.cs | 0 .../C#/tests/syntax_test_GeneralStructure.cs | 0 .../Packages/C#/tests/syntax_test_Generics.cs | 0 .../Packages/C#/tests/syntax_test_HelloWorld.cs | 0 .../Packages/C#/tests/syntax_test_Operators.cs | 0 .../C#/tests/syntax_test_PreprocessorDirectives.cs | 0 .../Packages/C#/tests/syntax_test_Strings.cs | 0 .../syntaxes/Packages/C#/tests/syntax_test_Using.cs | 0 .../syntaxes/Packages/C#/tests/syntax_test_c#.cs | 0 .../syntaxes/Packages/C#/tests/syntax_test_query.cs | 0 .../Packages/C++/C Single File.sublime-build | 0 .../C++/C Standard Includes.sublime-completions | 0 .../Packages/C++/C++ Single File.sublime-build | 0 .../C++/C++ Standard Includes.sublime-completions | 0 .../syntaxes/Packages/C++/C++.sublime-settings | 0 .../assets/syntaxes/Packages/C++/C++.sublime-syntax | 0 .../assets/syntaxes/Packages/C++/C.sublime-syntax | 0 .../Packages/C++/Comments (C++).tmPreferences | 0 .../Packages/C++/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/C++/Default.sublime-keymap | 0 .../C++/Indentation Rules Comments.tmPreferences | 0 .../Packages/C++/Indentation Rules.tmPreferences | 0 .../Snippets/#ifndef-#define-#endif.sublime-snippet | 0 .../Snippets/#include-(#inc angle).sublime-snippet | 0 .../C++/Snippets/#include-(#inc).sublime-snippet | 0 .../Snippets/#include-(inc angle).sublime-snippet | 0 .../C++/Snippets/#include-(inc).sublime-snippet | 0 .../$1.begin()-$1.end()-(beginend).sublime-snippet | 0 .../030-for-int-loop-(fori).sublime-snippet | 0 .../C++/Snippets/Enumeration.sublime-snippet | 0 .../Packages/C++/Snippets/Typedef.sublime-snippet | 0 .../C++/Snippets/class-..-(class).sublime-snippet | 0 .../Snippets/do...while-loop-(do).sublime-snippet | 0 .../Packages/C++/Snippets/forv.sublime-snippet | 0 .../Packages/C++/Snippets/fprintf.sublime-snippet | 0 .../C++/Snippets/if-..-(if).sublime-snippet | 0 .../C++/Snippets/main()-(int main).sublime-snippet | 0 .../C++/Snippets/main()-(main).sublime-snippet | 0 .../namespace-..-(namespace).sublime-snippet | 0 .../C++/Snippets/printf-..-(printf).sublime-snippet | 0 .../C++/Snippets/read-file-(readF).sublime-snippet | 0 .../C++/Snippets/std-map-(map).sublime-snippet | 0 .../C++/Snippets/std-vector-(v).sublime-snippet | 0 .../Packages/C++/Snippets/struct.sublime-snippet | 0 .../template-typename-..-(template).sublime-snippet | 0 .../C++/Symbol Index Hide Ctors.tmPreferences | 0 .../Symbol Index Include Constants.tmPreferences | 0 .../Packages/C++/Symbol Index.tmPreferences | 0 ...Symbol List - Indent Class Methods.tmPreferences | 0 .../Symbol List - Namespace Spacing.tmPreferences | 0 .../Symbol List - Prefix Banner Items.tmPreferences | 0 .../Symbol List Hide Forward Decls.tmPreferences | 0 .../syntaxes/Packages/C++/Symbol List.tmPreferences | 0 .../syntaxes/Packages/C++/syntax_test_accessor.c | 0 .../syntaxes/Packages/C++/syntax_test_accessor.cpp | 0 .../assets/syntaxes/Packages/C++/syntax_test_c.c | 0 .../syntaxes/Packages/C++/syntax_test_cpp.cpp | 0 .../assets/syntaxes/Packages/CSS/CSS.sublime-syntax | 0 .../syntaxes/Packages/CSS/Comments.tmPreferences | 0 .../Packages/CSS/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/CSS/Default.sublime-keymap | 0 .../Packages/CSS/Symbol Index.tmPreferences | 0 .../Packages/CSS/Symbol List Group.tmPreferences | 0 .../syntaxes/Packages/CSS/Symbol List.tmPreferences | 0 .../assets/syntaxes/Packages/CSS/css_completions.py | 0 .../Packages/Clojure/Clojure.sublime-settings | 0 .../Packages/Clojure/Clojure.sublime-syntax | 0 .../Packages/Clojure/ClojureSymbols.tmPreferences | 0 .../syntaxes/Packages/Clojure/Comment.tmPreferences | 0 .../Packages/Clojure/Default.sublime-keymap | 0 .../Packages/Clojure/tests/syntax_test_clojure.clj | 0 .../Clojure/tests/syntax_test_clojure_old.clj | 0 .../syntaxes/Packages/D/Comments.tmPreferences | 0 .../Packages/D/Completion Rules.tmPreferences | 0 .../assets/syntaxes/Packages/D/D dub.sublime-build | 0 .../assets/syntaxes/Packages/D/D.sublime-build | 0 .../assets/syntaxes/Packages/D/D.sublime-syntax | 0 .../syntaxes/Packages/D/DMD Output.sublime-syntax | 0 .../Packages/D/Indentation Rules.tmPreferences | 0 .../Packages/D/Snippets/class.sublime-snippet | 0 .../Packages/D/Snippets/constant.sublime-snippet | 0 .../Packages/D/Snippets/critical.sublime-snippet | 0 .../Packages/D/Snippets/debugm.sublime-snippet | 0 .../Packages/D/Snippets/enum.sublime-snippet | 0 .../D/Snippets/error-format.sublime-snippet | 0 .../Packages/D/Snippets/error.sublime-snippet | 0 .../Packages/D/Snippets/fatal.sublime-snippet | 0 .../D/Snippets/foreach-reverse.sublime-snippet | 0 .../Packages/D/Snippets/foreach.sublime-snippet | 0 .../Packages/D/Snippets/if-else.sublime-snippet | 0 .../syntaxes/Packages/D/Snippets/if.sublime-snippet | 0 .../Packages/D/Snippets/import.sublime-snippet | 0 .../Packages/D/Snippets/info.sublime-snippet | 0 .../Packages/D/Snippets/log-format.sublime-snippet | 0 .../Packages/D/Snippets/log.sublime-snippet | 0 .../D/Snippets/main-with-args.sublime-snippet | 0 .../Packages/D/Snippets/main.sublime-snippet | 0 .../Packages/D/Snippets/method.sublime-snippet | 0 .../Packages/D/Snippets/return.sublime-snippet | 0 .../Packages/D/Snippets/struct.sublime-snippet | 0 .../Packages/D/Snippets/trace.sublime-snippet | 0 .../D/Snippets/try-catch-finally.sublime-snippet | 0 .../Packages/D/Snippets/try-catch.sublime-snippet | 0 .../Packages/D/Snippets/try-finally.sublime-snippet | 0 .../Packages/D/Snippets/unittest.sublime-snippet | 0 .../Packages/D/Snippets/version.sublime-snippet | 0 .../Packages/D/Snippets/warning.sublime-snippet | 0 .../Packages/D/Snippets/while.sublime-snippet | 0 ...ymbol Index Hide Special Functions.tmPreferences | 0 .../syntaxes/Packages/D/Symbol List.tmPreferences | 0 .../syntaxes/Packages/D/tests/syntax_test_d.d | 0 .../syntaxes/Packages/D/tests/syntax_test_old.d | 0 .../syntaxes/Packages/D/tests/syntax_test_shebang.d | 0 .../syntaxes/Packages/Diff/Context.sublime-menu | 0 .../syntaxes/Packages/Diff/Diff.sublime-syntax | 0 .../syntaxes/Packages/Diff/Side Bar.sublime-menu | 0 .../assets/syntaxes/Packages/Diff/diff.py | 0 .../syntaxes/Packages/Diff/syntax_test_diff.diff | 0 .../syntaxes/Packages/Erlang/Comments.tmPreferences | 0 .../Packages/Erlang/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/Erlang/Erlang.sublime-build | 0 .../Packages/Erlang/Erlang.sublime-settings | 0 .../syntaxes/Packages/Erlang/Erlang.sublime-syntax | 0 .../Erlang/HTML (Erlang).sublime-completions | 0 .../Packages/Erlang/HTML (Erlang).sublime-syntax | 0 .../Packages/Erlang/Indentation Rules.tmPreferences | 0 .../Erlang/Indexed Reference List.tmPreferences | 0 .../Erlang/Indexed Symbol List.tmPreferences | 0 .../Snippets/Behaviour-Directive.sublime-snippet | 0 .../Erlang/Snippets/Case-Expression.sublime-snippet | 0 .../Snippets/Define-Directive.sublime-snippet | 0 .../Snippets/Export-Directive.sublime-snippet | 0 .../Erlang/Snippets/Fun-Expression.sublime-snippet | 0 .../Erlang/Snippets/If-Expression.sublime-snippet | 0 .../Erlang/Snippets/Ifdef-Directive.sublime-snippet | 0 .../Snippets/Ifndef-Directive.sublime-snippet | 0 .../Snippets/Import-Directive.sublime-snippet | 0 .../Snippets/Include-Directive.sublime-snippet | 0 .../Snippets/Module-Directive.sublime-snippet | 0 .../Snippets/Receive-Expression.sublime-snippet | 0 .../Snippets/Record-Directive.sublime-snippet | 0 .../Erlang/Snippets/Try-Expression.sublime-snippet | 0 .../Erlang/Snippets/Undef-Directive.sublime-snippet | 0 .../Erlang/Symbol List - Exports.tmPreferences | 0 .../Symbol List - Function Definition.tmPreferences | 0 ...mbol List - Function Specification.tmPreferences | 0 .../Erlang/Symbol List - Imports.tmPreferences | 0 .../Erlang/Symbol List - Macro.tmPreferences | 0 .../Erlang/Symbol List - Record.tmPreferences | 0 .../Erlang/Symbol List - Type.tmPreferences | 0 .../syntaxes/Packages/Erlang/syntax_test_erlang.erl | 0 .../Packages/Erlang/syntax_test_erlang.yaws | 0 .../Packages/Git Formats/Comments.tmPreferences | 0 .../Git Attributes - Attributes.sublime-completions | 0 .../Git Attributes - Diff.sublime-completions | 0 .../Git Attributes - EOL.sublime-completions | 0 .../Git Attributes - Encoding.sublime-completions | 0 .../Git Attributes - Filter.sublime-completions | 0 .../Git Attributes - Merge.sublime-completions | 0 .../Git Attributes - Text.sublime-completions | 0 .../Git Attributes - Whitespace.sublime-completions | 0 .../Git Formats/Git Attributes.sublime-settings | 0 .../Git Formats/Git Attributes.sublime-syntax | 0 .../Packages/Git Formats/Git Commit.sublime-syntax | 0 .../Packages/Git Formats/Git Common.sublime-syntax | 0 .../Git Config - Indentation Rules.tmPreferences | 0 .../Git Config - Symbol List.tmPreferences | 0 .../Packages/Git Formats/Git Config.sublime-syntax | 0 .../Packages/Git Formats/Git Ignore.sublime-syntax | 0 .../Packages/Git Formats/Git Link.sublime-syntax | 0 .../Packages/Git Formats/Git Log.sublime-settings | 0 .../Packages/Git Formats/Git Log.sublime-syntax | 0 .../Git Mailmap - Symbol List.tmPreferences | 0 .../Packages/Git Formats/Git Mailmap.sublime-syntax | 0 .../Packages/Git Formats/Git Rebase.sublime-syntax | 0 .../Snippets/Git Config - Section.sublime-snippet | 0 .../Packages/Git Formats/syntax_test_git_attributes | 0 .../Packages/Git Formats/syntax_test_git_commit | 0 .../Packages/Git Formats/syntax_test_git_config | 0 .../Packages/Git Formats/syntax_test_git_ignore | 0 .../Packages/Git Formats/syntax_test_git_link | 0 .../Packages/Git Formats/syntax_test_git_log | 0 .../Packages/Git Formats/syntax_test_git_mailmap | 0 .../Packages/Git Formats/syntax_test_git_merge | 0 .../Packages/Git Formats/syntax_test_git_rebase | 0 .../Packages/Git Formats/syntax_test_git_tag | 0 .../syntaxes/Packages/Go/Default.sublime-keymap | 0 .../syntaxes/Packages/Go/Go.sublime-completions | 0 .../assets/syntaxes/Packages/Go/Go.sublime-settings | 0 .../assets/syntaxes/Packages/Go/Go.sublime-syntax | 0 .../Packages/Go/GoCommentRules.tmPreferences | 0 .../Go/Indents/GoCommentIndent.tmPreferences | 0 .../Packages/Go/Indents/GoIndent.tmPreferences | 0 .../Go/Indents/GoStringIndent.tmPreferences | 0 .../Packages/Go/Snippets/go-defun.sublime-snippet | 0 .../Packages/Go/Snippets/go-fori.sublime-snippet | 0 .../Packages/Go/Snippets/go-gofun.sublime-snippet | 0 .../Packages/Go/Snippets/go-iferr.sublime-snippet | 0 .../Go/Symbols/GoConstSymbols.tmPreferences | 0 .../Packages/Go/Symbols/GoFuncSymbols.tmPreferences | 0 .../Packages/Go/Symbols/GoTypeSymbols.tmPreferences | 0 .../Packages/Go/Symbols/GoVarSymbols.tmPreferences | 0 .../assets/syntaxes/Packages/Go/syntax_test_go.go | 0 .../Packages/Graphviz/Comments.tmPreferences | 0 .../Attribute Values.sublime-completions | 0 .../Completions/Attributes.sublime-completions | 0 .../Graphviz/Completions/Graphs.sublime-completions | 0 .../Completions/Objects.sublime-completions | 0 .../syntaxes/Packages/Graphviz/DOT.sublime-syntax | 0 .../Packages/Graphviz/Graphviz.sublime-build | 0 .../Graphviz/Indentation Rules.tmPreferences | 0 .../Packages/Graphviz/Symbol List.tmPreferences | 0 .../syntaxes/Packages/Graphviz/syntax_test_dot.dot | 0 .../syntaxes/Packages/Groovy/Comments.tmPreferences | 0 .../syntaxes/Packages/Groovy/Groovy.sublime-syntax | 0 .../#!-usr-local-bin-groovy-w.sublime-snippet | 0 .../Groovy/Snippets/Ant-__-replace.sublime-snippet | 0 .../Groovy/Snippets/Block-Comment.sublime-snippet | 0 .../Groovy/Snippets/Constructor.sublime-snippet | 0 .../Groovy/Snippets/Hash-Pair.sublime-snippet | 0 .../Snippets/Thread_start-{-__-}.sublime-snippet | 0 .../Thread_startDaemon-{-__-}.sublime-snippet | 0 .../Groovy/Snippets/all{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/any{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/as-BigDecimal.sublime-snippet | 0 .../Groovy/Snippets/as-BigInteger.sublime-snippet | 0 .../Groovy/Snippets/as-Double.sublime-snippet | 0 .../Groovy/Snippets/as-Float.sublime-snippet | 0 .../Groovy/Snippets/as-Immutable.sublime-snippet | 0 .../Packages/Groovy/Snippets/as-Set.sublime-snippet | 0 .../Groovy/Snippets/as-String.sublime-snippet | 0 .../Groovy/Snippets/as-Synchronized.sublime-snippet | 0 .../Groovy/Snippets/as-Writable.sublime-snippet | 0 .../Groovy/Snippets/assert(__).sublime-snippet | 0 .../Snippets/assertEquals(__).sublime-snippet | 0 .../Groovy/Snippets/assertFalse.sublime-snippet | 0 .../Snippets/assertNotEquals(__).sublime-snippet | 0 .../Snippets/assertNotNull(__).sublime-snippet | 0 .../Groovy/Snippets/assertNull(__).sublime-snippet | 0 .../Groovy/Snippets/assertSame.sublime-snippet | 0 .../Groovy/Snippets/assertTrue.sublime-snippet | 0 .../Packages/Groovy/Snippets/case.sublime-snippet | 0 .../Snippets/class-__-singleton.sublime-snippet | 0 .../Groovy/Snippets/class-__.sublime-snippet | 0 .../Snippets/class-___-TestCase.sublime-snippet | 0 .../Snippets/collect-{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/copy__-file.sublime-snippet | 0 .../copy__-fileset-include-exclude.sublime-snippet | 0 .../Groovy/Snippets/copy__-fileset.sublime-snippet | 0 .../Snippets/def-__-closure-=-{__}.sublime-snippet | 0 .../Snippets/def-__-method()-{__}.sublime-snippet | 0 .../Snippets/downto(num)-{-n-__-}.sublime-snippet | 0 .../Groovy/Snippets/each-{-e-__-}.sublime-snippet | 0 .../Snippets/eachByte-{-byte-__-}.sublime-snippet | 0 .../Snippets/eachDir-{-dir-__-}.sublime-snippet | 0 .../Groovy/Snippets/eachDirMatch.sublime-snippet | 0 .../Groovy/Snippets/eachDirRecurse.sublime-snippet | 0 .../Snippets/eachFile-{-file-__-}.sublime-snippet | 0 .../eachFileMatch-{-file-__-}.sublime-snippet | 0 .../eachFileRecurse-{-file-__-}.sublime-snippet | 0 .../Snippets/eachKey-{-key-__-}.sublime-snippet | 0 .../Snippets/eachLine-{-line-__-}.sublime-snippet | 0 .../eachMatch(regex)-{-match-__-}.sublime-snippet | 0 .../Snippets/eachObject-{-obj-__-}.sublime-snippet | 0 .../Snippets/eachValue-{-val-__-}.sublime-snippet | 0 .../eachWithIndex-{-e-i-__-}.sublime-snippet | 0 .../Packages/Groovy/Snippets/else.sublime-snippet | 0 .../Groovy/Snippets/elseif-___.sublime-snippet | 0 .../Groovy/Snippets/every-{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/final-method.sublime-snippet | 0 .../Groovy/Snippets/final-var.sublime-snippet | 0 .../Groovy/Snippets/find-{-e-__-}.sublime-snippet | 0 .../Snippets/findAll-{-e-__-}.sublime-snippet | 0 .../Packages/Groovy/Snippets/for-in.sublime-snippet | 0 .../grep(-pattern-)-{-match-__-}.sublime-snippet | 0 .../Groovy/Snippets/if-else.sublime-snippet | 0 .../Packages/Groovy/Snippets/if.sublime-snippet | 0 .../Packages/Groovy/Snippets/import.sublime-snippet | 0 .../Packages/Groovy/Snippets/mkdir.sublime-snippet | 0 .../new-File(__)_eachLine-{-__-}.sublime-snippet | 0 .../Groovy/Snippets/package.sublime-snippet | 0 .../Packages/Groovy/Snippets/print.sublime-snippet | 0 .../Groovy/Snippets/println.sublime-snippet | 0 .../Snippets/private-final-method.sublime-snippet | 0 .../Snippets/private-final-var.sublime-snippet | 0 .../Groovy/Snippets/private-method.sublime-snippet | 0 .../private-static-final-String.sublime-snippet | 0 .../private-static-final-method.sublime-snippet | 0 .../Snippets/private-static-method.sublime-snippet | 0 .../Snippets/private-static-var.sublime-snippet | 0 .../Groovy/Snippets/private-var.sublime-snippet | 0 .../replaceAll(regex)-{-match-__}.sublime-snippet | 0 .../Snippets/reverseEach-{-e-__-}.sublime-snippet | 0 .../Groovy/Snippets/run-after.sublime-snippet | 0 .../Groovy/Snippets/setUp().sublime-snippet | 0 .../Snippets/shouldFail(__)-{-__-}.sublime-snippet | 0 .../sleep(secs)-{-__-on-interrupt-}.sublime-snippet | 0 .../Groovy/Snippets/sleep(secs).sublime-snippet | 0 .../Groovy/Snippets/sort-{-__-}.sublime-snippet | 0 ...Line(separator)-{-line-__-}-copy.sublime-snippet | 0 .../Snippets/static-final-method.sublime-snippet | 0 .../Snippets/static-final-var.sublime-snippet | 0 .../Snippets/static-main-method.sublime-snippet | 0 .../Groovy/Snippets/static-method.sublime-snippet | 0 .../Groovy/Snippets/static-var.sublime-snippet | 0 .../step(to-amount)-{-n-__-}.sublime-snippet | 0 .../Groovy/Snippets/switch__case.sublime-snippet | 0 .../Snippets/switch__case__default.sublime-snippet | 0 .../Groovy/Snippets/tearDown().sublime-snippet | 0 .../Groovy/Snippets/test-case.sublime-snippet | 0 .../Groovy/Snippets/times-{-n-__-}.sublime-snippet | 0 .../Groovy/Snippets/to-Array.sublime-snippet | 0 .../Groovy/Snippets/to-BigDecimal.sublime-snippet | 0 .../Groovy/Snippets/to-BigInteger.sublime-snippet | 0 .../Groovy/Snippets/to-Boolean.sublime-snippet | 0 .../Groovy/Snippets/to-Character.sublime-snippet | 0 .../Groovy/Snippets/to-Double.sublime-snippet | 0 .../Groovy/Snippets/to-Float.sublime-snippet | 0 .../Groovy/Snippets/to-Integer.sublime-snippet | 0 .../Groovy/Snippets/to-List.sublime-snippet | 0 .../Groovy/Snippets/to-String.sublime-snippet | 0 .../Packages/Groovy/Snippets/to-URI.sublime-snippet | 0 .../Packages/Groovy/Snippets/to-URL.sublime-snippet | 0 .../Snippets/try-__-catch__-finally.sublime-snippet | 0 .../Groovy/Snippets/try-__-catch__.sublime-snippet | 0 .../Snippets/upto(num)-{-n-__-}.sublime-snippet | 0 .../Packages/Groovy/Snippets/var.sublime-snippet | 0 .../Groovy/Snippets/while-___-{___}.sublime-snippet | 0 .../withInputStream-{-in-__-}.sublime-snippet | 0 .../withOutputStream-{-out-__-}.sublime-snippet | 0 .../withPrintWriter-{-pw-__}.sublime-snippet | 0 .../Snippets/withReader-{-r-__-}.sublime-snippet | 0 .../Snippets/withStream-{-in-__-}.sublime-snippet | 0 .../withStreams-{-Socket-s-__}.sublime-snippet | 0 .../withWriter(charset)-{-w-__-}.sublime-snippet | 0 .../Snippets/withWriter-{-w-__}.sublime-snippet | 0 ...withWriterAppend(charset)-{-__-}.sublime-snippet | 0 .../Symbol List%3A Class Variables.tmPreferences | 0 .../Groovy/Symbol List%3A Classes.tmPreferences | 0 .../Groovy/Symbol List%3A Methods.tmPreferences | 0 .../Groovy/Symbol List%3A Variables.tmPreferences | 0 .../Packages/Groovy/syntax_test_groovy.groovy | 0 .../Groovy/tests/syntax_test_Strings.groovy | 0 .../syntaxes/Packages/HTML/Comments.tmPreferences | 0 .../syntaxes/Packages/HTML/HTML.sublime-syntax | 0 .../Packages/HTML/Indentation Rules.tmPreferences | 0 .../HTML/Snippets/html (begin tag).sublime-snippet | 0 .../Packages/HTML/Snippets/html.sublime-snippet | 0 .../Packages/HTML/Symbol List - ID.tmPreferences | 0 .../syntaxes/Packages/HTML/encode_html_entities.py | 0 .../syntaxes/Packages/HTML/html_completions.py | 0 .../syntaxes/Packages/HTML/syntax_test_html.html | 0 .../Packages/Haskell/Comments.tmPreferences | 0 .../syntaxes/Packages/Haskell/Haskell.sublime-build | 0 .../Packages/Haskell/Haskell.sublime-syntax | 0 .../Packages/Haskell/Indent Patterns.tmPreferences | 0 .../Haskell/Literate Haskell.sublime-syntax | 0 .../Packages/Haskell/Snippets/Case.sublime-snippet | 0 .../Haskell/Snippets/Instance.sublime-snippet | 0 .../Haskell/Snippets/Lambda.sublime-snippet | 0 .../Packages/Haskell/Snippets/Main.sublime-snippet | 0 .../Haskell/Snippets/module.sublime-snippet | 0 .../Packages/Haskell/Symbol List.tmPreferences | 0 .../Packages/Haskell/syntax_test_haskell.hs | 0 .../Packages/JSON/JSON Indent.tmPreferences | 0 .../syntaxes/Packages/JSON/JSON.sublime-syntax | 0 .../syntaxes/Packages/JSON/syntax_test_json.json | 0 .../assets/syntaxes/Packages/Java/Ant.sublime-build | 0 .../Java/Comments - Properties.tmPreferences | 0 .../syntaxes/Packages/Java/Comments.tmPreferences | 0 .../Packages/Java/Completion Rules.tmPreferences | 0 .../Java/Indentation Rules Annex.tmPreferences | 0 .../Packages/Java/Indentation Rules.tmPreferences | 0 .../Packages/Java/Indexed Symbol List.tmPreferences | 0 .../Java/Java Server Pages (JSP).sublime-syntax | 0 .../syntaxes/Packages/Java/Java.sublime-completions | 0 .../syntaxes/Packages/Java/Java.sublime-syntax | 0 .../syntaxes/Packages/Java/JavaC.sublime-build | 0 .../syntaxes/Packages/Java/JavaDoc.sublime-syntax | 0 .../Packages/Java/JavaProperties.sublime-syntax | 0 .../Packages/Java/Snippets/abstract.sublime-snippet | 0 .../Packages/Java/Snippets/assert.sublime-snippet | 0 .../Packages/Java/Snippets/break.sublime-snippet | 0 .../Packages/Java/Snippets/case.sublime-snippet | 0 .../Packages/Java/Snippets/catch.sublime-snippet | 0 .../Packages/Java/Snippets/class.sublime-snippet | 0 .../Java/Snippets/constant-string.sublime-snippet | 0 .../Packages/Java/Snippets/constant.sublime-snippet | 0 .../Packages/Java/Snippets/default.sublime-snippet | 0 .../Packages/Java/Snippets/else-if.sublime-snippet | 0 .../Packages/Java/Snippets/else.sublime-snippet | 0 .../Packages/Java/Snippets/final.sublime-snippet | 0 .../Java/Snippets/for-(each).sublime-snippet | 0 .../Packages/Java/Snippets/for.sublime-snippet | 0 .../Packages/Java/Snippets/if.sublime-snippet | 0 ...import-junit_framework_TestCase;.sublime-snippet | 0 .../Packages/Java/Snippets/import.sublime-snippet | 0 .../Java/Snippets/interface.sublime-snippet | 0 .../Java/Snippets/java_beans_.sublime-snippet | 0 .../Packages/Java/Snippets/java_io.sublime-snippet | 0 .../Java/Snippets/java_math.sublime-snippet | 0 .../Java/Snippets/java_net_.sublime-snippet | 0 .../Java/Snippets/java_util_.sublime-snippet | 0 .../Java/Snippets/method-(main).sublime-snippet | 0 .../Packages/Java/Snippets/method.sublime-snippet | 0 .../Packages/Java/Snippets/package.sublime-snippet | 0 .../Packages/Java/Snippets/print.sublime-snippet | 0 .../Packages/Java/Snippets/println.sublime-snippet | 0 .../Packages/Java/Snippets/private.sublime-snippet | 0 .../Java/Snippets/protected.sublime-snippet | 0 .../Packages/Java/Snippets/public.sublime-snippet | 0 .../Packages/Java/Snippets/return.sublime-snippet | 0 .../Packages/Java/Snippets/static.sublime-snippet | 0 .../Packages/Java/Snippets/switch.sublime-snippet | 0 .../Java/Snippets/synchronized.sublime-snippet | 0 .../Java/Snippets/test-case.sublime-snippet | 0 .../Packages/Java/Snippets/test.sublime-snippet | 0 .../Packages/Java/Snippets/throw.sublime-snippet | 0 .../Packages/Java/Snippets/variable.sublime-snippet | 0 .../Packages/Java/Snippets/while.sublime-snippet | 0 .../Java/Symbol List - Classes.tmPreferences | 0 .../Java/Symbol List - Constants.tmPreferences | 0 .../Symbol List - Inner Class Methods.tmPreferences | 0 .../Java/Symbol List - Inner Classes.tmPreferences | 0 ...l List - Inner Inner Class Methods.tmPreferences | 0 .../Symbol List - Inner Inner Classes.tmPreferences | 0 .../Java/Symbol List - Method.tmPreferences | 0 .../Java/Symbol List - Modules.tmPreferences | 0 .../Java/Symbol List - Properties.tmPreferences | 0 .../syntaxes/Packages/Java/syntax_test_java.java | 0 .../Java/syntax_test_java_properties.properties | 0 .../syntaxes/Packages/Java/syntax_test_jsp.jsp | 0 .../Packages/JavaScript/Comments.tmPreferences | 0 .../JavaScript/Completion Rules.tmPreferences | 0 .../Packages/JavaScript/Default.sublime-keymap | 0 .../JavaScript/Indexed Symbols.tmPreferences | 0 .../JavaScript/JavaScript Indent.tmPreferences | 0 .../Packages/JavaScript/JavaScript.sublime-syntax | 0 .../Regular Expressions (JavaScript).sublime-syntax | 0 .../Snippets/Get-Elements.sublime-snippet | 0 .../Snippets/Object-Method.sublime-snippet | 0 .../Snippets/Object-Value-JS.sublime-snippet | 0 .../Snippets/Object-key-key-value.sublime-snippet | 0 .../Snippets/Prototype-(proto).sublime-snippet | 0 .../Snippets/for-()-{}-(faster).sublime-snippet | 0 .../JavaScript/Snippets/for-()-{}.sublime-snippet | 0 .../Snippets/function-(fun).sublime-snippet | 0 .../JavaScript/Snippets/function.sublime-snippet | 0 .../JavaScript/Snippets/if-___-else.sublime-snippet | 0 .../Packages/JavaScript/Snippets/if.sublime-snippet | 0 .../Snippets/setTimeout-function.sublime-snippet | 0 .../JavaScript/Symbol List Banned.tmPreferences | 0 .../JavaScript/Symbol List Function.tmPreferences | 0 .../Packages/JavaScript/tests/syntax_test_js.js | 0 .../JavaScript/tests/syntax_test_js_bindings.js | 0 .../JavaScript/tests/syntax_test_js_regexp.js | 0 .../tests/syntax_test_js_support_builtin.js | 0 .../tests/syntax_test_js_support_console.js | 0 .../JavaScript/tests/syntax_test_js_support_dom.js | 0 .../JavaScript/tests/syntax_test_js_support_node.js | 0 .../docs_rs_web/assets/syntaxes/Packages/LICENSE | 0 .../syntaxes/Packages/LaTeX/Bibtex.sublime-syntax | 0 .../syntaxes/Packages/LaTeX/Comments.tmPreferences | 0 .../Packages/LaTeX/Indentation Rules.tmPreferences | 0 .../Packages/LaTeX/LaTeX Log.sublime-syntax | 0 .../syntaxes/Packages/LaTeX/LaTeX.sublime-settings | 0 .../syntaxes/Packages/LaTeX/LaTeX.sublime-syntax | 0 .../Packages/LaTeX/Snippets/Cases.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Chapter.sublime-snippet | 0 .../LaTeX/Snippets/Description.sublime-snippet | 0 .../LaTeX/Snippets/Displaymath-($$).sublime-snippet | 0 .../LaTeX/Snippets/Enumerate.sublime-snippet | 0 .../LaTeX/Snippets/Equation.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Figure.sublime-snippet | 0 .../Snippets/Item[description].sublime-snippet | 0 .../Packages/LaTeX/Snippets/Itemize.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Listing.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Matrix.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Page.sublime-snippet | 0 .../LaTeX/Snippets/Paragraph.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Part.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Section.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Split.sublime-snippet | 0 .../LaTeX/Snippets/Sub-Paragraph.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Table.sublime-snippet | 0 .../Packages/LaTeX/Snippets/Tabular.sublime-snippet | 0 .../LaTeX/Snippets/begin{}-end{}.sublime-snippet | 0 .../Snippets/section-..-(section).sublime-snippet | 0 .../Snippets/subsection-..-(sub).sublime-snippet | 0 .../subsubsection-..-(ssub).sublime-snippet | 0 .../LaTeX/Symbol List - Commands.tmPreferences | 0 .../LaTeX/Symbol List - Labels.tmPreferences | 0 .../LaTeX/Symbol List - Sections.tmPreferences | 0 .../syntaxes/Packages/LaTeX/TeX.sublime-syntax | 0 .../syntaxes/Packages/LaTeX/syntax_test_latex.tex | 0 .../syntaxes/Packages/Lisp/Comments.tmPreferences | 0 .../syntaxes/Packages/Lisp/Lisp.sublime-syntax | 0 .../Packages/Lisp/Snippets/'(.sublime-snippet | 0 .../Lisp/Snippets/defconstant.sublime-snippet | 0 .../Packages/Lisp/Snippets/defmacro.sublime-snippet | 0 .../Lisp/Snippets/defparameter.sublime-snippet | 0 .../Packages/Lisp/Snippets/defun.sublime-snippet | 0 .../Packages/Lisp/Snippets/defvar.sublime-snippet | 0 .../Packages/Lisp/Snippets/if.sublime-snippet | 0 .../Packages/Lisp/Snippets/let.sublime-snippet | 0 .../Packages/Lisp/Snippets/let1.sublime-snippet | 0 .../Packages/Lisp/Snippets/setf.sublime-snippet | 0 .../syntaxes/Packages/Lisp/syntax_test_lisp.lisp | 0 .../syntaxes/Packages/Lua/Comments.tmPreferences | 0 .../Packages/Lua/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/Lua/Indent.tmPreferences | 0 .../assets/syntaxes/Packages/Lua/Lua.sublime-build | 0 .../assets/syntaxes/Packages/Lua/Lua.sublime-syntax | 0 .../Snippets/for-i-v-in-ipairs().sublime-snippet | 0 .../Lua/Snippets/for-i=1-10.sublime-snippet | 0 .../Lua/Snippets/for-k-v-in-pairs().sublime-snippet | 0 .../Lua/Snippets/function-(fun).sublime-snippet | 0 .../Snippets/function-(function).sublime-snippet | 0 .../Lua/Snippets/local-x-=-1.sublime-snippet | 0 .../Lua/Snippets/table.concat.sublime-snippet | 0 .../Lua/Snippets/table.sort.sublime-snippet | 0 .../syntaxes/Packages/Lua/Symbol List.tmPreferences | 0 .../syntaxes/Packages/Lua/tests/syntax_test_lua.lua | 0 .../Packages/Lua/tests/syntax_test_lua_support.lua | 0 .../Packages/Makefile/Comments.tmPreferences | 0 .../Packages/Makefile/Make Output.sublime-syntax | 0 .../syntaxes/Packages/Makefile/Make.sublime-build | 0 .../Packages/Makefile/Makefile.sublime-settings | 0 .../Packages/Makefile/Makefile.sublime-syntax | 0 .../Packages/Makefile/Miscellaneous.tmPreferences | 0 .../Packages/Makefile/syntax_test_makefile.mak | 0 .../Packages/Markdown/Indent%3A Raw.tmPreferences | 0 .../Packages/Markdown/Markdown.sublime-syntax | 0 .../Packages/Markdown/MultiMarkdown.sublime-syntax | 0 .../Markdown/Symbol List - Heading.tmPreferences | 0 .../Symbol List - Reference Link.tmPreferences | 0 .../Packages/Markdown/syntax_test_markdown.md | 0 .../Packages/Markdown/syntax_test_multimarkdown.md | 0 .../syntaxes/Packages/Matlab/Indent.tmPreferences | 0 .../syntaxes/Packages/Matlab/Matlab.sublime-syntax | 0 .../Packages/Matlab/Miscellaneous.tmPreferences | 0 .../Matlab/Snippets/Octave-function.sublime-snippet | 0 .../Packages/Matlab/Snippets/^.sublime-snippet | 0 .../Packages/Matlab/Snippets/case.sublime-snippet | 0 .../Packages/Matlab/Snippets/clear.sublime-snippet | 0 .../Matlab/Snippets/disp-sprintf.sublime-snippet | 0 .../Packages/Matlab/Snippets/disp.sublime-snippet | 0 .../Matlab/Snippets/dlmwrite.sublime-snippet | 0 .../Packages/Matlab/Snippets/else.sublime-snippet | 0 .../Packages/Matlab/Snippets/elseif.sublime-snippet | 0 .../Packages/Matlab/Snippets/error.sublime-snippet | 0 .../Packages/Matlab/Snippets/exp.sublime-snippet | 0 .../Matlab/Snippets/fprintf.sublime-snippet | 0 .../Packages/Matlab/Snippets/get.sublime-snippet | 0 .../Matlab/Snippets/griddata.sublime-snippet | 0 .../Matlab/Snippets/if-elseif.sublime-snippet | 0 .../Packages/Matlab/Snippets/line.sublime-snippet | 0 .../Packages/Matlab/Snippets/set.sublime-snippet | 0 .../Matlab/Snippets/small-function.sublime-snippet | 0 .../Matlab/Snippets/sprintf.sublime-snippet | 0 .../switch___case___otherwise___end.sublime-snippet | 0 .../Packages/Matlab/Snippets/title.sublime-snippet | 0 .../Packages/Matlab/Snippets/unix.sublime-snippet | 0 .../unwind_protect-cleanup-end.sublime-snippet | 0 .../Matlab/Snippets/warning.sublime-snippet | 0 .../Packages/Matlab/Snippets/while.sublime-snippet | 0 .../Packages/Matlab/Snippets/xlabel.sublime-snippet | 0 .../Packages/Matlab/Snippets/xtick.sublime-snippet | 0 .../Packages/Matlab/Snippets/ylabel.sublime-snippet | 0 .../Packages/Matlab/Snippets/ytick.sublime-snippet | 0 .../Packages/Matlab/Snippets/zlabel.sublime-snippet | 0 .../syntaxes/Packages/Matlab/Symbols.tmPreferences | 0 .../syntaxes/Packages/Matlab/syntax_test_matlab.m | 0 .../Packages/OCaml/Indent rules.tmPreferences | 0 .../Packages/OCaml/Miscellaneous.tmPreferences | 0 .../syntaxes/Packages/OCaml/OCaml.sublime-syntax | 0 .../syntaxes/Packages/OCaml/OCamllex.sublime-syntax | 0 .../Packages/OCaml/OCamlyacc.sublime-syntax | 0 .../OCaml/Snippets/Document.sublime-snippet | 0 .../OCaml/Snippets/For-Loop.sublime-snippet | 0 .../OCaml/Snippets/While-Loop.sublime-snippet | 0 .../Packages/OCaml/Snippets/begin.sublime-snippet | 0 .../Packages/OCaml/Snippets/class.sublime-snippet | 0 .../Packages/OCaml/Snippets/fun.sublime-snippet | 0 .../Packages/OCaml/Snippets/func.sublime-snippet | 0 .../OCaml/Snippets/function-label.sublime-snippet | 0 .../Packages/OCaml/Snippets/let-in.sublime-snippet | 0 .../Packages/OCaml/Snippets/let.sublime-snippet | 0 .../OCaml/Snippets/match-pattern.sublime-snippet | 0 .../Packages/OCaml/Snippets/match.sublime-snippet | 0 .../OCaml/Snippets/method-(method).sublime-snippet | 0 .../OCaml/Snippets/module-signature.sublime-snippet | 0 .../OCaml/Snippets/module-type.sublime-snippet | 0 .../Packages/OCaml/Snippets/module.sublime-snippet | 0 .../Packages/OCaml/Snippets/try.sublime-snippet | 0 .../OCaml/Snippets/type-(type).sublime-snippet | 0 .../OCaml/Snippets/untitled.sublime-snippet | 0 .../OCaml/Symbol List%3A Classes.tmPreferences | 0 .../OCaml/Symbol List%3A Exceptions.tmPreferences | 0 ...ist%3A Ocamllex pattern definition.tmPreferences | 0 ...ist%3A Ocamllex pattern references.tmPreferences | 0 .../Symbol List%3A Ocamllex rules.tmPreferences | 0 ... Ocamlyacc non-terminal definition.tmPreferences | 0 ...A Ocamlyacc non-terminal reference.tmPreferences | 0 ...List%3A Ocamlyacc token definition.tmPreferences | 0 ... List%3A Ocamlyacc token reference.tmPreferences | 0 .../OCaml/Symbol List%3A Types.tmPreferences | 0 .../OCaml/Symbol List%3A Variants.tmPreferences | 0 .../OCaml/Symbol List_ Classes.tmPreferences | 0 .../OCaml/Symbol List_ Exceptions.tmPreferences | 0 ... List_ Ocamllex pattern definition.tmPreferences | 0 ... List_ Ocamllex pattern references.tmPreferences | 0 .../OCaml/Symbol List_ Ocamllex rules.tmPreferences | 0 ... Ocamlyacc non-terminal definition.tmPreferences | 0 ..._ Ocamlyacc non-terminal reference.tmPreferences | 0 ...l List_ Ocamlyacc token definition.tmPreferences | 0 ...ol List_ Ocamlyacc token reference.tmPreferences | 0 .../Packages/OCaml/Symbol List_ Types.tmPreferences | 0 .../OCaml/Symbol List_ Variants.tmPreferences | 0 .../syntaxes/Packages/OCaml/camlp4.sublime-syntax | 0 .../syntaxes/Packages/OCaml/syntax_test_ml.ml | 0 .../Packages/Objective-C/Default.sublime-keymap | 0 .../Objective-C/Objective-C++.sublime-syntax | 0 .../Packages/Objective-C/Objective-C.sublime-syntax | 0 .../Symbol Index Include Constants.tmPreferences | 0 .../Packages/Objective-C/Symbol Index.tmPreferences | 0 .../Packages/Objective-C/syntax_test_accessor.m | 0 .../Packages/Objective-C/syntax_test_accessor.mm | 0 .../Packages/Objective-C/syntax_test_objc++.mm | 0 .../Packages/Objective-C/syntax_test_objc.m | 0 .../syntaxes/Packages/PHP/Comments.tmPreferences | 0 .../Packages/PHP/Completion Rules.tmPreferences | 0 .../Indentation Rules - heredoc end.tmPreferences | 0 .../PHP/Indentation Rules Annex.tmPreferences | 0 .../Packages/PHP/Indentation Rules.tmPreferences | 0 .../syntaxes/Packages/PHP/PHP Source.sublime-syntax | 0 .../syntaxes/Packages/PHP/PHP.sublime-completions | 0 .../assets/syntaxes/Packages/PHP/PHP.sublime-syntax | 0 .../PHP/Regular Expressions (PHP).sublime-syntax | 0 .../PHP/Snippets/$GLOBALS[''].sublime-snippet | 0 .../PHP/Snippets/$_COOKIE[''].sublime-snippet | 0 .../Packages/PHP/Snippets/$_ENV[''].sublime-snippet | 0 .../PHP/Snippets/$_FILES[''].sublime-snippet | 0 .../Packages/PHP/Snippets/$_GET[''].sublime-snippet | 0 .../PHP/Snippets/$_POST[''].sublime-snippet | 0 .../PHP/Snippets/$_REQUEST[''].sublime-snippet | 0 .../PHP/Snippets/$_SERVER[''].sublime-snippet | 0 .../PHP/Snippets/$_SESSION[''].sublime-snippet | 0 .../PHP/Snippets/Constructor.sublime-snippet | 0 .../PHP/Snippets/PHPDoc-class-var.sublime-snippet | 0 .../PHP/Snippets/PHPDoc-class.sublime-snippet | 0 .../PHPDoc-constant-definition.sublime-snippet | 0 .../PHPDoc-function-signature.sublime-snippet | 0 .../PHP/Snippets/PHPDoc-function.sublime-snippet | 0 .../PHP/Snippets/PHPDoc-interface.sublime-snippet | 0 .../PHP/Snippets/Start-Docblock.sublime-snippet | 0 .../Packages/PHP/Snippets/class-{-}.sublime-snippet | 0 .../Packages/PHP/Snippets/define(-).sublime-snippet | 0 .../PHP/Snippets/defined(-).sublime-snippet | 0 .../PHP/Snippets/do-while(-).sublime-snippet | 0 .../Packages/PHP/Snippets/echo-___.sublime-snippet | 0 .../Packages/PHP/Snippets/elseif(-).sublime-snippet | 0 .../Packages/PHP/Snippets/for(-).sublime-snippet | 0 .../PHP/Snippets/foreach(-).sublime-snippet | 0 .../PHP/Snippets/function-xx(-).sublime-snippet | 0 .../PHP/Snippets/if(-)-else(-).sublime-snippet | 0 .../Packages/PHP/Snippets/if(-).sublime-snippet | 0 .../Packages/PHP/Snippets/if-a-b;.sublime-snippet | 0 .../PHP/Snippets/include(-).sublime-snippet | 0 .../PHP/Snippets/include_once(-).sublime-snippet | 0 .../PHP/Snippets/new-array(-).sublime-snippet | 0 .../Packages/PHP/Snippets/php-$this.sublime-snippet | 0 .../PHP/Snippets/php-echo-$this.sublime-snippet | 0 .../PHP/Snippets/php-echo-___.sublime-snippet | 0 .../php-echo-htmlentities(___).sublime-snippet | 0 .../Packages/PHP/Snippets/php-else.sublime-snippet | 0 ...foreach-(___)-___-php-endforeach.sublime-snippet | 0 ...(___)-___-php-else-___-php-endif.sublime-snippet | 0 .../php-if-(___)-___-php-endif.sublime-snippet | 0 .../Packages/PHP/Snippets/php.sublime-snippet | 0 .../PHP/Snippets/require(-).sublime-snippet | 0 .../PHP/Snippets/require_once(-).sublime-snippet | 0 .../PHP/Snippets/return-$retVal;.sublime-snippet | 0 .../PHP/Snippets/return-FALSE;.sublime-snippet | 0 .../PHP/Snippets/return-TRUE;.sublime-snippet | 0 .../PHP/Snippets/switch(-)-case.sublime-snippet | 0 .../Packages/PHP/Snippets/switch(-).sublime-snippet | 0 .../Packages/PHP/Snippets/throw.sublime-snippet | 0 .../try-{-___-}-catch-(___)-{-___-}.sublime-snippet | 0 .../Packages/PHP/Snippets/while(-).sublime-snippet | 0 .../syntaxes/Packages/PHP/Symbol List.tmPreferences | 0 .../syntaxes/Packages/PHP/syntax_test_php.php | 0 .../Packages/Pascal/Miscellaneous.tmPreferences | 0 .../syntaxes/Packages/Pascal/Pascal.sublime-syntax | 0 .../assets/syntaxes/Packages/Pascal/syntax_test.pas | 0 .../syntaxes/Packages/Perl/Comments.tmPreferences | 0 .../syntaxes/Packages/Perl/Perl.sublime-build | 0 .../syntaxes/Packages/Perl/Perl.sublime-syntax | 0 .../Snippets/Conditional-if-(if).sublime-snippet | 0 .../Conditional-if..else-(ife).sublime-snippet | 0 ...nditional-if..elsif..else-(ifee).sublime-snippet | 0 .../Conditional-one-line-(xif).sublime-snippet | 0 .../Conditional-one-line-(xunless).sublime-snippet | 0 .../Conditional-one-line-(xwhen).sublime-snippet | 0 .../Conditional-unless-(unless).sublime-snippet | 0 ...nditional-unless..else-(unlesse).sublime-snippet | 0 ...l-unless..elsif..else-(unlessee).sublime-snippet | 0 .../Conditional-when-(when).sublime-snippet | 0 .../Perl/Snippets/Function-(sub).sublime-snippet | 0 .../Perl/Snippets/Loop-for-(for).sublime-snippet | 0 .../Snippets/Loop-foreach-(fore).sublime-snippet | 0 .../Snippets/Loop-one-line-(xfor).sublime-snippet | 0 .../Snippets/Loop-one-line-(xfore).sublime-snippet | 0 .../Snippets/Loop-one-line-(xuntil).sublime-snippet | 0 .../Snippets/Loop-one-line-(xwhile).sublime-snippet | 0 .../Snippets/Loop-while-(while).sublime-snippet | 0 .../Packages/Perl/Snippets/Test.sublime-snippet | 0 .../Packages/Perl/Snippets/class.sublime-snippet | 0 .../Packages/Perl/Snippets/eval.sublime-snippet | 0 .../Packages/Perl/Snippets/slurp.sublime-snippet | 0 .../syntaxes/Packages/Perl/syntax_test_perl.pl | 0 .../Packages/Python/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/Python/Default.sublime-keymap | 0 .../Packages/Python/Miscellaneous.tmPreferences | 0 .../syntaxes/Packages/Python/Python.sublime-build | 0 .../syntaxes/Packages/Python/Python.sublime-syntax | 0 .../Regular Expressions (Python).sublime-syntax | 0 .../Python/Snippets/New-Class.sublime-snippet | 0 .../Python/Snippets/New-Property.sublime-snippet | 0 .../Try-Except-Else-Finally.sublime-snippet | 0 .../Python/Snippets/Try-Except-Else.sublime-snippet | 0 .../Snippets/Try-Except-Finally.sublime-snippet | 0 .../Python/Snippets/Try-Except.sublime-snippet | 0 .../Python/Snippets/__magic__.sublime-snippet | 0 .../Packages/Python/Snippets/for.sublime-snippet | 0 .../Python/Snippets/function.sublime-snippet | 0 .../if-__name__-==-'__main__'.sublime-snippet | 0 .../Packages/Python/Snippets/if.sublime-snippet | 0 .../Packages/Python/Snippets/method.sublime-snippet | 0 .../Packages/Python/Snippets/while.sublime-snippet | 0 .../Packages/Python/Symbol Index.tmPreferences | 0 .../Packages/Python/Symbol List.tmPreferences | 0 .../syntaxes/Packages/Python/syntax_test_python.py | 0 .../Packages/Python/syntax_test_python_strings.py | 0 .../syntaxes/Packages/R/Comments.tmPreferences | 0 .../syntaxes/Packages/R/R Console.sublime-syntax | 0 .../assets/syntaxes/Packages/R/R.sublime-build | 0 .../assets/syntaxes/Packages/R/R.sublime-settings | 0 .../assets/syntaxes/Packages/R/R.sublime-syntax | 0 .../Packages/R/Rd (R Documentation).sublime-syntax | 0 .../R/Snippets/Add-Tick-Marks.sublime-snippet | 0 .../Packages/R/Snippets/Attach.sublime-snippet | 0 .../Packages/R/Snippets/Cummulative.sublime-snippet | 0 .../Packages/R/Snippets/Density.sublime-snippet | 0 .../Packages/R/Snippets/Detach.sublime-snippet | 0 .../Snippets/Divide-Into-Intervals.sublime-snippet | 0 .../Packages/R/Snippets/Factor.sublime-snippet | 0 .../Packages/R/Snippets/For-Loop.sublime-snippet | 0 .../Packages/R/Snippets/Function.sublime-snippet | 0 .../Packages/R/Snippets/Ifelse.sublime-snippet | 0 .../Packages/R/Snippets/Length.sublime-snippet | 0 .../R/Snippets/Load-Dataset.sublime-snippet | 0 .../R/Snippets/Polygonal-Line.sublime-snippet | 0 .../R/Snippets/Read-From-File.sublime-snippet | 0 .../Snippets/Sequence-(from-to-by).sublime-snippet | 0 .../Packages/R/Snippets/Sort.sublime-snippet | 0 .../Packages/R/Snippets/Source.sublime-snippet | 0 .../Packages/R/Snippets/na_omit.sublime-snippet | 0 .../Packages/R/Symbol List - Methods.tmPreferences | 0 ...List - Sections (Rd Documentation).tmPreferences | 0 .../Packages/R/Symbol List - Sections.tmPreferences | 0 .../assets/syntaxes/Packages/R/syntax_test_r.R | 0 .../docs_rs_web/assets/syntaxes/Packages/README.md | 0 .../Packages/Rails/HTML (Rails).sublime-syntax | 0 .../Rails/JavaScript (Rails).sublime-syntax | 0 .../Packages/Rails/Ruby Haml Comments.tmPreferences | 0 .../Packages/Rails/Ruby Haml.sublime-syntax | 0 .../Packages/Rails/Ruby on Rails.sublime-syntax | 0 .../Packages/Rails/SQL (Rails).sublime-syntax | 0 .../Packages/Rails/Snippets/$LABEL.sublime-snippet | 0 ...ixtures_identify(%3Asymbol)-%%3E.sublime-snippet | 0 .../Snippets/180-rails-form_tag.sublime-snippet | 0 .../Snippets/Create-binary-column.sublime-snippet | 0 .../Snippets/Create-boolean-column.sublime-snippet | 0 .../Create-controller-class.sublime-snippet | 0 .../Snippets/Create-date-column.sublime-snippet | 0 .../Snippets/Create-datetime-column.sublime-snippet | 0 .../Snippets/Create-decimal-column.sublime-snippet | 0 .../Snippets/Create-float-column.sublime-snippet | 0 .../Create-functional-test-class.sublime-snippet | 0 .../Snippets/Create-integer-column.sublime-snippet | 0 .../Create-lock_version-column.sublime-snippet | 0 .../Create-references-column.sublime-snippet | 0 .../Snippets/Create-string-column.sublime-snippet | 0 .../Snippets/Create-text-column.sublime-snippet | 0 .../Snippets/Create-time-column.sublime-snippet | 0 .../Create-timestamp-column.sublime-snippet | 0 .../Create-timestamps-columns.sublime-snippet | 0 .../Migration-Create-Column-(mcc).sublime-snippet | 0 ...on-Create-Column-Continue-(mccc).sublime-snippet | 0 ...gration-Drop-Create-Table-(mdct).sublime-snippet | 0 ...ion-Remove-and-Add-Column-(mrac).sublime-snippet | 0 ...RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet | 0 .../Snippets/Table-column(s)-rename.sublime-snippet | 0 .../Test-Assert-Redirected-To-(art).sublime-snippet | 0 .../Test-Assert-Response-(are).sublime-snippet | 0 .../Rails/Snippets/after_create.sublime-snippet | 0 .../Rails/Snippets/after_destroy.sublime-snippet | 0 .../Rails/Snippets/after_save.sublime-snippet | 0 .../Rails/Snippets/after_update.sublime-snippet | 0 .../Rails/Snippets/after_validation.sublime-snippet | 0 .../after_validation_on_create.sublime-snippet | 0 .../after_validation_on_update.sublime-snippet | 0 .../assert(var-=-assigns(%3Avar)).sublime-snippet | 0 .../Snippets/assert_difference.sublime-snippet | 0 .../Snippets/assert_no_difference.sublime-snippet | 0 ...sert_redirected_to-(nested-path).sublime-snippet | 0 ...directed_to-(nested-path-plural).sublime-snippet | 0 .../assert_redirected_to-(path).sublime-snippet | 0 ...sert_redirected_to-(path-plural).sublime-snippet | 0 .../Rails/Snippets/assert_select.sublime-snippet | 0 .../Rails/Snippets/before_create.sublime-snippet | 0 .../Rails/Snippets/before_destroy.sublime-snippet | 0 .../Rails/Snippets/before_save.sublime-snippet | 0 .../Rails/Snippets/before_update.sublime-snippet | 0 .../Snippets/before_validation.sublime-snippet | 0 .../before_validation_on_create.sublime-snippet | 0 .../before_validation_on_update.sublime-snippet | 0 .../Rails/Snippets/belongs_to-(bt).sublime-snippet | 0 .../Rails/Snippets/cattr_accessor.sublime-snippet | 0 .../Snippets/def-create-resource.sublime-snippet | 0 .../Rails/Snippets/def-get-request.sublime-snippet | 0 .../Rails/Snippets/def-post-request.sublime-snippet | 0 .../Packages/Rails/Snippets/end.sublime-snippet | 0 .../Rails/Snippets/find(id).sublime-snippet | 0 .../Rails/Snippets/for-loop-erb.sublime-snippet | 0 .../Snippets/form_for-check_box.sublime-snippet | 0 .../Snippets/form_for-checkbox.sublime-snippet | 0 .../Snippets/form_for-file_field-2.sublime-snippet | 0 .../Snippets/form_for-file_field.sublime-snippet | 0 .../form_for-hidden_field-2.sublime-snippet | 0 .../Snippets/form_for-hidden_field.sublime-snippet | 0 .../Rails/Snippets/form_for-label-2.sublime-snippet | 0 .../Rails/Snippets/form_for-label.sublime-snippet | 0 .../form_for-password_field-2.sublime-snippet | 0 .../form_for-password_field.sublime-snippet | 0 .../Snippets/form_for-radio_box-2.sublime-snippet | 0 .../Snippets/form_for-radio_box.sublime-snippet | 0 .../Snippets/form_for-submit-2.sublime-snippet | 0 .../Rails/Snippets/form_for-submit.sublime-snippet | 0 .../Snippets/form_for-text_area-2.sublime-snippet | 0 .../Snippets/form_for-text_area.sublime-snippet | 0 .../Snippets/form_for-text_field-2.sublime-snippet | 0 .../Snippets/form_for-text_field.sublime-snippet | 0 .../Snippets/form_for-with-errors.sublime-snippet | 0 .../Rails/Snippets/form_for.sublime-snippet | 0 .../has_and_belongs_to_many-(habtm).sublime-snippet | 0 .../Rails/Snippets/has_many-(hm).sublime-snippet | 0 .../Snippets/has_many-(through).sublime-snippet | 0 .../has_many-dependent-=-destroy.sublime-snippet | 0 .../Rails/Snippets/has_one-(ho).sublime-snippet | 0 .../Rails/Snippets/image_submit_tag.sublime-snippet | 0 .../Snippets/javascript_include_tag.sublime-snippet | 0 .../Packages/Rails/Snippets/lia.sublime-snippet | 0 .../Packages/Rails/Snippets/liai.sublime-snippet | 0 .../Packages/Rails/Snippets/lic.sublime-snippet | 0 .../Packages/Rails/Snippets/lica.sublime-snippet | 0 .../Packages/Rails/Snippets/licai.sublime-snippet | 0 .../Snippets/link_to-(nested-path).sublime-snippet | 0 .../link_to-(nested-path-plural).sublime-snippet | 0 .../Rails/Snippets/link_to-(path).sublime-snippet | 0 .../Snippets/link_to-(path-plural).sublime-snippet | 0 .../Rails/Snippets/link_to-model.sublime-snippet | 0 .../Rails/Snippets/logger_debug.sublime-snippet | 0 .../Rails/Snippets/logger_error.sublime-snippet | 0 .../Rails/Snippets/logger_fatal.sublime-snippet | 0 .../Rails/Snippets/logger_info.sublime-snippet | 0 .../Rails/Snippets/logger_warn.sublime-snippet | 0 .../Snippets/map(-%3Asym_proc).sublime-snippet | 0 .../Rails/Snippets/map_catch_all.sublime-snippet | 0 .../Rails/Snippets/map_named_route.sublime-snippet | 0 .../Rails/Snippets/map_resource.sublime-snippet | 0 .../Rails/Snippets/map_resources.sublime-snippet | 0 .../Rails/Snippets/map_with_options.sublime-snippet | 0 .../Rails/Snippets/mattr_accessor.sublime-snippet | 0 .../Snippets/named_scope-lambda.sublime-snippet | 0 .../Rails/Snippets/named_scope.sublime-snippet | 0 .../Rails/Snippets/rails-flash.sublime-snippet | 0 .../Packages/Rails/Snippets/rea.sublime-snippet | 0 .../Packages/Rails/Snippets/reai.sublime-snippet | 0 .../Packages/Rails/Snippets/rec.sublime-snippet | 0 .../Packages/Rails/Snippets/reca.sublime-snippet | 0 .../Packages/Rails/Snippets/recai.sublime-snippet | 0 .../redirect_to-(nested-path).sublime-snippet | 0 ...redirect_to-(nested-path-plural).sublime-snippet | 0 .../Snippets/redirect_to-(path).sublime-snippet | 0 .../redirect_to-(path-plural).sublime-snippet | 0 .../render-(action)...-(ra).sublime-snippet | 0 .../render-(action-layout)-(ral).sublime-snippet | 0 .../Snippets/render-(file)-(rf).sublime-snippet | 0 ...ender-(file-use_full_path)-(rfu).sublime-snippet | 0 .../Snippets/render-(inline)-(ri).sublime-snippet | 0 .../render-(inline-locals)-(ril).sublime-snippet | 0 .../render-(inline-type)-(rit).sublime-snippet | 0 .../Snippets/render-(layout)-(rl).sublime-snippet | 0 .../Snippets/render-(nothing)-(rn).sublime-snippet | 0 .../render-(nothing-status)-(rns).sublime-snippet | 0 .../Snippets/render-(partial)-(rp).sublime-snippet | 0 ...ender-(partial-collection)-(rpc).sublime-snippet | 0 .../render-(partial-locals)-(rpl).sublime-snippet | 0 .../render-(partial-object)-(rpo).sublime-snippet | 0 .../render-(partial-status)-(rps).sublime-snippet | 0 .../Snippets/render-(text)-(rt).sublime-snippet | 0 .../render-(text-layout)-(rtl).sublime-snippet | 0 ...der-(text-layout=%3Etrue)-(rtlt).sublime-snippet | 0 .../render-(text-status)-(rts).sublime-snippet | 0 .../Rails/Snippets/render-(update).sublime-snippet | 0 .../Rails/Snippets/respond_to.sublime-snippet | 0 ...-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet | 0 .../Snippets/stylesheet_link_tag.sublime-snippet | 0 .../Rails/Snippets/submit_tag.sublime-snippet | 0 .../Rails/Snippets/t_binary-(tcbi).sublime-snippet | 0 .../Rails/Snippets/t_boolean-(tcb).sublime-snippet | 0 .../Rails/Snippets/t_date-(tcda).sublime-snippet | 0 .../Snippets/t_datetime-(tcdt).sublime-snippet | 0 .../Rails/Snippets/t_decimal-(tcd).sublime-snippet | 0 .../Rails/Snippets/t_float-(tcf).sublime-snippet | 0 .../Rails/Snippets/t_integer-(tci).sublime-snippet | 0 .../Snippets/t_lock_version-(tcl).sublime-snippet | 0 .../Snippets/t_references-(tcr).sublime-snippet | 0 .../Rails/Snippets/t_rename-(tre).sublime-snippet | 0 .../Rails/Snippets/t_string-(tcs).sublime-snippet | 0 .../Rails/Snippets/t_text-(tct).sublime-snippet | 0 .../Rails/Snippets/t_time-(tcti).sublime-snippet | 0 .../Snippets/t_timestamp-(tcts).sublime-snippet | 0 .../Snippets/t_timestamps-(tctss).sublime-snippet | 0 .../validates_acceptance_of-if.sublime-snippet | 0 .../validates_acceptance_of.sublime-snippet | 0 .../validates_associated-(va).sublime-snippet | 0 .../validates_associated-if-(vaif).sublime-snippet | 0 .../validates_confirmation_of-(vc).sublime-snippet | 0 ...idates_confirmation_of-if-(vcif).sublime-snippet | 0 .../validates_exclusion_of-(ve).sublime-snippet | 0 ...validates_exclusion_of-if-(veif).sublime-snippet | 0 .../Snippets/validates_format_of-if.sublime-snippet | 0 .../Snippets/validates_format_of.sublime-snippet | 0 .../validates_inclusion_of-if.sublime-snippet | 0 .../Snippets/validates_inclusion_of.sublime-snippet | 0 .../validates_length_of-(vl).sublime-snippet | 0 .../Snippets/validates_length_of-if.sublime-snippet | 0 .../validates_numericality_of-if.sublime-snippet | 0 .../validates_numericality_of.sublime-snippet | 0 .../validates_presence_of-(vp).sublime-snippet | 0 ...alidates_presence_of-if-(vpif)-2.sublime-snippet | 0 .../validates_uniqueness_of-(vu).sublime-snippet | 0 ...alidates_uniqueness_of-if-(vuif).sublime-snippet | 0 .../Rails/Snippets/verify-(verify).sublime-snippet | 0 .../verify-redirect-(verify).sublime-snippet | 0 .../Rails/Snippets/wants_format.sublime-snippet | 0 .../Rails/Snippets/xhr-delete.sublime-snippet | 0 .../Packages/Rails/Snippets/xhr-get.sublime-snippet | 0 .../Rails/Snippets/xhr-post.sublime-snippet | 0 .../Packages/Rails/Snippets/xhr-put.sublime-snippet | 0 .../Packages/Rails/Template (ERB).tmPreferences | 0 .../Packages/Rails/Template (Haml).tmPreferences | 0 .../Packages/Rails/syntax_test_html_rails.html.erb | 0 .../syntaxes/Packages/Rails/syntax_test_rails.rb | 0 .../Regular Expressions/Comments.tmPreferences | 0 .../Regular Expressions/RegExp.sublime-syntax | 0 .../Regular Expressions/syntax_test_regexp.re | 0 .../RestructuredText/Comments.tmPreferences | 0 .../reStructuredText.sublime-syntax | 0 .../syntax_test_restructuredtext.rst | 0 .../syntaxes/Packages/Ruby/Comments.tmPreferences | 0 .../Packages/Ruby/Completion Rules.tmPreferences | 0 .../syntaxes/Packages/Ruby/Default.sublime-keymap | 0 .../Packages/Ruby/Miscellaneous.tmPreferences | 0 .../syntaxes/Packages/Ruby/Ruby.sublime-build | 0 .../syntaxes/Packages/Ruby/Ruby.sublime-syntax | 0 .../#!;usr;local;bin;ruby-w.sublime-snippet | 0 .../Ruby/Snippets/060-ruby-if-else.sublime-snippet | 0 .../Ruby/Snippets/070-ruby-if.sublime-snippet | 0 .../Ruby/Snippets/080-ruby-case.sublime-snippet | 0 .../Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet | 0 .../Array.new(10)-{-i-..-}-(Arr).sublime-snippet | 0 .../Benchmark_bmbm(__)-do-__-end.sublime-snippet | 0 ....glob(-..-)-do-file-..-end-(Dir).sublime-snippet | 0 .../Ruby/Snippets/Dir[-__-].sublime-snippet | 0 ...ach-(-..-)-do-line-..-end-(File).sublime-snippet | 0 .../File_open(-__-)-{-file-__-}.sublime-snippet | 0 .../Ruby/Snippets/File_read(-__-).sublime-snippet | 0 ...-hash-key-hash[key]-=-..-}-(Has).sublime-snippet | 0 .../Marshal.dump(obj-file)-(Md).sublime-snippet | 0 .../Snippets/Marshal.load(obj)-(Ml).sublime-snippet | 0 .../Ruby/Snippets/PStore_new(-__-).sublime-snippet | 0 .../RDoc-documentation-block.sublime-snippet | 0 .../Wrap-in-Begin-Rescue-End.sublime-snippet | 0 .../YAML.dump(..-file)-(Yd-).sublime-snippet | 0 .../Snippets/YAML.load(file)-(Yl-).sublime-snippet | 0 .../Snippets/alias_method-..-(am).sublime-snippet | 0 .../Snippets/all-{-e-..-}-(all).sublime-snippet | 0 .../Snippets/any-{-e-..-}-(any).sublime-snippet | 0 .../application_code-..-(app).sublime-snippet | 0 .../Ruby/Snippets/assert(..)-(as).sublime-snippet | 0 .../Ruby/Snippets/assert_equal.sublime-snippet | 0 .../assert_in_delta(..)-(asid).sublime-snippet | 0 .../assert_instance_of(..)-(asio).sublime-snippet | 0 .../assert_kind_of(..)-(asko).sublime-snippet | 0 .../Snippets/assert_match(..)-(asm).sublime-snippet | 0 .../Snippets/assert_nil(..)-(asn).sublime-snippet | 0 .../assert_no_match(..)-(asnm).sublime-snippet | 0 .../assert_not_equal(..)-(asne).sublime-snippet | 0 .../assert_not_nil(..)-(asnn).sublime-snippet | 0 .../assert_not_same(..)-(asns).sublime-snippet | 0 ...nothing_raised(..)-{-..-}-(asnr).sublime-snippet | 0 ...ert_nothing_thrown-{-..-}-(asnt).sublime-snippet | 0 .../assert_operator(..)-(aso).sublime-snippet | 0 .../assert_raise(..)-{-..-}-(asr).sublime-snippet | 0 .../assert_respond_to(..)-(asrt).sublime-snippet | 0 .../Snippets/assert_same(..)-(ass).sublime-snippet | 0 .../Snippets/assert_send(..)-(ass).sublime-snippet | 0 .../assert_throws(..)-{-..-}-(ast).sublime-snippet | 0 .../Snippets/attr_accessor-..-(rw).sublime-snippet | 0 .../Snippets/attr_reader-..-(r).sublime-snippet | 0 .../Snippets/attr_writer-..-(w).sublime-snippet | 0 ...ass-..-initialize-..-end-(class).sublime-snippet | 0 ...ParentClass-..-initialize-..-end.sublime-snippet | 0 ...s-..-Struct-..-initialize-..-end.sublime-snippet | 0 ...Test;;Unit;;TestCase-..-end-(tc).sublime-snippet | 0 .../Snippets/class-..-end-(cla).sublime-snippet | 0 .../class-..-initialize-..-end.sublime-snippet | 0 ...def-..-initialize-..-end-(class).sublime-snippet | 0 .../Ruby/Snippets/class-self-__-end.sublime-snippet | 0 .../class_from_name()-(clafn).sublime-snippet | 0 .../classify-{-e-..-}-(clas).sublime-snippet | 0 .../Snippets/collect-{-e-..-}-(col).sublime-snippet | 0 .../Snippets/deep_copy(..)-(dee).sublime-snippet | 0 .../Packages/Ruby/Snippets/def-end.sublime-snippet | 0 .../def-method_missing-..-end-(mm).sublime-snippet | 0 .../Snippets/def-self-..-end-(defs).sublime-snippet | 0 .../Snippets/def-test_-..-end-(t).sublime-snippet | 0 .../def_delegator-..-(defd).sublime-snippet | 0 .../def_delegators-..-(defds).sublime-snippet | 0 .../Ruby/Snippets/def_initialize.sublime-snippet | 0 .../delete_if-{-e-..-}-(deli).sublime-snippet | 0 .../Snippets/detect-{-e-..-}-(det).sublime-snippet | 0 .../Ruby/Snippets/directory().sublime-snippet | 0 .../Snippets/do-obj-..-end-(doo).sublime-snippet | 0 .../downto(0)-{-n-..-}-(dow).sublime-snippet | 0 .../Snippets/each-{-e-..-}-(ea).sublime-snippet | 0 .../each_byte-{-byte-..-}-(eab).sublime-snippet | 0 .../each_char-{-chr-..-}-(eac-).sublime-snippet | 0 ...ach_cons(..)-{-group-..-}-(eac-).sublime-snippet | 0 .../each_index-{-i-..-}-(eai).sublime-snippet | 0 .../each_key-{-key-..-}-(eak).sublime-snippet | 0 .../each_line-{-line-..-}-(eal).sublime-snippet | 0 .../each_pair-{-name-val-..-}-(eap).sublime-snippet | 0 .../each_slice-{-group-..-}-(eas).sublime-snippet | 0 .../each_value-{-val-..-}-(eav).sublime-snippet | 0 ...ach_with_index-{-e-i-..-}-(eawi).sublime-snippet | 0 .../Ruby/Snippets/elsif-___.sublime-snippet | 0 .../extend-Forwardable-(Forw).sublime-snippet | 0 .../fetch(name)-{-key-..-}-(fet).sublime-snippet | 0 .../fill(range)-{-i-..-}-(fil).sublime-snippet | 0 .../Snippets/find-{-e-..-}-(fin).sublime-snippet | 0 .../find_all-{-e-..-}-(fina).sublime-snippet | 0 .../Snippets/flatten_once-(fla).sublime-snippet | 0 .../Ruby/Snippets/flunk(..)-(fl).sublime-snippet | 0 ...ep(;pattern;)-{-match-..-}-(gre).sublime-snippet | 0 .../gsub(;..;)-{-match-..-}-(gsu).sublime-snippet | 0 .../Ruby/Snippets/hash-pair-(-).sublime-snippet | 0 .../include-Comparable-..-(Comp).sublime-snippet | 0 .../include-Enumerable-..-(Enum).sublime-snippet | 0 ...nject(init)-{-mem-var-..-}-(inj).sublime-snippet | 0 .../lambda-{-args-..-}-(lam).sublime-snippet | 0 .../Ruby/Snippets/loop-{-__-}.sublime-snippet | 0 .../Snippets/map-{-e-..-}-(map).sublime-snippet | 0 ...ap_with_index-{-e-i-..-}-(mapwi).sublime-snippet | 0 .../Snippets/max-{-a-b-..-}-(max).sublime-snippet | 0 .../Snippets/min-{-a-b-..-}-(min).sublime-snippet | 0 .../module-..-ClassMethods-..-end.sublime-snippet | 0 .../Ruby/Snippets/module-..-end.sublime-snippet | 0 ...module-..-module_function-..-end.sublime-snippet | 0 .../Snippets/namespace-__-do-__-end.sublime-snippet | 0 ...h;or;url-w-)-do-doc-..-end-(ope).sublime-snippet | 0 .../Snippets/open-yield-block-({).sublime-snippet | 0 .../option_parse-{-..-}-(optp).sublime-snippet | 0 .../partition-{-e-..-}-(par).sublime-snippet | 0 .../Snippets/path_from_here(-__-).sublime-snippet | 0 .../Ruby/Snippets/randomize-(ran).sublime-snippet | 0 .../Snippets/reject-{-e-..-}-(rej).sublime-snippet | 0 .../Ruby/Snippets/require-..-(req).sublime-snippet | 0 .../Snippets/require-tc_..-..-(ts).sublime-snippet | 0 .../Ruby/Snippets/require_gem-__.sublime-snippet | 0 .../results_report(__)-{-__-}.sublime-snippet | 0 .../reverse_each-{-e-..-}-(rea).sublime-snippet | 0 .../scan(;..;)-{-match-..-}-(sca).sublime-snippet | 0 .../Snippets/select-{-e-..-}-(sel).sublime-snippet | 0 .../Ruby/Snippets/service_object.sublime-snippet | 0 .../Ruby/Snippets/singleton_class().sublime-snippet | 0 .../Snippets/sort-{-a-b-..-}-(sor).sublime-snippet | 0 .../sort_by-{-e-..-}-(sorb).sublime-snippet | 0 .../Snippets/step(2)-{-e-..-}-(ste).sublime-snippet | 0 .../sub(;..;)-{-match-..-}-(sub).sublime-snippet | 0 ...e-=-[-dependent-tasks]-do-__-end.sublime-snippet | 0 .../Snippets/times-{-n-..-}-(tim).sublime-snippet | 0 .../transaction(-__-)-do-__-end.sublime-snippet | 0 .../Snippets/unix_filter-..-(uni).sublime-snippet | 0 .../Ruby/Snippets/unless-(unless).sublime-snippet | 0 .../Ruby/Snippets/until-___-end.sublime-snippet | 0 .../Packages/Ruby/Snippets/untitled.sublime-snippet | 0 .../upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet | 0 .../Ruby/Snippets/usage_if()-(usai).sublime-snippet | 0 .../Snippets/usage_unless()-(usau).sublime-snippet | 0 .../Packages/Ruby/Snippets/when.sublime-snippet | 0 .../Ruby/Snippets/while-___-end.sublime-snippet | 0 .../Ruby/Snippets/xmlread(__).sublime-snippet | 0 .../Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet | 0 .../Snippets/yields-RDoc-comment.sublime-snippet | 0 .../zip(enums)-{-row-..-}-(zip).sublime-snippet | 0 .../Ruby/Symbols - Classes - Modules.tmPreferences | 0 .../Packages/Ruby/Symbols - Methods.tmPreferences | 0 .../syntaxes/Packages/Ruby/syntax_test_ruby.rb | 0 .../syntaxes/Packages/Rust/Cargo.sublime-build | 0 .../syntaxes/Packages/Rust/Cargo.sublime-syntax | 0 .../syntaxes/Packages/Rust/Default.sublime-keymap | 0 .../assets/syntaxes/Packages/Rust/LICENSE.txt | 0 .../syntaxes/Packages/Rust/Rust.sublime-build | 0 .../syntaxes/Packages/Rust/Rust.sublime-syntax | 0 .../Packages/Rust/RustComment.tmPreferences | 0 .../syntaxes/Packages/Rust/RustIndent.tmPreferences | 0 .../Packages/Rust/RustSymbols.tmPreferences | 0 .../Packages/Rust/Snippets/Err.sublime-snippet | 0 .../Packages/Rust/Snippets/Ok.sublime-snippet | 0 .../Packages/Rust/Snippets/Some.sublime-snippet | 0 .../Packages/Rust/Snippets/assert.sublime-snippet | 0 .../Rust/Snippets/assert_eq.sublime-snippet | 0 .../Packages/Rust/Snippets/bench.sublime-snippet | 0 .../Packages/Rust/Snippets/const.sublime-snippet | 0 .../Packages/Rust/Snippets/else.sublime-snippet | 0 .../Packages/Rust/Snippets/enum.sublime-snippet | 0 .../Rust/Snippets/extern-crate.sublime-snippet | 0 .../Rust/Snippets/extern-fn.sublime-snippet | 0 .../Rust/Snippets/extern-mod.sublime-snippet | 0 .../Packages/Rust/Snippets/fmt.sublime-snippet | 0 .../Packages/Rust/Snippets/fn.sublime-snippet | 0 .../Packages/Rust/Snippets/for.sublime-snippet | 0 .../Packages/Rust/Snippets/if-let.sublime-snippet | 0 .../Packages/Rust/Snippets/if.sublime-snippet | 0 .../Rust/Snippets/impl-trait.sublime-snippet | 0 .../Packages/Rust/Snippets/impl.sublime-snippet | 0 .../Packages/Rust/Snippets/let.sublime-snippet | 0 .../Packages/Rust/Snippets/loop.sublime-snippet | 0 .../Rust/Snippets/macro_rules.sublime-snippet | 0 .../Packages/Rust/Snippets/main.sublime-snippet | 0 .../Packages/Rust/Snippets/match.sublime-snippet | 0 .../Packages/Rust/Snippets/mod.sublime-snippet | 0 .../Packages/Rust/Snippets/panic.sublime-snippet | 0 .../Packages/Rust/Snippets/print.sublime-snippet | 0 .../Packages/Rust/Snippets/println.sublime-snippet | 0 .../Packages/Rust/Snippets/static.sublime-snippet | 0 .../Rust/Snippets/struct-tuple.sublime-snippet | 0 .../Rust/Snippets/struct-unit.sublime-snippet | 0 .../Packages/Rust/Snippets/struct.sublime-snippet | 0 .../Packages/Rust/Snippets/test.sublime-snippet | 0 .../Packages/Rust/Snippets/trait.sublime-snippet | 0 .../Packages/Rust/Snippets/type.sublime-snippet | 0 .../Rust/Snippets/while-let.sublime-snippet | 0 .../Packages/Rust/Snippets/while.sublime-snippet | 0 .../syntaxes/Packages/Rust/syntax_test_rust.rs | 0 .../syntaxes/Packages/SQL/Comments.tmPreferences | 0 .../Packages/SQL/Miscellaneous.tmPreferences | 0 .../assets/syntaxes/Packages/SQL/SQL.sublime-syntax | 0 .../syntaxes/Packages/SQL/syntax_test_sql.sql | 0 .../syntaxes/Packages/Scala/Comments.tmPreferences | 0 .../Packages/Scala/Dedent-case.tmPreferences | 0 .../Packages/Scala/Indent-case.tmPreferences | 0 .../syntaxes/Packages/Scala/Indent.tmPreferences | 0 .../syntaxes/Packages/Scala/Scala.sublime-syntax | 0 .../Packages/Scala/Snippets/adt.sublime-snippet | 0 .../Packages/Scala/Snippets/app.sublime-snippet | 0 .../Packages/Scala/Snippets/case.sublime-snippet | 0 .../Packages/Scala/Snippets/cc.sublime-snippet | 0 .../Packages/Scala/Snippets/co.sublime-snippet | 0 .../Packages/Scala/Snippets/def.sublime-snippet | 0 .../Packages/Scala/Snippets/match.sublime-snippet | 0 .../Packages/Scala/Snippets/p.sublime-snippet | 0 .../Packages/Scala/Snippets/try.sublime-snippet | 0 .../Packages/Scala/Snippets/tryf.sublime-snippet | 0 .../Packages/Scala/Snippets/val.sublime-snippet | 0 .../Packages/Scala/Snippets/var.sublime-snippet | 0 .../Packages/Scala/Symbols-class.tmPreferences | 0 .../Packages/Scala/Symbols-def.tmPreferences | 0 .../Packages/Scala/Symbols-namespace.tmPreferences | 0 .../Packages/Scala/Symbols-type.tmPreferences | 0 .../Packages/Scala/Symbols-val.tmPreferences | 0 .../Packages/Scala/Symbols-var.tmPreferences | 0 .../syntaxes/Packages/Scala/Symbols.tmPreferences | 0 .../assets/syntaxes/Packages/Scala/info.plist | 0 .../syntaxes/Packages/Scala/syntax_test_scala.scala | 0 .../Packages/ShellScript/Bash.sublime-syntax | 0 .../Packages/ShellScript/Comments.tmPreferences | 0 .../ShellScript/Completion Rules.tmPreferences | 0 .../Packages/ShellScript/Indentation.tmPreferences | 0 .../assets/syntaxes/Packages/ShellScript/Makefile | 0 .../ShellScript/Shell-Unix-Generic.sublime-syntax | 0 .../syntaxes/Packages/ShellScript/ShellScript.py | 0 .../Packages/ShellScript/ShellScript.sublime-build | 0 .../Snippets/#!-usr-bin-env-(!env).sublime-snippet | 0 .../Snippets/case-..-esac-(case).sublime-snippet | 0 .../Snippets/elif-..-(elif).sublime-snippet | 0 .../Snippets/for-...-done-(for).sublime-snippet | 0 .../Snippets/for-in-done-(forin).sublime-snippet | 0 .../Snippets/if-...-then-(if).sublime-snippet | 0 .../Snippets/until-(done).sublime-snippet | 0 .../Snippets/while-(done).sublime-snippet | 0 .../ShellScript/Symbol List - Aliases.tmPreferences | 0 .../Symbol List - Expansions.tmPreferences | 0 .../Symbol List - Functions.tmPreferences | 0 .../Symbol List - Variables.tmPreferences | 0 .../commands-builtin-shell-bash.sublime-syntax | 0 .../ShellScript/commands-builtin-shell-bash.yml | 0 .../Packages/ShellScript/test/syntax_test_bash.sh | 0 .../test/syntax_test_shell_unix_generic.sh | 0 .../Packages/ShellScript/tools/update-commands.py | 0 .../syntaxes/Packages/TCL/Comments.tmPreferences | 0 .../syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax | 0 .../TCL/Snippets/for...-(for).sublime-snippet | 0 .../Snippets/foreach...-(foreach).sublime-snippet | 0 .../TCL/Snippets/if...-(if).sublime-snippet | 0 .../TCL/Snippets/proc...-(proc).sublime-snippet | 0 .../TCL/Snippets/switch...-(switch).sublime-snippet | 0 .../TCL/Snippets/while...-(while).sublime-snippet | 0 .../TCL/Symbol List Indent NS Proc.tmPreferences | 0 .../syntaxes/Packages/TCL/Symbol List.tmPreferences | 0 .../assets/syntaxes/Packages/TCL/Tcl.sublime-syntax | 0 .../syntaxes/Packages/TCL/syntax_test_tcl.tcl | 0 .../syntaxes/Packages/Text/Plain text.tmLanguage | 0 .../Packages/Text/Snippets/lorem.sublime-snippet | 0 .../Textile/Snippets/Acronym.sublime-snippet | 0 .../Textile/Snippets/Block-Quotes.sublime-snippet | 0 .../Textile/Snippets/Heading-1.sublime-snippet | 0 .../Textile/Snippets/Heading-2.sublime-snippet | 0 .../Textile/Snippets/Heading-3.sublime-snippet | 0 .../Textile/Snippets/Heading-4.sublime-snippet | 0 .../Textile/Snippets/Heading-5.sublime-snippet | 0 .../Textile/Snippets/Heading-6.sublime-snippet | 0 .../Packages/Textile/Snippets/Image.sublime-snippet | 0 .../Textile/Snippets/Linked-Image.sublime-snippet | 0 .../Packages/Textile/Textile.sublime-syntax | 0 .../Packages/Textile/syntax_test_textile.textile | 0 .../syntaxes/Packages/XML/Comments.tmPreferences | 0 .../Packages/XML/Miscellaneous.tmPreferences | 0 .../Packages/XML/Snippets/xml-cdata.sublime-snippet | 0 .../XML/Snippets/xml-declaration.sublime-snippet | 0 .../XML/Snippets/xml-long-tag.sublime-snippet | 0 .../Packages/XML/Snippets/xml-model.sublime-snippet | 0 .../XML/Snippets/xml-short-tag.sublime-snippet | 0 .../XML/Snippets/xml-stylesheet.sublime-snippet | 0 .../syntaxes/Packages/XML/XML.sublime-settings | 0 .../assets/syntaxes/Packages/XML/XML.sublime-syntax | 0 .../syntaxes/Packages/XML/syntax_test_xml.xml | 0 .../syntaxes/Packages/YAML/Comments.tmPreferences | 0 .../Packages/YAML/Indentation Rules.tmPreferences | 0 .../Packages/YAML/Symbol List.tmPreferences | 0 .../syntaxes/Packages/YAML/YAML.sublime-settings | 0 .../syntaxes/Packages/YAML/YAML.sublime-syntax | 0 .../assets/syntaxes/Packages/YAML/preview.yaml | 0 .../Packages/YAML/tests/syntax_test_block.yaml | 0 .../Packages/YAML/tests/syntax_test_directives.yaml | 0 .../Packages/YAML/tests/syntax_test_flow-plain.yaml | 0 .../Packages/YAML/tests/syntax_test_flow.yaml | 0 .../Packages/YAML/tests/syntax_test_general.yaml | 0 .../Packages/YAML/tests/syntax_test_properties.yaml | 0 .../Packages/YAML/tests/syntax_test_types.yaml | 0 crates/{ => bin}/docs_rs_web/build.rs | 0 crates/{ => bin}/docs_rs_web/src/build_details.rs | 0 crates/{ => bin}/docs_rs_web/src/builds.rs | 0 crates/{ => bin}/docs_rs_web/src/cache.rs | 0 crates/{ => bin}/docs_rs_web/src/config.rs | 0 crates/{ => bin}/docs_rs_web/src/crate_details.rs | 0 crates/{ => bin}/docs_rs_web/src/csp.rs | 0 crates/{ => bin}/docs_rs_web/src/error.rs | 0 .../{ => bin}/docs_rs_web/src/extractors/context.rs | 0 crates/{ => bin}/docs_rs_web/src/extractors/mod.rs | 0 crates/{ => bin}/docs_rs_web/src/extractors/path.rs | 0 .../{ => bin}/docs_rs_web/src/extractors/rustdoc.rs | 0 crates/{ => bin}/docs_rs_web/src/features.rs | 0 crates/{ => bin}/docs_rs_web/src/file.rs | 0 crates/{ => bin}/docs_rs_web/src/highlight.rs | 0 crates/{ => bin}/docs_rs_web/src/lib.rs | 0 crates/{ => bin}/docs_rs_web/src/licenses.rs | 0 crates/{ => bin}/docs_rs_web/src/markdown.rs | 0 crates/{ => bin}/docs_rs_web/src/metrics.rs | 0 crates/{ => bin}/docs_rs_web/src/page/mod.rs | 0 crates/{ => bin}/docs_rs_web/src/page/templates.rs | 0 crates/{ => bin}/docs_rs_web/src/page/web_page.rs | 0 crates/{ => bin}/docs_rs_web/src/releases.rs | 0 crates/{ => bin}/docs_rs_web/src/routes.rs | 0 crates/{ => bin}/docs_rs_web/src/rustdoc.rs | 0 crates/{ => bin}/docs_rs_web/src/sitemap.rs | 0 crates/{ => bin}/docs_rs_web/src/source.rs | 0 crates/{ => bin}/docs_rs_web/src/statics.rs | 0 crates/{ => bin}/docs_rs_web/src/status.rs | 0 crates/{ => bin}/docs_rs_web/src/utils/html.rs | 0 crates/{ => bin}/docs_rs_web/src/utils/mod.rs | 0 .../docs_rs_web/src/utils/rustc_version.rs | 0 .../docs_rs_web/static/FiraSans-LICENSE.txt | 0 .../docs_rs_web/static/FiraSans-Medium.woff | Bin .../docs_rs_web/static/FiraSans-Medium.woff2 | Bin .../docs_rs_web/static/FiraSans-Regular.woff | Bin .../docs_rs_web/static/FiraSans-Regular.woff2 | Bin .../docs_rs_web/static/SourceCodePro-It.ttf.woff | Bin .../docs_rs_web/static/SourceCodePro-It.ttf.woff2 | Bin .../docs_rs_web/static/SourceCodePro-LICENSE.md | 0 .../static/SourceCodePro-Regular.ttf.woff | Bin .../static/SourceCodePro-Regular.ttf.woff2 | Bin .../static/SourceCodePro-Semibold.ttf.woff | Bin .../static/SourceCodePro-Semibold.ttf.woff2 | Bin .../docs_rs_web/static/SourceSerif4-Bold.ttf.woff | Bin .../docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 | Bin .../docs_rs_web/static/SourceSerif4-It.ttf.woff | Bin .../docs_rs_web/static/SourceSerif4-It.ttf.woff2 | Bin .../docs_rs_web/static/SourceSerif4-LICENSE.md | 0 .../static/SourceSerif4-Regular.ttf.woff | Bin .../static/SourceSerif4-Regular.ttf.woff2 | Bin crates/{ => bin}/docs_rs_web/static/clipboard.svg | 0 .../{ => bin}/docs_rs_web/static/fa-brands-400.ttf | Bin .../docs_rs_web/static/fa-brands-400.woff2 | Bin .../{ => bin}/docs_rs_web/static/fa-regular-400.ttf | Bin .../docs_rs_web/static/fa-regular-400.woff2 | Bin .../{ => bin}/docs_rs_web/static/fa-solid-900.ttf | Bin .../{ => bin}/docs_rs_web/static/fa-solid-900.woff2 | Bin .../docs_rs_web/static/fa-v4compatibility.ttf | Bin .../docs_rs_web/static/fa-v4compatibility.woff2 | Bin crates/{ => bin}/docs_rs_web/static/favicon.ico | Bin crates/{ => bin}/docs_rs_web/static/index.js | 0 crates/{ => bin}/docs_rs_web/static/keyboard.js | 0 crates/{ => bin}/docs_rs_web/static/menu.js | 0 crates/{ => bin}/docs_rs_web/static/opensearch.xml | 0 crates/{ => bin}/docs_rs_web/static/robots.txt | 0 crates/{ => bin}/docs_rs_web/static/source.js | 0 .../docs_rs_web/static/trigger-rebuild.png | Bin .../{ => bin}/docs_rs_web/templates/about-base.html | 0 crates/{ => bin}/docs_rs_web/templates/base.html | 0 .../docs_rs_web/templates/core/Cargo.toml.example | 0 .../docs_rs_web/templates/core/about/badges.html | 0 .../docs_rs_web/templates/core/about/builds.html | 0 .../docs_rs_web/templates/core/about/download.html | 0 .../docs_rs_web/templates/core/about/index.html | 0 .../docs_rs_web/templates/core/about/metadata.html | 0 .../templates/core/about/redirections.html | 0 .../templates/core/about/rustdoc-json.html | 0 .../{ => bin}/docs_rs_web/templates/core/home.html | 0 .../docs_rs_web/templates/core/sitemap/_item.xml | 0 .../docs_rs_web/templates/core/sitemap/index.xml | 0 .../docs_rs_web/templates/crate/build_details.html | 0 .../docs_rs_web/templates/crate/builds.html | 0 .../docs_rs_web/templates/crate/details.html | 0 .../docs_rs_web/templates/crate/features.html | 0 .../docs_rs_web/templates/crate/source.html | 0 crates/{ => bin}/docs_rs_web/templates/error.html | 0 .../docs_rs_web/templates/header/global_alert.html | 0 .../templates/header/package_navigation.html | 0 .../docs_rs_web/templates/header/topbar.html | 0 .../docs_rs_web/templates/header/topbar_begin.html | 0 .../docs_rs_web/templates/header/topbar_end.html | 0 crates/{ => bin}/docs_rs_web/templates/macros.html | 0 .../docs_rs_web/templates/releases/activity.html | 0 .../docs_rs_web/templates/releases/build_queue.html | 0 .../docs_rs_web/templates/releases/feed.xml | 0 .../docs_rs_web/templates/releases/header.html | 0 .../docs_rs_web/templates/releases/releases.html | 0 .../templates/releases/search_results.html | 0 .../docs_rs_web/templates/rustdoc/body.html | 0 .../docs_rs_web/templates/rustdoc/head.html | 0 .../docs_rs_web/templates/rustdoc/platforms.html | 0 .../docs_rs_web/templates/rustdoc/releases.html | 0 .../docs_rs_web/templates/rustdoc/topbar.html | 0 .../docs_rs_web/templates/rustdoc/vendored.html | 0 .../templates/storage-change-detection.html | 0 .../docs_rs_web/templates/style/_navbar.scss | 0 .../templates/style/_rustdoc-common.scss | 0 .../docs_rs_web/templates/style/_syntax-themes.scss | 0 .../docs_rs_web/templates/style/_syntax.scss | 0 .../docs_rs_web/templates/style/_themes.scss | 0 .../docs_rs_web/templates/style/_utils.scss | 0 .../docs_rs_web/templates/style/_vars.scss | 0 .../templates/style/rustdoc-2021-12-05.scss | 0 .../templates/style/rustdoc-2025-08-20.scss | 0 .../docs_rs_web/templates/style/rustdoc.scss | 0 .../docs_rs_web/templates/style/style.scss | 0 crates/{ => bin}/docs_rs_web/templates/theme.js | 0 crates/{ => bin}/docs_rs_web/vendor/chartjs/LICENSE | 0 .../docs_rs_web/vendor/chartjs/chart.min.js | 0 .../{ => bin}/docs_rs_web/vendor/pure-css/LICENSE | 0 crates/{ => lib}/docs_rs_build_queue/Cargo.toml | 0 crates/{ => lib}/docs_rs_build_queue/src/config.rs | 0 crates/{ => lib}/docs_rs_build_queue/src/lib.rs | 0 crates/{ => lib}/docs_rs_build_queue/src/metrics.rs | 0 .../{ => lib}/docs_rs_build_queue/src/rebuilds.rs | 0 crates/{ => lib}/docs_rs_build_utils/Cargo.toml | 0 crates/{ => lib}/docs_rs_build_utils/src/config.rs | 0 crates/{ => lib}/docs_rs_build_utils/src/lib.rs | 0 crates/{ => lib}/docs_rs_build_utils/src/limits.rs | 0 .../{ => lib}/docs_rs_build_utils/src/overrides.rs | 0 crates/{ => lib}/docs_rs_cargo_metadata/Cargo.toml | 0 crates/{ => lib}/docs_rs_cargo_metadata/src/db.rs | 0 crates/{ => lib}/docs_rs_cargo_metadata/src/lib.rs | 0 crates/{ => lib}/docs_rs_context/Cargo.toml | 0 crates/{ => lib}/docs_rs_context/src/lib.rs | 0 crates/{ => lib}/docs_rs_database/Cargo.toml | 0 crates/{ => lib}/docs_rs_database/build.rs | 0 .../migrations/20231021111635_initial.down.sql | 0 .../migrations/20231021111635_initial.up.sql | 0 ...240221104457_drop_releases_build_status.down.sql | 0 ...20240221104457_drop_releases_build_status.up.sql | 0 ...40221113734_drop_releases_rustc_version.down.sql | 0 ...0240221113734_drop_releases_rustc_version.up.sql | 0 ...4302_ensure_no_buildless_releases_exist.down.sql | 0 ...114302_ensure_no_buildless_releases_exist.up.sql | 0 ...20240221124844_multi_stage_build_status.down.sql | 0 .../20240221124844_multi_stage_build_status.up.sql | 0 .../20240227040753_add_owner_kind.down.sql | 0 .../migrations/20240227040753_add_owner_kind.up.sql | 0 .../20240309082057_release_status_view.sql.down.sql | 0 .../20240309082057_release_status_view.sql.up.sql | 0 ...40311202914_release_status_materialized.down.sql | 0 ...0240311202914_release_status_materialized.up.sql | 0 ...0313103708_make_release_fields_optional.down.sql | 0 ...240313103708_make_release_fields_optional.up.sql | 0 ...240313182623_make_build_fields_optional.down.sql | 0 ...20240313182623_make_build_fields_optional.up.sql | 0 .../migrations/20240313184911_build_errors.down.sql | 0 .../migrations/20240313184911_build_errors.up.sql | 0 ...9141105_crate-version-name-field-length.down.sql | 0 ...519141105_crate-version-name-field-length.up.sql | 0 .../20240624085737_build-status-idx.down.sql | 0 .../20240624085737_build-status-idx.up.sql | 0 ...e-queue-crate-version-name-field-length.down.sql | 0 ...eue-queue-crate-version-name-field-length.up.sql | 0 .../20241018031600_documentation_size.down.sql | 0 .../20241018031600_documentation_size.up.sql | 0 ...0241018052241_builds-rustc-nightly-date.down.sql | 0 .../20241018052241_builds-rustc-nightly-date.up.sql | 0 .../20241021050229_builds-started-finished.down.sql | 0 .../20241021050229_builds-started-finished.up.sql | 0 ...41106085600_releases-rustdoc-status-idx.down.sql | 0 ...0241106085600_releases-rustdoc-status-idx.up.sql | 0 .../20241219091521_owner-avatar-longer.down.sql | 0 .../20241219091521_owner-avatar-longer.up.sql | 0 .../20251202020754_remove-file-public.down.sql | 0 .../20251202020754_remove-file-public.up.sql | 0 ...202040858_remove-cdn-invalidation-queue.down.sql | 0 ...51202040858_remove-cdn-invalidation-queue.up.sql | 0 crates/{ => lib}/docs_rs_database/src/config.rs | 0 crates/{ => lib}/docs_rs_database/src/lib.rs | 0 crates/{ => lib}/docs_rs_database/src/migrate.rs | 0 crates/{ => lib}/docs_rs_database/src/mimes.rs | 0 .../docs_rs_database/src/service_config.rs | 0 .../docs_rs_database/src/types/krate_name.rs | 0 crates/{ => lib}/docs_rs_database/src/types/mod.rs | 0 .../{ => lib}/docs_rs_database/src/types/version.rs | 0 crates/{ => lib}/docs_rs_env_vars/Cargo.toml | 0 crates/{ => lib}/docs_rs_env_vars/src/lib.rs | 0 crates/{ => lib}/docs_rs_fastly/Cargo.toml | 0 crates/{ => lib}/docs_rs_fastly/src/config.rs | 0 crates/{ => lib}/docs_rs_fastly/src/lib.rs | 0 crates/{ => lib}/docs_rs_fastly/src/metrics.rs | 0 crates/{ => lib}/docs_rs_headers/Cargo.toml | 0 .../{ => lib}/docs_rs_headers/src/canonical_url.rs | 0 crates/{ => lib}/docs_rs_headers/src/etag.rs | 0 .../{ => lib}/docs_rs_headers/src/if_none_match.rs | 0 crates/{ => lib}/docs_rs_headers/src/lib.rs | 0 .../{ => lib}/docs_rs_headers/src/surrogate_key.rs | 0 crates/{ => lib}/docs_rs_logging/Cargo.toml | 0 crates/{ => lib}/docs_rs_logging/src/lib.rs | 0 crates/{ => lib}/docs_rs_opentelemetry/Cargo.toml | 0 .../{ => lib}/docs_rs_opentelemetry/src/config.rs | 0 crates/{ => lib}/docs_rs_opentelemetry/src/lib.rs | 0 crates/{ => lib}/docs_rs_registry_api/Cargo.toml | 0 crates/{ => lib}/docs_rs_registry_api/src/config.rs | 0 crates/{ => lib}/docs_rs_registry_api/src/lib.rs | 0 .../{ => lib}/docs_rs_repository_stats/Cargo.toml | 0 .../docs_rs_repository_stats/src/config.rs | 0 .../docs_rs_repository_stats/src/github.rs | 0 .../docs_rs_repository_stats/src/gitlab.rs | 0 .../{ => lib}/docs_rs_repository_stats/src/lib.rs | 0 .../{ => lib}/docs_rs_repository_stats/src/mod.rs | 0 .../docs_rs_repository_stats/src/updater.rs | 0 crates/{ => lib}/docs_rs_storage/Cargo.toml | 0 .../docs_rs_storage/benches/compression.rs | 0 .../benches/struct.CaptureMatches.html | 0 .../{ => lib}/docs_rs_storage/src/archive_index.rs | 0 crates/{ => lib}/docs_rs_storage/src/compression.rs | 0 crates/{ => lib}/docs_rs_storage/src/config.rs | 0 crates/{ => lib}/docs_rs_storage/src/database.rs | 0 crates/{ => lib}/docs_rs_storage/src/errors.rs | 0 crates/{ => lib}/docs_rs_storage/src/file.rs | 0 crates/{ => lib}/docs_rs_storage/src/lib.rs | 0 crates/{ => lib}/docs_rs_storage/src/s3.rs | 0 crates/{ => lib}/docs_rs_storage/src/utils/mod.rs | 0 .../docs_rs_storage/src/utils/sized_buffer.rs | 0 crates/{ => lib}/docs_rs_utils/Cargo.toml | 0 crates/{ => lib}/docs_rs_utils/build.rs | 0 crates/{ => lib}/docs_rs_utils/src/lib.rs | 0 crates/{ => lib}/docs_rs_utils/src/rustc_version.rs | 0 crates/{ => lib}/docs_rs_web_utils/Cargo.toml | 0 .../{ => lib}/docs_rs_web_utils/src/escaped_uri.rs | 0 crates/{ => lib}/docs_rs_web_utils/src/lib.rs | 0 crates/{ => lib}/font-awesome-as-a-crate/.gitignore | 0 crates/{ => lib}/font-awesome-as-a-crate/Cargo.toml | 0 crates/{ => lib}/font-awesome-as-a-crate/README.md | 0 crates/{ => lib}/font-awesome-as-a-crate/build.rs | 0 .../fontawesome-free-6.2.0-desktop/LICENSE.txt | 0 .../fontawesome-free-6.2.0-desktop/VENDOR.md | 0 .../svgs/brands/42-group.svg | 0 .../svgs/brands/500px.svg | 0 .../svgs/brands/accessible-icon.svg | 0 .../svgs/brands/accusoft.svg | 0 .../svgs/brands/adn.svg | 0 .../svgs/brands/adversal.svg | 0 .../svgs/brands/affiliatetheme.svg | 0 .../svgs/brands/airbnb.svg | 0 .../svgs/brands/algolia.svg | 0 .../svgs/brands/alipay.svg | 0 .../svgs/brands/amazon-pay.svg | 0 .../svgs/brands/amazon.svg | 0 .../svgs/brands/amilia.svg | 0 .../svgs/brands/android.svg | 0 .../svgs/brands/angellist.svg | 0 .../svgs/brands/angrycreative.svg | 0 .../svgs/brands/angular.svg | 0 .../svgs/brands/app-store-ios.svg | 0 .../svgs/brands/app-store.svg | 0 .../svgs/brands/apper.svg | 0 .../svgs/brands/apple-pay.svg | 0 .../svgs/brands/apple.svg | 0 .../svgs/brands/artstation.svg | 0 .../svgs/brands/asymmetrik.svg | 0 .../svgs/brands/atlassian.svg | 0 .../svgs/brands/audible.svg | 0 .../svgs/brands/autoprefixer.svg | 0 .../svgs/brands/avianex.svg | 0 .../svgs/brands/aviato.svg | 0 .../svgs/brands/aws.svg | 0 .../svgs/brands/bandcamp.svg | 0 .../svgs/brands/battle-net.svg | 0 .../svgs/brands/behance.svg | 0 .../svgs/brands/bilibili.svg | 0 .../svgs/brands/bimobject.svg | 0 .../svgs/brands/bitbucket.svg | 0 .../svgs/brands/bitcoin.svg | 0 .../svgs/brands/bity.svg | 0 .../svgs/brands/black-tie.svg | 0 .../svgs/brands/blackberry.svg | 0 .../svgs/brands/blogger-b.svg | 0 .../svgs/brands/blogger.svg | 0 .../svgs/brands/bluetooth-b.svg | 0 .../svgs/brands/bluetooth.svg | 0 .../svgs/brands/bootstrap.svg | 0 .../svgs/brands/bots.svg | 0 .../svgs/brands/btc.svg | 0 .../svgs/brands/buffer.svg | 0 .../svgs/brands/buromobelexperte.svg | 0 .../svgs/brands/buy-n-large.svg | 0 .../svgs/brands/buysellads.svg | 0 .../svgs/brands/canadian-maple-leaf.svg | 0 .../svgs/brands/cc-amazon-pay.svg | 0 .../svgs/brands/cc-amex.svg | 0 .../svgs/brands/cc-apple-pay.svg | 0 .../svgs/brands/cc-diners-club.svg | 0 .../svgs/brands/cc-discover.svg | 0 .../svgs/brands/cc-jcb.svg | 0 .../svgs/brands/cc-mastercard.svg | 0 .../svgs/brands/cc-paypal.svg | 0 .../svgs/brands/cc-stripe.svg | 0 .../svgs/brands/cc-visa.svg | 0 .../svgs/brands/centercode.svg | 0 .../svgs/brands/centos.svg | 0 .../svgs/brands/chrome.svg | 0 .../svgs/brands/chromecast.svg | 0 .../svgs/brands/cloudflare.svg | 0 .../svgs/brands/cloudscale.svg | 0 .../svgs/brands/cloudsmith.svg | 0 .../svgs/brands/cloudversify.svg | 0 .../svgs/brands/cmplid.svg | 0 .../svgs/brands/codepen.svg | 0 .../svgs/brands/codiepie.svg | 0 .../svgs/brands/confluence.svg | 0 .../svgs/brands/connectdevelop.svg | 0 .../svgs/brands/contao.svg | 0 .../svgs/brands/cotton-bureau.svg | 0 .../svgs/brands/cpanel.svg | 0 .../svgs/brands/creative-commons-by.svg | 0 .../svgs/brands/creative-commons-nc-eu.svg | 0 .../svgs/brands/creative-commons-nc-jp.svg | 0 .../svgs/brands/creative-commons-nc.svg | 0 .../svgs/brands/creative-commons-nd.svg | 0 .../svgs/brands/creative-commons-pd-alt.svg | 0 .../svgs/brands/creative-commons-pd.svg | 0 .../svgs/brands/creative-commons-remix.svg | 0 .../svgs/brands/creative-commons-sa.svg | 0 .../svgs/brands/creative-commons-sampling-plus.svg | 0 .../svgs/brands/creative-commons-sampling.svg | 0 .../svgs/brands/creative-commons-share.svg | 0 .../svgs/brands/creative-commons-zero.svg | 0 .../svgs/brands/creative-commons.svg | 0 .../svgs/brands/critical-role.svg | 0 .../svgs/brands/css3-alt.svg | 0 .../svgs/brands/css3.svg | 0 .../svgs/brands/cuttlefish.svg | 0 .../svgs/brands/d-and-d-beyond.svg | 0 .../svgs/brands/d-and-d.svg | 0 .../svgs/brands/dailymotion.svg | 0 .../svgs/brands/dashcube.svg | 0 .../svgs/brands/deezer.svg | 0 .../svgs/brands/delicious.svg | 0 .../svgs/brands/deploydog.svg | 0 .../svgs/brands/deskpro.svg | 0 .../svgs/brands/dev.svg | 0 .../svgs/brands/deviantart.svg | 0 .../svgs/brands/dhl.svg | 0 .../svgs/brands/diaspora.svg | 0 .../svgs/brands/digg.svg | 0 .../svgs/brands/digital-ocean.svg | 0 .../svgs/brands/discord.svg | 0 .../svgs/brands/discourse.svg | 0 .../svgs/brands/dochub.svg | 0 .../svgs/brands/docker.svg | 0 .../svgs/brands/draft2digital.svg | 0 .../svgs/brands/dribbble.svg | 0 .../svgs/brands/dropbox.svg | 0 .../svgs/brands/drupal.svg | 0 .../svgs/brands/dyalog.svg | 0 .../svgs/brands/earlybirds.svg | 0 .../svgs/brands/ebay.svg | 0 .../svgs/brands/edge-legacy.svg | 0 .../svgs/brands/edge.svg | 0 .../svgs/brands/elementor.svg | 0 .../svgs/brands/ello.svg | 0 .../svgs/brands/ember.svg | 0 .../svgs/brands/empire.svg | 0 .../svgs/brands/envira.svg | 0 .../svgs/brands/erlang.svg | 0 .../svgs/brands/ethereum.svg | 0 .../svgs/brands/etsy.svg | 0 .../svgs/brands/evernote.svg | 0 .../svgs/brands/expeditedssl.svg | 0 .../svgs/brands/facebook-f.svg | 0 .../svgs/brands/facebook-messenger.svg | 0 .../svgs/brands/facebook.svg | 0 .../svgs/brands/fantasy-flight-games.svg | 0 .../svgs/brands/fedex.svg | 0 .../svgs/brands/fedora.svg | 0 .../svgs/brands/figma.svg | 0 .../svgs/brands/firefox-browser.svg | 0 .../svgs/brands/firefox.svg | 0 .../svgs/brands/first-order-alt.svg | 0 .../svgs/brands/first-order.svg | 0 .../svgs/brands/firstdraft.svg | 0 .../svgs/brands/flickr.svg | 0 .../svgs/brands/flipboard.svg | 0 .../svgs/brands/fly.svg | 0 .../svgs/brands/font-awesome.svg | 0 .../svgs/brands/fonticons-fi.svg | 0 .../svgs/brands/fonticons.svg | 0 .../svgs/brands/fort-awesome-alt.svg | 0 .../svgs/brands/fort-awesome.svg | 0 .../svgs/brands/forumbee.svg | 0 .../svgs/brands/foursquare.svg | 0 .../svgs/brands/free-code-camp.svg | 0 .../svgs/brands/freebsd.svg | 0 .../svgs/brands/fulcrum.svg | 0 .../svgs/brands/galactic-republic.svg | 0 .../svgs/brands/galactic-senate.svg | 0 .../svgs/brands/get-pocket.svg | 0 .../svgs/brands/gg-circle.svg | 0 .../svgs/brands/gg.svg | 0 .../svgs/brands/git-alt.svg | 0 .../svgs/brands/git.svg | 0 .../svgs/brands/github-alt.svg | 0 .../svgs/brands/github.svg | 0 .../svgs/brands/gitkraken.svg | 0 .../svgs/brands/gitlab.svg | 0 .../svgs/brands/gitter.svg | 0 .../svgs/brands/glide-g.svg | 0 .../svgs/brands/glide.svg | 0 .../svgs/brands/gofore.svg | 0 .../svgs/brands/golang.svg | 0 .../svgs/brands/goodreads-g.svg | 0 .../svgs/brands/goodreads.svg | 0 .../svgs/brands/google-drive.svg | 0 .../svgs/brands/google-pay.svg | 0 .../svgs/brands/google-play.svg | 0 .../svgs/brands/google-plus-g.svg | 0 .../svgs/brands/google-plus.svg | 0 .../svgs/brands/google-wallet.svg | 0 .../svgs/brands/google.svg | 0 .../svgs/brands/gratipay.svg | 0 .../svgs/brands/grav.svg | 0 .../svgs/brands/gripfire.svg | 0 .../svgs/brands/grunt.svg | 0 .../svgs/brands/guilded.svg | 0 .../svgs/brands/gulp.svg | 0 .../svgs/brands/hacker-news.svg | 0 .../svgs/brands/hackerrank.svg | 0 .../svgs/brands/hashnode.svg | 0 .../svgs/brands/hips.svg | 0 .../svgs/brands/hire-a-helper.svg | 0 .../svgs/brands/hive.svg | 0 .../svgs/brands/hooli.svg | 0 .../svgs/brands/hornbill.svg | 0 .../svgs/brands/hotjar.svg | 0 .../svgs/brands/houzz.svg | 0 .../svgs/brands/html5.svg | 0 .../svgs/brands/hubspot.svg | 0 .../svgs/brands/ideal.svg | 0 .../svgs/brands/imdb.svg | 0 .../svgs/brands/instagram.svg | 0 .../svgs/brands/instalod.svg | 0 .../svgs/brands/intercom.svg | 0 .../svgs/brands/internet-explorer.svg | 0 .../svgs/brands/invision.svg | 0 .../svgs/brands/ioxhost.svg | 0 .../svgs/brands/itch-io.svg | 0 .../svgs/brands/itunes-note.svg | 0 .../svgs/brands/itunes.svg | 0 .../svgs/brands/java.svg | 0 .../svgs/brands/jedi-order.svg | 0 .../svgs/brands/jenkins.svg | 0 .../svgs/brands/jira.svg | 0 .../svgs/brands/joget.svg | 0 .../svgs/brands/joomla.svg | 0 .../svgs/brands/js.svg | 0 .../svgs/brands/jsfiddle.svg | 0 .../svgs/brands/kaggle.svg | 0 .../svgs/brands/keybase.svg | 0 .../svgs/brands/keycdn.svg | 0 .../svgs/brands/kickstarter-k.svg | 0 .../svgs/brands/kickstarter.svg | 0 .../svgs/brands/korvue.svg | 0 .../svgs/brands/laravel.svg | 0 .../svgs/brands/lastfm.svg | 0 .../svgs/brands/leanpub.svg | 0 .../svgs/brands/less.svg | 0 .../svgs/brands/line.svg | 0 .../svgs/brands/linkedin-in.svg | 0 .../svgs/brands/linkedin.svg | 0 .../svgs/brands/linode.svg | 0 .../svgs/brands/linux.svg | 0 .../svgs/brands/lyft.svg | 0 .../svgs/brands/magento.svg | 0 .../svgs/brands/mailchimp.svg | 0 .../svgs/brands/mandalorian.svg | 0 .../svgs/brands/markdown.svg | 0 .../svgs/brands/mastodon.svg | 0 .../svgs/brands/maxcdn.svg | 0 .../svgs/brands/mdb.svg | 0 .../svgs/brands/medapps.svg | 0 .../svgs/brands/medium.svg | 0 .../svgs/brands/medrt.svg | 0 .../svgs/brands/meetup.svg | 0 .../svgs/brands/megaport.svg | 0 .../svgs/brands/mendeley.svg | 0 .../svgs/brands/meta.svg | 0 .../svgs/brands/microblog.svg | 0 .../svgs/brands/microsoft.svg | 0 .../svgs/brands/mix.svg | 0 .../svgs/brands/mixcloud.svg | 0 .../svgs/brands/mixer.svg | 0 .../svgs/brands/mizuni.svg | 0 .../svgs/brands/modx.svg | 0 .../svgs/brands/monero.svg | 0 .../svgs/brands/napster.svg | 0 .../svgs/brands/neos.svg | 0 .../svgs/brands/nfc-directional.svg | 0 .../svgs/brands/nfc-symbol.svg | 0 .../svgs/brands/nimblr.svg | 0 .../svgs/brands/node-js.svg | 0 .../svgs/brands/node.svg | 0 .../svgs/brands/npm.svg | 0 .../svgs/brands/ns8.svg | 0 .../svgs/brands/nutritionix.svg | 0 .../svgs/brands/octopus-deploy.svg | 0 .../svgs/brands/odnoklassniki.svg | 0 .../svgs/brands/old-republic.svg | 0 .../svgs/brands/opencart.svg | 0 .../svgs/brands/openid.svg | 0 .../svgs/brands/opera.svg | 0 .../svgs/brands/optin-monster.svg | 0 .../svgs/brands/orcid.svg | 0 .../svgs/brands/osi.svg | 0 .../svgs/brands/padlet.svg | 0 .../svgs/brands/page4.svg | 0 .../svgs/brands/pagelines.svg | 0 .../svgs/brands/palfed.svg | 0 .../svgs/brands/patreon.svg | 0 .../svgs/brands/paypal.svg | 0 .../svgs/brands/perbyte.svg | 0 .../svgs/brands/periscope.svg | 0 .../svgs/brands/phabricator.svg | 0 .../svgs/brands/phoenix-framework.svg | 0 .../svgs/brands/phoenix-squadron.svg | 0 .../svgs/brands/php.svg | 0 .../svgs/brands/pied-piper-alt.svg | 0 .../svgs/brands/pied-piper-hat.svg | 0 .../svgs/brands/pied-piper-pp.svg | 0 .../svgs/brands/pied-piper.svg | 0 .../svgs/brands/pinterest-p.svg | 0 .../svgs/brands/pinterest.svg | 0 .../svgs/brands/pix.svg | 0 .../svgs/brands/playstation.svg | 0 .../svgs/brands/product-hunt.svg | 0 .../svgs/brands/pushed.svg | 0 .../svgs/brands/python.svg | 0 .../svgs/brands/qq.svg | 0 .../svgs/brands/quinscape.svg | 0 .../svgs/brands/quora.svg | 0 .../svgs/brands/r-project.svg | 0 .../svgs/brands/raspberry-pi.svg | 0 .../svgs/brands/ravelry.svg | 0 .../svgs/brands/react.svg | 0 .../svgs/brands/reacteurope.svg | 0 .../svgs/brands/readme.svg | 0 .../svgs/brands/rebel.svg | 0 .../svgs/brands/red-river.svg | 0 .../svgs/brands/reddit-alien.svg | 0 .../svgs/brands/reddit.svg | 0 .../svgs/brands/redhat.svg | 0 .../svgs/brands/renren.svg | 0 .../svgs/brands/replyd.svg | 0 .../svgs/brands/researchgate.svg | 0 .../svgs/brands/resolving.svg | 0 .../svgs/brands/rev.svg | 0 .../svgs/brands/rocketchat.svg | 0 .../svgs/brands/rockrms.svg | 0 .../svgs/brands/rust.svg | 0 .../svgs/brands/safari.svg | 0 .../svgs/brands/salesforce.svg | 0 .../svgs/brands/sass.svg | 0 .../svgs/brands/schlix.svg | 0 .../svgs/brands/screenpal.svg | 0 .../svgs/brands/scribd.svg | 0 .../svgs/brands/searchengin.svg | 0 .../svgs/brands/sellcast.svg | 0 .../svgs/brands/sellsy.svg | 0 .../svgs/brands/servicestack.svg | 0 .../svgs/brands/shirtsinbulk.svg | 0 .../svgs/brands/shopify.svg | 0 .../svgs/brands/shopware.svg | 0 .../svgs/brands/simplybuilt.svg | 0 .../svgs/brands/sistrix.svg | 0 .../svgs/brands/sith.svg | 0 .../svgs/brands/sitrox.svg | 0 .../svgs/brands/sketch.svg | 0 .../svgs/brands/skyatlas.svg | 0 .../svgs/brands/skype.svg | 0 .../svgs/brands/slack.svg | 0 .../svgs/brands/slideshare.svg | 0 .../svgs/brands/snapchat.svg | 0 .../svgs/brands/soundcloud.svg | 0 .../svgs/brands/sourcetree.svg | 0 .../svgs/brands/space-awesome.svg | 0 .../svgs/brands/speakap.svg | 0 .../svgs/brands/speaker-deck.svg | 0 .../svgs/brands/spotify.svg | 0 .../svgs/brands/square-behance.svg | 0 .../svgs/brands/square-dribbble.svg | 0 .../svgs/brands/square-facebook.svg | 0 .../svgs/brands/square-font-awesome-stroke.svg | 0 .../svgs/brands/square-font-awesome.svg | 0 .../svgs/brands/square-git.svg | 0 .../svgs/brands/square-github.svg | 0 .../svgs/brands/square-gitlab.svg | 0 .../svgs/brands/square-google-plus.svg | 0 .../svgs/brands/square-hacker-news.svg | 0 .../svgs/brands/square-instagram.svg | 0 .../svgs/brands/square-js.svg | 0 .../svgs/brands/square-lastfm.svg | 0 .../svgs/brands/square-odnoklassniki.svg | 0 .../svgs/brands/square-pied-piper.svg | 0 .../svgs/brands/square-pinterest.svg | 0 .../svgs/brands/square-reddit.svg | 0 .../svgs/brands/square-snapchat.svg | 0 .../svgs/brands/square-steam.svg | 0 .../svgs/brands/square-tumblr.svg | 0 .../svgs/brands/square-twitter.svg | 0 .../svgs/brands/square-viadeo.svg | 0 .../svgs/brands/square-vimeo.svg | 0 .../svgs/brands/square-whatsapp.svg | 0 .../svgs/brands/square-xing.svg | 0 .../svgs/brands/square-youtube.svg | 0 .../svgs/brands/squarespace.svg | 0 .../svgs/brands/stack-exchange.svg | 0 .../svgs/brands/stack-overflow.svg | 0 .../svgs/brands/stackpath.svg | 0 .../svgs/brands/staylinked.svg | 0 .../svgs/brands/steam-symbol.svg | 0 .../svgs/brands/steam.svg | 0 .../svgs/brands/sticker-mule.svg | 0 .../svgs/brands/strava.svg | 0 .../svgs/brands/stripe-s.svg | 0 .../svgs/brands/stripe.svg | 0 .../svgs/brands/studiovinari.svg | 0 .../svgs/brands/stumbleupon-circle.svg | 0 .../svgs/brands/stumbleupon.svg | 0 .../svgs/brands/superpowers.svg | 0 .../svgs/brands/supple.svg | 0 .../svgs/brands/suse.svg | 0 .../svgs/brands/swift.svg | 0 .../svgs/brands/symfony.svg | 0 .../svgs/brands/teamspeak.svg | 0 .../svgs/brands/telegram.svg | 0 .../svgs/brands/tencent-weibo.svg | 0 .../svgs/brands/the-red-yeti.svg | 0 .../svgs/brands/themeco.svg | 0 .../svgs/brands/themeisle.svg | 0 .../svgs/brands/think-peaks.svg | 0 .../svgs/brands/tiktok.svg | 0 .../svgs/brands/trade-federation.svg | 0 .../svgs/brands/trello.svg | 0 .../svgs/brands/tumblr.svg | 0 .../svgs/brands/twitch.svg | 0 .../svgs/brands/twitter.svg | 0 .../svgs/brands/typo3.svg | 0 .../svgs/brands/uber.svg | 0 .../svgs/brands/ubuntu.svg | 0 .../svgs/brands/uikit.svg | 0 .../svgs/brands/umbraco.svg | 0 .../svgs/brands/uncharted.svg | 0 .../svgs/brands/uniregistry.svg | 0 .../svgs/brands/unity.svg | 0 .../svgs/brands/unsplash.svg | 0 .../svgs/brands/untappd.svg | 0 .../svgs/brands/ups.svg | 0 .../svgs/brands/usb.svg | 0 .../svgs/brands/usps.svg | 0 .../svgs/brands/ussunnah.svg | 0 .../svgs/brands/vaadin.svg | 0 .../svgs/brands/viacoin.svg | 0 .../svgs/brands/viadeo.svg | 0 .../svgs/brands/viber.svg | 0 .../svgs/brands/vimeo-v.svg | 0 .../svgs/brands/vimeo.svg | 0 .../svgs/brands/vine.svg | 0 .../svgs/brands/vk.svg | 0 .../svgs/brands/vnv.svg | 0 .../svgs/brands/vuejs.svg | 0 .../svgs/brands/watchman-monitoring.svg | 0 .../svgs/brands/waze.svg | 0 .../svgs/brands/weebly.svg | 0 .../svgs/brands/weibo.svg | 0 .../svgs/brands/weixin.svg | 0 .../svgs/brands/whatsapp.svg | 0 .../svgs/brands/whmcs.svg | 0 .../svgs/brands/wikipedia-w.svg | 0 .../svgs/brands/windows.svg | 0 .../svgs/brands/wirsindhandwerk.svg | 0 .../svgs/brands/wix.svg | 0 .../svgs/brands/wizards-of-the-coast.svg | 0 .../svgs/brands/wodu.svg | 0 .../svgs/brands/wolf-pack-battalion.svg | 0 .../svgs/brands/wordpress-simple.svg | 0 .../svgs/brands/wordpress.svg | 0 .../svgs/brands/wpbeginner.svg | 0 .../svgs/brands/wpexplorer.svg | 0 .../svgs/brands/wpforms.svg | 0 .../svgs/brands/wpressr.svg | 0 .../svgs/brands/xbox.svg | 0 .../svgs/brands/xing.svg | 0 .../svgs/brands/y-combinator.svg | 0 .../svgs/brands/yahoo.svg | 0 .../svgs/brands/yammer.svg | 0 .../svgs/brands/yandex-international.svg | 0 .../svgs/brands/yandex.svg | 0 .../svgs/brands/yarn.svg | 0 .../svgs/brands/yelp.svg | 0 .../svgs/brands/yoast.svg | 0 .../svgs/brands/youtube.svg | 0 .../svgs/brands/zhihu.svg | 0 .../svgs/regular/address-book.svg | 0 .../svgs/regular/address-card.svg | 0 .../svgs/regular/bell-slash.svg | 0 .../svgs/regular/bell.svg | 0 .../svgs/regular/bookmark.svg | 0 .../svgs/regular/building.svg | 0 .../svgs/regular/calendar-check.svg | 0 .../svgs/regular/calendar-days.svg | 0 .../svgs/regular/calendar-minus.svg | 0 .../svgs/regular/calendar-plus.svg | 0 .../svgs/regular/calendar-xmark.svg | 0 .../svgs/regular/calendar.svg | 0 .../svgs/regular/chart-bar.svg | 0 .../svgs/regular/chess-bishop.svg | 0 .../svgs/regular/chess-king.svg | 0 .../svgs/regular/chess-knight.svg | 0 .../svgs/regular/chess-pawn.svg | 0 .../svgs/regular/chess-queen.svg | 0 .../svgs/regular/chess-rook.svg | 0 .../svgs/regular/circle-check.svg | 0 .../svgs/regular/circle-dot.svg | 0 .../svgs/regular/circle-down.svg | 0 .../svgs/regular/circle-left.svg | 0 .../svgs/regular/circle-pause.svg | 0 .../svgs/regular/circle-play.svg | 0 .../svgs/regular/circle-question.svg | 0 .../svgs/regular/circle-right.svg | 0 .../svgs/regular/circle-stop.svg | 0 .../svgs/regular/circle-up.svg | 0 .../svgs/regular/circle-user.svg | 0 .../svgs/regular/circle-xmark.svg | 0 .../svgs/regular/circle.svg | 0 .../svgs/regular/clipboard.svg | 0 .../svgs/regular/clock.svg | 0 .../svgs/regular/clone.svg | 0 .../svgs/regular/closed-captioning.svg | 0 .../svgs/regular/comment-dots.svg | 0 .../svgs/regular/comment.svg | 0 .../svgs/regular/comments.svg | 0 .../svgs/regular/compass.svg | 0 .../svgs/regular/copy.svg | 0 .../svgs/regular/copyright.svg | 0 .../svgs/regular/credit-card.svg | 0 .../svgs/regular/envelope-open.svg | 0 .../svgs/regular/envelope.svg | 0 .../svgs/regular/eye-slash.svg | 0 .../svgs/regular/eye.svg | 0 .../svgs/regular/face-angry.svg | 0 .../svgs/regular/face-dizzy.svg | 0 .../svgs/regular/face-flushed.svg | 0 .../svgs/regular/face-frown-open.svg | 0 .../svgs/regular/face-frown.svg | 0 .../svgs/regular/face-grimace.svg | 0 .../svgs/regular/face-grin-beam-sweat.svg | 0 .../svgs/regular/face-grin-beam.svg | 0 .../svgs/regular/face-grin-hearts.svg | 0 .../svgs/regular/face-grin-squint-tears.svg | 0 .../svgs/regular/face-grin-squint.svg | 0 .../svgs/regular/face-grin-stars.svg | 0 .../svgs/regular/face-grin-tears.svg | 0 .../svgs/regular/face-grin-tongue-squint.svg | 0 .../svgs/regular/face-grin-tongue-wink.svg | 0 .../svgs/regular/face-grin-tongue.svg | 0 .../svgs/regular/face-grin-wide.svg | 0 .../svgs/regular/face-grin-wink.svg | 0 .../svgs/regular/face-grin.svg | 0 .../svgs/regular/face-kiss-beam.svg | 0 .../svgs/regular/face-kiss-wink-heart.svg | 0 .../svgs/regular/face-kiss.svg | 0 .../svgs/regular/face-laugh-beam.svg | 0 .../svgs/regular/face-laugh-squint.svg | 0 .../svgs/regular/face-laugh-wink.svg | 0 .../svgs/regular/face-laugh.svg | 0 .../svgs/regular/face-meh-blank.svg | 0 .../svgs/regular/face-meh.svg | 0 .../svgs/regular/face-rolling-eyes.svg | 0 .../svgs/regular/face-sad-cry.svg | 0 .../svgs/regular/face-sad-tear.svg | 0 .../svgs/regular/face-smile-beam.svg | 0 .../svgs/regular/face-smile-wink.svg | 0 .../svgs/regular/face-smile.svg | 0 .../svgs/regular/face-surprise.svg | 0 .../svgs/regular/face-tired.svg | 0 .../svgs/regular/file-audio.svg | 0 .../svgs/regular/file-code.svg | 0 .../svgs/regular/file-excel.svg | 0 .../svgs/regular/file-image.svg | 0 .../svgs/regular/file-lines.svg | 0 .../svgs/regular/file-pdf.svg | 0 .../svgs/regular/file-powerpoint.svg | 0 .../svgs/regular/file-video.svg | 0 .../svgs/regular/file-word.svg | 0 .../svgs/regular/file-zipper.svg | 0 .../svgs/regular/file.svg | 0 .../svgs/regular/flag.svg | 0 .../svgs/regular/floppy-disk.svg | 0 .../svgs/regular/folder-closed.svg | 0 .../svgs/regular/folder-open.svg | 0 .../svgs/regular/folder.svg | 0 .../svgs/regular/font-awesome.svg | 0 .../svgs/regular/futbol.svg | 0 .../svgs/regular/gem.svg | 0 .../svgs/regular/hand-back-fist.svg | 0 .../svgs/regular/hand-lizard.svg | 0 .../svgs/regular/hand-peace.svg | 0 .../svgs/regular/hand-point-down.svg | 0 .../svgs/regular/hand-point-left.svg | 0 .../svgs/regular/hand-point-right.svg | 0 .../svgs/regular/hand-point-up.svg | 0 .../svgs/regular/hand-pointer.svg | 0 .../svgs/regular/hand-scissors.svg | 0 .../svgs/regular/hand-spock.svg | 0 .../svgs/regular/hand.svg | 0 .../svgs/regular/handshake.svg | 0 .../svgs/regular/hard-drive.svg | 0 .../svgs/regular/heart.svg | 0 .../svgs/regular/hospital.svg | 0 .../svgs/regular/hourglass-half.svg | 0 .../svgs/regular/hourglass.svg | 0 .../svgs/regular/id-badge.svg | 0 .../svgs/regular/id-card.svg | 0 .../svgs/regular/image.svg | 0 .../svgs/regular/images.svg | 0 .../svgs/regular/keyboard.svg | 0 .../svgs/regular/lemon.svg | 0 .../svgs/regular/life-ring.svg | 0 .../svgs/regular/lightbulb.svg | 0 .../svgs/regular/map.svg | 0 .../svgs/regular/message.svg | 0 .../svgs/regular/money-bill-1.svg | 0 .../svgs/regular/moon.svg | 0 .../svgs/regular/newspaper.svg | 0 .../svgs/regular/note-sticky.svg | 0 .../svgs/regular/object-group.svg | 0 .../svgs/regular/object-ungroup.svg | 0 .../svgs/regular/paper-plane.svg | 0 .../svgs/regular/paste.svg | 0 .../svgs/regular/pen-to-square.svg | 0 .../svgs/regular/rectangle-list.svg | 0 .../svgs/regular/rectangle-xmark.svg | 0 .../svgs/regular/registered.svg | 0 .../svgs/regular/share-from-square.svg | 0 .../svgs/regular/snowflake.svg | 0 .../svgs/regular/square-caret-down.svg | 0 .../svgs/regular/square-caret-left.svg | 0 .../svgs/regular/square-caret-right.svg | 0 .../svgs/regular/square-caret-up.svg | 0 .../svgs/regular/square-check.svg | 0 .../svgs/regular/square-full.svg | 0 .../svgs/regular/square-minus.svg | 0 .../svgs/regular/square-plus.svg | 0 .../svgs/regular/square.svg | 0 .../svgs/regular/star-half-stroke.svg | 0 .../svgs/regular/star-half.svg | 0 .../svgs/regular/star.svg | 0 .../svgs/regular/sun.svg | 0 .../svgs/regular/thumbs-down.svg | 0 .../svgs/regular/thumbs-up.svg | 0 .../svgs/regular/trash-can.svg | 0 .../svgs/regular/user.svg | 0 .../svgs/regular/window-maximize.svg | 0 .../svgs/regular/window-minimize.svg | 0 .../svgs/regular/window-restore.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/0.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/1.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/2.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/3.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/4.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/5.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/6.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/7.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/8.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/9.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/a.svg | 0 .../svgs/solid/address-book.svg | 0 .../svgs/solid/address-card.svg | 0 .../svgs/solid/align-center.svg | 0 .../svgs/solid/align-justify.svg | 0 .../svgs/solid/align-left.svg | 0 .../svgs/solid/align-right.svg | 0 .../svgs/solid/anchor-circle-check.svg | 0 .../svgs/solid/anchor-circle-exclamation.svg | 0 .../svgs/solid/anchor-circle-xmark.svg | 0 .../svgs/solid/anchor-lock.svg | 0 .../svgs/solid/anchor.svg | 0 .../svgs/solid/angle-down.svg | 0 .../svgs/solid/angle-left.svg | 0 .../svgs/solid/angle-right.svg | 0 .../svgs/solid/angle-up.svg | 0 .../svgs/solid/angles-down.svg | 0 .../svgs/solid/angles-left.svg | 0 .../svgs/solid/angles-right.svg | 0 .../svgs/solid/angles-up.svg | 0 .../svgs/solid/ankh.svg | 0 .../svgs/solid/apple-whole.svg | 0 .../svgs/solid/archway.svg | 0 .../svgs/solid/arrow-down-1-9.svg | 0 .../svgs/solid/arrow-down-9-1.svg | 0 .../svgs/solid/arrow-down-a-z.svg | 0 .../svgs/solid/arrow-down-long.svg | 0 .../svgs/solid/arrow-down-short-wide.svg | 0 .../svgs/solid/arrow-down-up-across-line.svg | 0 .../svgs/solid/arrow-down-up-lock.svg | 0 .../svgs/solid/arrow-down-wide-short.svg | 0 .../svgs/solid/arrow-down-z-a.svg | 0 .../svgs/solid/arrow-down.svg | 0 .../svgs/solid/arrow-left-long.svg | 0 .../svgs/solid/arrow-left.svg | 0 .../svgs/solid/arrow-pointer.svg | 0 .../svgs/solid/arrow-right-arrow-left.svg | 0 .../svgs/solid/arrow-right-from-bracket.svg | 0 .../svgs/solid/arrow-right-long.svg | 0 .../svgs/solid/arrow-right-to-bracket.svg | 0 .../svgs/solid/arrow-right-to-city.svg | 0 .../svgs/solid/arrow-right.svg | 0 .../svgs/solid/arrow-rotate-left.svg | 0 .../svgs/solid/arrow-rotate-right.svg | 0 .../svgs/solid/arrow-trend-down.svg | 0 .../svgs/solid/arrow-trend-up.svg | 0 .../svgs/solid/arrow-turn-down.svg | 0 .../svgs/solid/arrow-turn-up.svg | 0 .../svgs/solid/arrow-up-1-9.svg | 0 .../svgs/solid/arrow-up-9-1.svg | 0 .../svgs/solid/arrow-up-a-z.svg | 0 .../svgs/solid/arrow-up-from-bracket.svg | 0 .../svgs/solid/arrow-up-from-ground-water.svg | 0 .../svgs/solid/arrow-up-from-water-pump.svg | 0 .../svgs/solid/arrow-up-long.svg | 0 .../svgs/solid/arrow-up-right-dots.svg | 0 .../svgs/solid/arrow-up-right-from-square.svg | 0 .../svgs/solid/arrow-up-short-wide.svg | 0 .../svgs/solid/arrow-up-wide-short.svg | 0 .../svgs/solid/arrow-up-z-a.svg | 0 .../svgs/solid/arrow-up.svg | 0 .../svgs/solid/arrows-down-to-line.svg | 0 .../svgs/solid/arrows-down-to-people.svg | 0 .../svgs/solid/arrows-left-right-to-line.svg | 0 .../svgs/solid/arrows-left-right.svg | 0 .../svgs/solid/arrows-rotate.svg | 0 .../svgs/solid/arrows-spin.svg | 0 .../svgs/solid/arrows-split-up-and-left.svg | 0 .../svgs/solid/arrows-to-circle.svg | 0 .../svgs/solid/arrows-to-dot.svg | 0 .../svgs/solid/arrows-to-eye.svg | 0 .../svgs/solid/arrows-turn-right.svg | 0 .../svgs/solid/arrows-turn-to-dots.svg | 0 .../svgs/solid/arrows-up-down-left-right.svg | 0 .../svgs/solid/arrows-up-down.svg | 0 .../svgs/solid/arrows-up-to-line.svg | 0 .../svgs/solid/asterisk.svg | 0 .../svgs/solid/at.svg | 0 .../svgs/solid/atom.svg | 0 .../svgs/solid/audio-description.svg | 0 .../svgs/solid/austral-sign.svg | 0 .../svgs/solid/award.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/b.svg | 0 .../svgs/solid/baby-carriage.svg | 0 .../svgs/solid/baby.svg | 0 .../svgs/solid/backward-fast.svg | 0 .../svgs/solid/backward-step.svg | 0 .../svgs/solid/backward.svg | 0 .../svgs/solid/bacon.svg | 0 .../svgs/solid/bacteria.svg | 0 .../svgs/solid/bacterium.svg | 0 .../svgs/solid/bag-shopping.svg | 0 .../svgs/solid/bahai.svg | 0 .../svgs/solid/baht-sign.svg | 0 .../svgs/solid/ban-smoking.svg | 0 .../svgs/solid/ban.svg | 0 .../svgs/solid/bandage.svg | 0 .../svgs/solid/barcode.svg | 0 .../svgs/solid/bars-progress.svg | 0 .../svgs/solid/bars-staggered.svg | 0 .../svgs/solid/bars.svg | 0 .../svgs/solid/baseball-bat-ball.svg | 0 .../svgs/solid/baseball.svg | 0 .../svgs/solid/basket-shopping.svg | 0 .../svgs/solid/basketball.svg | 0 .../svgs/solid/bath.svg | 0 .../svgs/solid/battery-empty.svg | 0 .../svgs/solid/battery-full.svg | 0 .../svgs/solid/battery-half.svg | 0 .../svgs/solid/battery-quarter.svg | 0 .../svgs/solid/battery-three-quarters.svg | 0 .../svgs/solid/bed-pulse.svg | 0 .../svgs/solid/bed.svg | 0 .../svgs/solid/beer-mug-empty.svg | 0 .../svgs/solid/bell-concierge.svg | 0 .../svgs/solid/bell-slash.svg | 0 .../svgs/solid/bell.svg | 0 .../svgs/solid/bezier-curve.svg | 0 .../svgs/solid/bicycle.svg | 0 .../svgs/solid/binoculars.svg | 0 .../svgs/solid/biohazard.svg | 0 .../svgs/solid/bitcoin-sign.svg | 0 .../svgs/solid/blender-phone.svg | 0 .../svgs/solid/blender.svg | 0 .../svgs/solid/blog.svg | 0 .../svgs/solid/bold.svg | 0 .../svgs/solid/bolt-lightning.svg | 0 .../svgs/solid/bolt.svg | 0 .../svgs/solid/bomb.svg | 0 .../svgs/solid/bone.svg | 0 .../svgs/solid/bong.svg | 0 .../svgs/solid/book-atlas.svg | 0 .../svgs/solid/book-bible.svg | 0 .../svgs/solid/book-bookmark.svg | 0 .../svgs/solid/book-journal-whills.svg | 0 .../svgs/solid/book-medical.svg | 0 .../svgs/solid/book-open-reader.svg | 0 .../svgs/solid/book-open.svg | 0 .../svgs/solid/book-quran.svg | 0 .../svgs/solid/book-skull.svg | 0 .../svgs/solid/book-tanakh.svg | 0 .../svgs/solid/book.svg | 0 .../svgs/solid/bookmark.svg | 0 .../svgs/solid/border-all.svg | 0 .../svgs/solid/border-none.svg | 0 .../svgs/solid/border-top-left.svg | 0 .../svgs/solid/bore-hole.svg | 0 .../svgs/solid/bottle-droplet.svg | 0 .../svgs/solid/bottle-water.svg | 0 .../svgs/solid/bowl-food.svg | 0 .../svgs/solid/bowl-rice.svg | 0 .../svgs/solid/bowling-ball.svg | 0 .../svgs/solid/box-archive.svg | 0 .../svgs/solid/box-open.svg | 0 .../svgs/solid/box-tissue.svg | 0 .../svgs/solid/box.svg | 0 .../svgs/solid/boxes-packing.svg | 0 .../svgs/solid/boxes-stacked.svg | 0 .../svgs/solid/braille.svg | 0 .../svgs/solid/brain.svg | 0 .../svgs/solid/brazilian-real-sign.svg | 0 .../svgs/solid/bread-slice.svg | 0 .../svgs/solid/bridge-circle-check.svg | 0 .../svgs/solid/bridge-circle-exclamation.svg | 0 .../svgs/solid/bridge-circle-xmark.svg | 0 .../svgs/solid/bridge-lock.svg | 0 .../svgs/solid/bridge-water.svg | 0 .../svgs/solid/bridge.svg | 0 .../svgs/solid/briefcase-medical.svg | 0 .../svgs/solid/briefcase.svg | 0 .../svgs/solid/broom-ball.svg | 0 .../svgs/solid/broom.svg | 0 .../svgs/solid/brush.svg | 0 .../svgs/solid/bucket.svg | 0 .../svgs/solid/bug-slash.svg | 0 .../svgs/solid/bug.svg | 0 .../svgs/solid/bugs.svg | 0 .../svgs/solid/building-circle-arrow-right.svg | 0 .../svgs/solid/building-circle-check.svg | 0 .../svgs/solid/building-circle-exclamation.svg | 0 .../svgs/solid/building-circle-xmark.svg | 0 .../svgs/solid/building-columns.svg | 0 .../svgs/solid/building-flag.svg | 0 .../svgs/solid/building-lock.svg | 0 .../svgs/solid/building-ngo.svg | 0 .../svgs/solid/building-shield.svg | 0 .../svgs/solid/building-un.svg | 0 .../svgs/solid/building-user.svg | 0 .../svgs/solid/building-wheat.svg | 0 .../svgs/solid/building.svg | 0 .../svgs/solid/bullhorn.svg | 0 .../svgs/solid/bullseye.svg | 0 .../svgs/solid/burger.svg | 0 .../svgs/solid/burst.svg | 0 .../svgs/solid/bus-simple.svg | 0 .../svgs/solid/bus.svg | 0 .../svgs/solid/business-time.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/c.svg | 0 .../svgs/solid/cable-car.svg | 0 .../svgs/solid/cake-candles.svg | 0 .../svgs/solid/calculator.svg | 0 .../svgs/solid/calendar-check.svg | 0 .../svgs/solid/calendar-day.svg | 0 .../svgs/solid/calendar-days.svg | 0 .../svgs/solid/calendar-minus.svg | 0 .../svgs/solid/calendar-plus.svg | 0 .../svgs/solid/calendar-week.svg | 0 .../svgs/solid/calendar-xmark.svg | 0 .../svgs/solid/calendar.svg | 0 .../svgs/solid/camera-retro.svg | 0 .../svgs/solid/camera-rotate.svg | 0 .../svgs/solid/camera.svg | 0 .../svgs/solid/campground.svg | 0 .../svgs/solid/candy-cane.svg | 0 .../svgs/solid/cannabis.svg | 0 .../svgs/solid/capsules.svg | 0 .../svgs/solid/car-battery.svg | 0 .../svgs/solid/car-burst.svg | 0 .../svgs/solid/car-on.svg | 0 .../svgs/solid/car-rear.svg | 0 .../svgs/solid/car-side.svg | 0 .../svgs/solid/car-tunnel.svg | 0 .../svgs/solid/car.svg | 0 .../svgs/solid/caravan.svg | 0 .../svgs/solid/caret-down.svg | 0 .../svgs/solid/caret-left.svg | 0 .../svgs/solid/caret-right.svg | 0 .../svgs/solid/caret-up.svg | 0 .../svgs/solid/carrot.svg | 0 .../svgs/solid/cart-arrow-down.svg | 0 .../svgs/solid/cart-flatbed-suitcase.svg | 0 .../svgs/solid/cart-flatbed.svg | 0 .../svgs/solid/cart-plus.svg | 0 .../svgs/solid/cart-shopping.svg | 0 .../svgs/solid/cash-register.svg | 0 .../svgs/solid/cat.svg | 0 .../svgs/solid/cedi-sign.svg | 0 .../svgs/solid/cent-sign.svg | 0 .../svgs/solid/certificate.svg | 0 .../svgs/solid/chair.svg | 0 .../svgs/solid/chalkboard-user.svg | 0 .../svgs/solid/chalkboard.svg | 0 .../svgs/solid/champagne-glasses.svg | 0 .../svgs/solid/charging-station.svg | 0 .../svgs/solid/chart-area.svg | 0 .../svgs/solid/chart-bar.svg | 0 .../svgs/solid/chart-column.svg | 0 .../svgs/solid/chart-gantt.svg | 0 .../svgs/solid/chart-line.svg | 0 .../svgs/solid/chart-pie.svg | 0 .../svgs/solid/chart-simple.svg | 0 .../svgs/solid/check-double.svg | 0 .../svgs/solid/check-to-slot.svg | 0 .../svgs/solid/check.svg | 0 .../svgs/solid/cheese.svg | 0 .../svgs/solid/chess-bishop.svg | 0 .../svgs/solid/chess-board.svg | 0 .../svgs/solid/chess-king.svg | 0 .../svgs/solid/chess-knight.svg | 0 .../svgs/solid/chess-pawn.svg | 0 .../svgs/solid/chess-queen.svg | 0 .../svgs/solid/chess-rook.svg | 0 .../svgs/solid/chess.svg | 0 .../svgs/solid/chevron-down.svg | 0 .../svgs/solid/chevron-left.svg | 0 .../svgs/solid/chevron-right.svg | 0 .../svgs/solid/chevron-up.svg | 0 .../svgs/solid/child-dress.svg | 0 .../svgs/solid/child-reaching.svg | 0 .../svgs/solid/child-rifle.svg | 0 .../svgs/solid/child.svg | 0 .../svgs/solid/children.svg | 0 .../svgs/solid/church.svg | 0 .../svgs/solid/circle-arrow-down.svg | 0 .../svgs/solid/circle-arrow-left.svg | 0 .../svgs/solid/circle-arrow-right.svg | 0 .../svgs/solid/circle-arrow-up.svg | 0 .../svgs/solid/circle-check.svg | 0 .../svgs/solid/circle-chevron-down.svg | 0 .../svgs/solid/circle-chevron-left.svg | 0 .../svgs/solid/circle-chevron-right.svg | 0 .../svgs/solid/circle-chevron-up.svg | 0 .../svgs/solid/circle-dollar-to-slot.svg | 0 .../svgs/solid/circle-dot.svg | 0 .../svgs/solid/circle-down.svg | 0 .../svgs/solid/circle-exclamation.svg | 0 .../svgs/solid/circle-h.svg | 0 .../svgs/solid/circle-half-stroke.svg | 0 .../svgs/solid/circle-info.svg | 0 .../svgs/solid/circle-left.svg | 0 .../svgs/solid/circle-minus.svg | 0 .../svgs/solid/circle-nodes.svg | 0 .../svgs/solid/circle-notch.svg | 0 .../svgs/solid/circle-pause.svg | 0 .../svgs/solid/circle-play.svg | 0 .../svgs/solid/circle-plus.svg | 0 .../svgs/solid/circle-question.svg | 0 .../svgs/solid/circle-radiation.svg | 0 .../svgs/solid/circle-right.svg | 0 .../svgs/solid/circle-stop.svg | 0 .../svgs/solid/circle-up.svg | 0 .../svgs/solid/circle-user.svg | 0 .../svgs/solid/circle-xmark.svg | 0 .../svgs/solid/circle.svg | 0 .../svgs/solid/city.svg | 0 .../svgs/solid/clapperboard.svg | 0 .../svgs/solid/clipboard-check.svg | 0 .../svgs/solid/clipboard-list.svg | 0 .../svgs/solid/clipboard-question.svg | 0 .../svgs/solid/clipboard-user.svg | 0 .../svgs/solid/clipboard.svg | 0 .../svgs/solid/clock-rotate-left.svg | 0 .../svgs/solid/clock.svg | 0 .../svgs/solid/clone.svg | 0 .../svgs/solid/closed-captioning.svg | 0 .../svgs/solid/cloud-arrow-down.svg | 0 .../svgs/solid/cloud-arrow-up.svg | 0 .../svgs/solid/cloud-bolt.svg | 0 .../svgs/solid/cloud-meatball.svg | 0 .../svgs/solid/cloud-moon-rain.svg | 0 .../svgs/solid/cloud-moon.svg | 0 .../svgs/solid/cloud-rain.svg | 0 .../svgs/solid/cloud-showers-heavy.svg | 0 .../svgs/solid/cloud-showers-water.svg | 0 .../svgs/solid/cloud-sun-rain.svg | 0 .../svgs/solid/cloud-sun.svg | 0 .../svgs/solid/cloud.svg | 0 .../svgs/solid/clover.svg | 0 .../svgs/solid/code-branch.svg | 0 .../svgs/solid/code-commit.svg | 0 .../svgs/solid/code-compare.svg | 0 .../svgs/solid/code-fork.svg | 0 .../svgs/solid/code-merge.svg | 0 .../svgs/solid/code-pull-request.svg | 0 .../svgs/solid/code.svg | 0 .../svgs/solid/coins.svg | 0 .../svgs/solid/colon-sign.svg | 0 .../svgs/solid/comment-dollar.svg | 0 .../svgs/solid/comment-dots.svg | 0 .../svgs/solid/comment-medical.svg | 0 .../svgs/solid/comment-slash.svg | 0 .../svgs/solid/comment-sms.svg | 0 .../svgs/solid/comment.svg | 0 .../svgs/solid/comments-dollar.svg | 0 .../svgs/solid/comments.svg | 0 .../svgs/solid/compact-disc.svg | 0 .../svgs/solid/compass-drafting.svg | 0 .../svgs/solid/compass.svg | 0 .../svgs/solid/compress.svg | 0 .../svgs/solid/computer-mouse.svg | 0 .../svgs/solid/computer.svg | 0 .../svgs/solid/cookie-bite.svg | 0 .../svgs/solid/cookie.svg | 0 .../svgs/solid/copy.svg | 0 .../svgs/solid/copyright.svg | 0 .../svgs/solid/couch.svg | 0 .../svgs/solid/cow.svg | 0 .../svgs/solid/credit-card.svg | 0 .../svgs/solid/crop-simple.svg | 0 .../svgs/solid/crop.svg | 0 .../svgs/solid/cross.svg | 0 .../svgs/solid/crosshairs.svg | 0 .../svgs/solid/crow.svg | 0 .../svgs/solid/crown.svg | 0 .../svgs/solid/crutch.svg | 0 .../svgs/solid/cruzeiro-sign.svg | 0 .../svgs/solid/cube.svg | 0 .../svgs/solid/cubes-stacked.svg | 0 .../svgs/solid/cubes.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/d.svg | 0 .../svgs/solid/database.svg | 0 .../svgs/solid/delete-left.svg | 0 .../svgs/solid/democrat.svg | 0 .../svgs/solid/desktop.svg | 0 .../svgs/solid/dharmachakra.svg | 0 .../svgs/solid/diagram-next.svg | 0 .../svgs/solid/diagram-predecessor.svg | 0 .../svgs/solid/diagram-project.svg | 0 .../svgs/solid/diagram-successor.svg | 0 .../svgs/solid/diamond-turn-right.svg | 0 .../svgs/solid/diamond.svg | 0 .../svgs/solid/dice-d20.svg | 0 .../svgs/solid/dice-d6.svg | 0 .../svgs/solid/dice-five.svg | 0 .../svgs/solid/dice-four.svg | 0 .../svgs/solid/dice-one.svg | 0 .../svgs/solid/dice-six.svg | 0 .../svgs/solid/dice-three.svg | 0 .../svgs/solid/dice-two.svg | 0 .../svgs/solid/dice.svg | 0 .../svgs/solid/disease.svg | 0 .../svgs/solid/display.svg | 0 .../svgs/solid/divide.svg | 0 .../svgs/solid/dna.svg | 0 .../svgs/solid/dog.svg | 0 .../svgs/solid/dollar-sign.svg | 0 .../svgs/solid/dolly.svg | 0 .../svgs/solid/dong-sign.svg | 0 .../svgs/solid/door-closed.svg | 0 .../svgs/solid/door-open.svg | 0 .../svgs/solid/dove.svg | 0 .../svgs/solid/down-left-and-up-right-to-center.svg | 0 .../svgs/solid/down-long.svg | 0 .../svgs/solid/download.svg | 0 .../svgs/solid/dragon.svg | 0 .../svgs/solid/draw-polygon.svg | 0 .../svgs/solid/droplet-slash.svg | 0 .../svgs/solid/droplet.svg | 0 .../svgs/solid/drum-steelpan.svg | 0 .../svgs/solid/drum.svg | 0 .../svgs/solid/drumstick-bite.svg | 0 .../svgs/solid/dumbbell.svg | 0 .../svgs/solid/dumpster-fire.svg | 0 .../svgs/solid/dumpster.svg | 0 .../svgs/solid/dungeon.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/e.svg | 0 .../svgs/solid/ear-deaf.svg | 0 .../svgs/solid/ear-listen.svg | 0 .../svgs/solid/earth-africa.svg | 0 .../svgs/solid/earth-americas.svg | 0 .../svgs/solid/earth-asia.svg | 0 .../svgs/solid/earth-europe.svg | 0 .../svgs/solid/earth-oceania.svg | 0 .../svgs/solid/egg.svg | 0 .../svgs/solid/eject.svg | 0 .../svgs/solid/elevator.svg | 0 .../svgs/solid/ellipsis-vertical.svg | 0 .../svgs/solid/ellipsis.svg | 0 .../svgs/solid/envelope-circle-check.svg | 0 .../svgs/solid/envelope-open-text.svg | 0 .../svgs/solid/envelope-open.svg | 0 .../svgs/solid/envelope.svg | 0 .../svgs/solid/envelopes-bulk.svg | 0 .../svgs/solid/equals.svg | 0 .../svgs/solid/eraser.svg | 0 .../svgs/solid/ethernet.svg | 0 .../svgs/solid/euro-sign.svg | 0 .../svgs/solid/exclamation.svg | 0 .../svgs/solid/expand.svg | 0 .../svgs/solid/explosion.svg | 0 .../svgs/solid/eye-dropper.svg | 0 .../svgs/solid/eye-low-vision.svg | 0 .../svgs/solid/eye-slash.svg | 0 .../svgs/solid/eye.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/f.svg | 0 .../svgs/solid/face-angry.svg | 0 .../svgs/solid/face-dizzy.svg | 0 .../svgs/solid/face-flushed.svg | 0 .../svgs/solid/face-frown-open.svg | 0 .../svgs/solid/face-frown.svg | 0 .../svgs/solid/face-grimace.svg | 0 .../svgs/solid/face-grin-beam-sweat.svg | 0 .../svgs/solid/face-grin-beam.svg | 0 .../svgs/solid/face-grin-hearts.svg | 0 .../svgs/solid/face-grin-squint-tears.svg | 0 .../svgs/solid/face-grin-squint.svg | 0 .../svgs/solid/face-grin-stars.svg | 0 .../svgs/solid/face-grin-tears.svg | 0 .../svgs/solid/face-grin-tongue-squint.svg | 0 .../svgs/solid/face-grin-tongue-wink.svg | 0 .../svgs/solid/face-grin-tongue.svg | 0 .../svgs/solid/face-grin-wide.svg | 0 .../svgs/solid/face-grin-wink.svg | 0 .../svgs/solid/face-grin.svg | 0 .../svgs/solid/face-kiss-beam.svg | 0 .../svgs/solid/face-kiss-wink-heart.svg | 0 .../svgs/solid/face-kiss.svg | 0 .../svgs/solid/face-laugh-beam.svg | 0 .../svgs/solid/face-laugh-squint.svg | 0 .../svgs/solid/face-laugh-wink.svg | 0 .../svgs/solid/face-laugh.svg | 0 .../svgs/solid/face-meh-blank.svg | 0 .../svgs/solid/face-meh.svg | 0 .../svgs/solid/face-rolling-eyes.svg | 0 .../svgs/solid/face-sad-cry.svg | 0 .../svgs/solid/face-sad-tear.svg | 0 .../svgs/solid/face-smile-beam.svg | 0 .../svgs/solid/face-smile-wink.svg | 0 .../svgs/solid/face-smile.svg | 0 .../svgs/solid/face-surprise.svg | 0 .../svgs/solid/face-tired.svg | 0 .../svgs/solid/fan.svg | 0 .../svgs/solid/faucet-drip.svg | 0 .../svgs/solid/faucet.svg | 0 .../svgs/solid/fax.svg | 0 .../svgs/solid/feather-pointed.svg | 0 .../svgs/solid/feather.svg | 0 .../svgs/solid/ferry.svg | 0 .../svgs/solid/file-arrow-down.svg | 0 .../svgs/solid/file-arrow-up.svg | 0 .../svgs/solid/file-audio.svg | 0 .../svgs/solid/file-circle-check.svg | 0 .../svgs/solid/file-circle-exclamation.svg | 0 .../svgs/solid/file-circle-minus.svg | 0 .../svgs/solid/file-circle-plus.svg | 0 .../svgs/solid/file-circle-question.svg | 0 .../svgs/solid/file-circle-xmark.svg | 0 .../svgs/solid/file-code.svg | 0 .../svgs/solid/file-contract.svg | 0 .../svgs/solid/file-csv.svg | 0 .../svgs/solid/file-excel.svg | 0 .../svgs/solid/file-export.svg | 0 .../svgs/solid/file-image.svg | 0 .../svgs/solid/file-import.svg | 0 .../svgs/solid/file-invoice-dollar.svg | 0 .../svgs/solid/file-invoice.svg | 0 .../svgs/solid/file-lines.svg | 0 .../svgs/solid/file-medical.svg | 0 .../svgs/solid/file-pdf.svg | 0 .../svgs/solid/file-pen.svg | 0 .../svgs/solid/file-powerpoint.svg | 0 .../svgs/solid/file-prescription.svg | 0 .../svgs/solid/file-shield.svg | 0 .../svgs/solid/file-signature.svg | 0 .../svgs/solid/file-video.svg | 0 .../svgs/solid/file-waveform.svg | 0 .../svgs/solid/file-word.svg | 0 .../svgs/solid/file-zipper.svg | 0 .../svgs/solid/file.svg | 0 .../svgs/solid/fill-drip.svg | 0 .../svgs/solid/fill.svg | 0 .../svgs/solid/film.svg | 0 .../svgs/solid/filter-circle-dollar.svg | 0 .../svgs/solid/filter-circle-xmark.svg | 0 .../svgs/solid/filter.svg | 0 .../svgs/solid/fingerprint.svg | 0 .../svgs/solid/fire-burner.svg | 0 .../svgs/solid/fire-extinguisher.svg | 0 .../svgs/solid/fire-flame-curved.svg | 0 .../svgs/solid/fire-flame-simple.svg | 0 .../svgs/solid/fire.svg | 0 .../svgs/solid/fish-fins.svg | 0 .../svgs/solid/fish.svg | 0 .../svgs/solid/flag-checkered.svg | 0 .../svgs/solid/flag-usa.svg | 0 .../svgs/solid/flag.svg | 0 .../svgs/solid/flask-vial.svg | 0 .../svgs/solid/flask.svg | 0 .../svgs/solid/floppy-disk.svg | 0 .../svgs/solid/florin-sign.svg | 0 .../svgs/solid/folder-closed.svg | 0 .../svgs/solid/folder-minus.svg | 0 .../svgs/solid/folder-open.svg | 0 .../svgs/solid/folder-plus.svg | 0 .../svgs/solid/folder-tree.svg | 0 .../svgs/solid/folder.svg | 0 .../svgs/solid/font-awesome.svg | 0 .../svgs/solid/font.svg | 0 .../svgs/solid/football.svg | 0 .../svgs/solid/forward-fast.svg | 0 .../svgs/solid/forward-step.svg | 0 .../svgs/solid/forward.svg | 0 .../svgs/solid/franc-sign.svg | 0 .../svgs/solid/frog.svg | 0 .../svgs/solid/futbol.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/g.svg | 0 .../svgs/solid/gamepad.svg | 0 .../svgs/solid/gas-pump.svg | 0 .../svgs/solid/gauge-high.svg | 0 .../svgs/solid/gauge-simple-high.svg | 0 .../svgs/solid/gauge-simple.svg | 0 .../svgs/solid/gauge.svg | 0 .../svgs/solid/gavel.svg | 0 .../svgs/solid/gear.svg | 0 .../svgs/solid/gears.svg | 0 .../svgs/solid/gem.svg | 0 .../svgs/solid/genderless.svg | 0 .../svgs/solid/ghost.svg | 0 .../svgs/solid/gift.svg | 0 .../svgs/solid/gifts.svg | 0 .../svgs/solid/glass-water-droplet.svg | 0 .../svgs/solid/glass-water.svg | 0 .../svgs/solid/glasses.svg | 0 .../svgs/solid/globe.svg | 0 .../svgs/solid/golf-ball-tee.svg | 0 .../svgs/solid/gopuram.svg | 0 .../svgs/solid/graduation-cap.svg | 0 .../svgs/solid/greater-than-equal.svg | 0 .../svgs/solid/greater-than.svg | 0 .../svgs/solid/grip-lines-vertical.svg | 0 .../svgs/solid/grip-lines.svg | 0 .../svgs/solid/grip-vertical.svg | 0 .../svgs/solid/grip.svg | 0 .../svgs/solid/group-arrows-rotate.svg | 0 .../svgs/solid/guarani-sign.svg | 0 .../svgs/solid/guitar.svg | 0 .../svgs/solid/gun.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/h.svg | 0 .../svgs/solid/hammer.svg | 0 .../svgs/solid/hamsa.svg | 0 .../svgs/solid/hand-back-fist.svg | 0 .../svgs/solid/hand-dots.svg | 0 .../svgs/solid/hand-fist.svg | 0 .../svgs/solid/hand-holding-dollar.svg | 0 .../svgs/solid/hand-holding-droplet.svg | 0 .../svgs/solid/hand-holding-hand.svg | 0 .../svgs/solid/hand-holding-heart.svg | 0 .../svgs/solid/hand-holding-medical.svg | 0 .../svgs/solid/hand-holding.svg | 0 .../svgs/solid/hand-lizard.svg | 0 .../svgs/solid/hand-middle-finger.svg | 0 .../svgs/solid/hand-peace.svg | 0 .../svgs/solid/hand-point-down.svg | 0 .../svgs/solid/hand-point-left.svg | 0 .../svgs/solid/hand-point-right.svg | 0 .../svgs/solid/hand-point-up.svg | 0 .../svgs/solid/hand-pointer.svg | 0 .../svgs/solid/hand-scissors.svg | 0 .../svgs/solid/hand-sparkles.svg | 0 .../svgs/solid/hand-spock.svg | 0 .../svgs/solid/hand.svg | 0 .../svgs/solid/handcuffs.svg | 0 .../svgs/solid/hands-asl-interpreting.svg | 0 .../svgs/solid/hands-bound.svg | 0 .../svgs/solid/hands-bubbles.svg | 0 .../svgs/solid/hands-clapping.svg | 0 .../svgs/solid/hands-holding-child.svg | 0 .../svgs/solid/hands-holding-circle.svg | 0 .../svgs/solid/hands-holding.svg | 0 .../svgs/solid/hands-praying.svg | 0 .../svgs/solid/hands.svg | 0 .../svgs/solid/handshake-angle.svg | 0 .../svgs/solid/handshake-simple-slash.svg | 0 .../svgs/solid/handshake-simple.svg | 0 .../svgs/solid/handshake-slash.svg | 0 .../svgs/solid/handshake.svg | 0 .../svgs/solid/hanukiah.svg | 0 .../svgs/solid/hard-drive.svg | 0 .../svgs/solid/hashtag.svg | 0 .../svgs/solid/hat-cowboy-side.svg | 0 .../svgs/solid/hat-cowboy.svg | 0 .../svgs/solid/hat-wizard.svg | 0 .../svgs/solid/head-side-cough-slash.svg | 0 .../svgs/solid/head-side-cough.svg | 0 .../svgs/solid/head-side-mask.svg | 0 .../svgs/solid/head-side-virus.svg | 0 .../svgs/solid/heading.svg | 0 .../svgs/solid/headphones-simple.svg | 0 .../svgs/solid/headphones.svg | 0 .../svgs/solid/headset.svg | 0 .../svgs/solid/heart-circle-bolt.svg | 0 .../svgs/solid/heart-circle-check.svg | 0 .../svgs/solid/heart-circle-exclamation.svg | 0 .../svgs/solid/heart-circle-minus.svg | 0 .../svgs/solid/heart-circle-plus.svg | 0 .../svgs/solid/heart-circle-xmark.svg | 0 .../svgs/solid/heart-crack.svg | 0 .../svgs/solid/heart-pulse.svg | 0 .../svgs/solid/heart.svg | 0 .../svgs/solid/helicopter-symbol.svg | 0 .../svgs/solid/helicopter.svg | 0 .../svgs/solid/helmet-safety.svg | 0 .../svgs/solid/helmet-un.svg | 0 .../svgs/solid/highlighter.svg | 0 .../svgs/solid/hill-avalanche.svg | 0 .../svgs/solid/hill-rockslide.svg | 0 .../svgs/solid/hippo.svg | 0 .../svgs/solid/hockey-puck.svg | 0 .../svgs/solid/holly-berry.svg | 0 .../svgs/solid/horse-head.svg | 0 .../svgs/solid/horse.svg | 0 .../svgs/solid/hospital-user.svg | 0 .../svgs/solid/hospital.svg | 0 .../svgs/solid/hot-tub-person.svg | 0 .../svgs/solid/hotdog.svg | 0 .../svgs/solid/hotel.svg | 0 .../svgs/solid/hourglass-end.svg | 0 .../svgs/solid/hourglass-half.svg | 0 .../svgs/solid/hourglass-start.svg | 0 .../svgs/solid/hourglass.svg | 0 .../svgs/solid/house-chimney-crack.svg | 0 .../svgs/solid/house-chimney-medical.svg | 0 .../svgs/solid/house-chimney-user.svg | 0 .../svgs/solid/house-chimney-window.svg | 0 .../svgs/solid/house-chimney.svg | 0 .../svgs/solid/house-circle-check.svg | 0 .../svgs/solid/house-circle-exclamation.svg | 0 .../svgs/solid/house-circle-xmark.svg | 0 .../svgs/solid/house-crack.svg | 0 .../svgs/solid/house-fire.svg | 0 .../svgs/solid/house-flag.svg | 0 .../solid/house-flood-water-circle-arrow-right.svg | 0 .../svgs/solid/house-flood-water.svg | 0 .../svgs/solid/house-laptop.svg | 0 .../svgs/solid/house-lock.svg | 0 .../svgs/solid/house-medical-circle-check.svg | 0 .../svgs/solid/house-medical-circle-exclamation.svg | 0 .../svgs/solid/house-medical-circle-xmark.svg | 0 .../svgs/solid/house-medical-flag.svg | 0 .../svgs/solid/house-medical.svg | 0 .../svgs/solid/house-signal.svg | 0 .../svgs/solid/house-tsunami.svg | 0 .../svgs/solid/house-user.svg | 0 .../svgs/solid/house.svg | 0 .../svgs/solid/hryvnia-sign.svg | 0 .../svgs/solid/hurricane.svg | 0 .../svgs/solid/i-cursor.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/i.svg | 0 .../svgs/solid/ice-cream.svg | 0 .../svgs/solid/icicles.svg | 0 .../svgs/solid/icons.svg | 0 .../svgs/solid/id-badge.svg | 0 .../svgs/solid/id-card-clip.svg | 0 .../svgs/solid/id-card.svg | 0 .../svgs/solid/igloo.svg | 0 .../svgs/solid/image-portrait.svg | 0 .../svgs/solid/image.svg | 0 .../svgs/solid/images.svg | 0 .../svgs/solid/inbox.svg | 0 .../svgs/solid/indent.svg | 0 .../svgs/solid/indian-rupee-sign.svg | 0 .../svgs/solid/industry.svg | 0 .../svgs/solid/infinity.svg | 0 .../svgs/solid/info.svg | 0 .../svgs/solid/italic.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/j.svg | 0 .../svgs/solid/jar-wheat.svg | 0 .../svgs/solid/jar.svg | 0 .../svgs/solid/jedi.svg | 0 .../svgs/solid/jet-fighter-up.svg | 0 .../svgs/solid/jet-fighter.svg | 0 .../svgs/solid/joint.svg | 0 .../svgs/solid/jug-detergent.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/k.svg | 0 .../svgs/solid/kaaba.svg | 0 .../svgs/solid/key.svg | 0 .../svgs/solid/keyboard.svg | 0 .../svgs/solid/khanda.svg | 0 .../svgs/solid/kip-sign.svg | 0 .../svgs/solid/kit-medical.svg | 0 .../svgs/solid/kitchen-set.svg | 0 .../svgs/solid/kiwi-bird.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/l.svg | 0 .../svgs/solid/land-mine-on.svg | 0 .../svgs/solid/landmark-dome.svg | 0 .../svgs/solid/landmark-flag.svg | 0 .../svgs/solid/landmark.svg | 0 .../svgs/solid/language.svg | 0 .../svgs/solid/laptop-code.svg | 0 .../svgs/solid/laptop-file.svg | 0 .../svgs/solid/laptop-medical.svg | 0 .../svgs/solid/laptop.svg | 0 .../svgs/solid/lari-sign.svg | 0 .../svgs/solid/layer-group.svg | 0 .../svgs/solid/leaf.svg | 0 .../svgs/solid/left-long.svg | 0 .../svgs/solid/left-right.svg | 0 .../svgs/solid/lemon.svg | 0 .../svgs/solid/less-than-equal.svg | 0 .../svgs/solid/less-than.svg | 0 .../svgs/solid/life-ring.svg | 0 .../svgs/solid/lightbulb.svg | 0 .../svgs/solid/lines-leaning.svg | 0 .../svgs/solid/link-slash.svg | 0 .../svgs/solid/link.svg | 0 .../svgs/solid/lira-sign.svg | 0 .../svgs/solid/list-check.svg | 0 .../svgs/solid/list-ol.svg | 0 .../svgs/solid/list-ul.svg | 0 .../svgs/solid/list.svg | 0 .../svgs/solid/litecoin-sign.svg | 0 .../svgs/solid/location-arrow.svg | 0 .../svgs/solid/location-crosshairs.svg | 0 .../svgs/solid/location-dot.svg | 0 .../svgs/solid/location-pin-lock.svg | 0 .../svgs/solid/location-pin.svg | 0 .../svgs/solid/lock-open.svg | 0 .../svgs/solid/lock.svg | 0 .../svgs/solid/locust.svg | 0 .../svgs/solid/lungs-virus.svg | 0 .../svgs/solid/lungs.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/m.svg | 0 .../svgs/solid/magnet.svg | 0 .../svgs/solid/magnifying-glass-arrow-right.svg | 0 .../svgs/solid/magnifying-glass-chart.svg | 0 .../svgs/solid/magnifying-glass-dollar.svg | 0 .../svgs/solid/magnifying-glass-location.svg | 0 .../svgs/solid/magnifying-glass-minus.svg | 0 .../svgs/solid/magnifying-glass-plus.svg | 0 .../svgs/solid/magnifying-glass.svg | 0 .../svgs/solid/manat-sign.svg | 0 .../svgs/solid/map-location-dot.svg | 0 .../svgs/solid/map-location.svg | 0 .../svgs/solid/map-pin.svg | 0 .../svgs/solid/map.svg | 0 .../svgs/solid/marker.svg | 0 .../svgs/solid/mars-and-venus-burst.svg | 0 .../svgs/solid/mars-and-venus.svg | 0 .../svgs/solid/mars-double.svg | 0 .../svgs/solid/mars-stroke-right.svg | 0 .../svgs/solid/mars-stroke-up.svg | 0 .../svgs/solid/mars-stroke.svg | 0 .../svgs/solid/mars.svg | 0 .../svgs/solid/martini-glass-citrus.svg | 0 .../svgs/solid/martini-glass-empty.svg | 0 .../svgs/solid/martini-glass.svg | 0 .../svgs/solid/mask-face.svg | 0 .../svgs/solid/mask-ventilator.svg | 0 .../svgs/solid/mask.svg | 0 .../svgs/solid/masks-theater.svg | 0 .../svgs/solid/mattress-pillow.svg | 0 .../svgs/solid/maximize.svg | 0 .../svgs/solid/medal.svg | 0 .../svgs/solid/memory.svg | 0 .../svgs/solid/menorah.svg | 0 .../svgs/solid/mercury.svg | 0 .../svgs/solid/message.svg | 0 .../svgs/solid/meteor.svg | 0 .../svgs/solid/microchip.svg | 0 .../svgs/solid/microphone-lines-slash.svg | 0 .../svgs/solid/microphone-lines.svg | 0 .../svgs/solid/microphone-slash.svg | 0 .../svgs/solid/microphone.svg | 0 .../svgs/solid/microscope.svg | 0 .../svgs/solid/mill-sign.svg | 0 .../svgs/solid/minimize.svg | 0 .../svgs/solid/minus.svg | 0 .../svgs/solid/mitten.svg | 0 .../svgs/solid/mobile-button.svg | 0 .../svgs/solid/mobile-retro.svg | 0 .../svgs/solid/mobile-screen-button.svg | 0 .../svgs/solid/mobile-screen.svg | 0 .../svgs/solid/mobile.svg | 0 .../svgs/solid/money-bill-1-wave.svg | 0 .../svgs/solid/money-bill-1.svg | 0 .../svgs/solid/money-bill-transfer.svg | 0 .../svgs/solid/money-bill-trend-up.svg | 0 .../svgs/solid/money-bill-wave.svg | 0 .../svgs/solid/money-bill-wheat.svg | 0 .../svgs/solid/money-bill.svg | 0 .../svgs/solid/money-bills.svg | 0 .../svgs/solid/money-check-dollar.svg | 0 .../svgs/solid/money-check.svg | 0 .../svgs/solid/monument.svg | 0 .../svgs/solid/moon.svg | 0 .../svgs/solid/mortar-pestle.svg | 0 .../svgs/solid/mosque.svg | 0 .../svgs/solid/mosquito-net.svg | 0 .../svgs/solid/mosquito.svg | 0 .../svgs/solid/motorcycle.svg | 0 .../svgs/solid/mound.svg | 0 .../svgs/solid/mountain-city.svg | 0 .../svgs/solid/mountain-sun.svg | 0 .../svgs/solid/mountain.svg | 0 .../svgs/solid/mug-hot.svg | 0 .../svgs/solid/mug-saucer.svg | 0 .../svgs/solid/music.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/n.svg | 0 .../svgs/solid/naira-sign.svg | 0 .../svgs/solid/network-wired.svg | 0 .../svgs/solid/neuter.svg | 0 .../svgs/solid/newspaper.svg | 0 .../svgs/solid/not-equal.svg | 0 .../svgs/solid/note-sticky.svg | 0 .../svgs/solid/notes-medical.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/o.svg | 0 .../svgs/solid/object-group.svg | 0 .../svgs/solid/object-ungroup.svg | 0 .../svgs/solid/oil-can.svg | 0 .../svgs/solid/oil-well.svg | 0 .../svgs/solid/om.svg | 0 .../svgs/solid/otter.svg | 0 .../svgs/solid/outdent.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/p.svg | 0 .../svgs/solid/pager.svg | 0 .../svgs/solid/paint-roller.svg | 0 .../svgs/solid/paintbrush.svg | 0 .../svgs/solid/palette.svg | 0 .../svgs/solid/pallet.svg | 0 .../svgs/solid/panorama.svg | 0 .../svgs/solid/paper-plane.svg | 0 .../svgs/solid/paperclip.svg | 0 .../svgs/solid/parachute-box.svg | 0 .../svgs/solid/paragraph.svg | 0 .../svgs/solid/passport.svg | 0 .../svgs/solid/paste.svg | 0 .../svgs/solid/pause.svg | 0 .../svgs/solid/paw.svg | 0 .../svgs/solid/peace.svg | 0 .../svgs/solid/pen-clip.svg | 0 .../svgs/solid/pen-fancy.svg | 0 .../svgs/solid/pen-nib.svg | 0 .../svgs/solid/pen-ruler.svg | 0 .../svgs/solid/pen-to-square.svg | 0 .../svgs/solid/pen.svg | 0 .../svgs/solid/pencil.svg | 0 .../svgs/solid/people-arrows.svg | 0 .../svgs/solid/people-carry-box.svg | 0 .../svgs/solid/people-group.svg | 0 .../svgs/solid/people-line.svg | 0 .../svgs/solid/people-pulling.svg | 0 .../svgs/solid/people-robbery.svg | 0 .../svgs/solid/people-roof.svg | 0 .../svgs/solid/pepper-hot.svg | 0 .../svgs/solid/percent.svg | 0 .../svgs/solid/person-arrow-down-to-line.svg | 0 .../svgs/solid/person-arrow-up-from-line.svg | 0 .../svgs/solid/person-biking.svg | 0 .../svgs/solid/person-booth.svg | 0 .../svgs/solid/person-breastfeeding.svg | 0 .../svgs/solid/person-burst.svg | 0 .../svgs/solid/person-cane.svg | 0 .../svgs/solid/person-chalkboard.svg | 0 .../svgs/solid/person-circle-check.svg | 0 .../svgs/solid/person-circle-exclamation.svg | 0 .../svgs/solid/person-circle-minus.svg | 0 .../svgs/solid/person-circle-plus.svg | 0 .../svgs/solid/person-circle-question.svg | 0 .../svgs/solid/person-circle-xmark.svg | 0 .../svgs/solid/person-digging.svg | 0 .../svgs/solid/person-dots-from-line.svg | 0 .../svgs/solid/person-dress-burst.svg | 0 .../svgs/solid/person-dress.svg | 0 .../svgs/solid/person-drowning.svg | 0 .../svgs/solid/person-falling-burst.svg | 0 .../svgs/solid/person-falling.svg | 0 .../svgs/solid/person-half-dress.svg | 0 .../svgs/solid/person-harassing.svg | 0 .../svgs/solid/person-hiking.svg | 0 .../svgs/solid/person-military-pointing.svg | 0 .../svgs/solid/person-military-rifle.svg | 0 .../svgs/solid/person-military-to-person.svg | 0 .../svgs/solid/person-praying.svg | 0 .../svgs/solid/person-pregnant.svg | 0 .../svgs/solid/person-rays.svg | 0 .../svgs/solid/person-rifle.svg | 0 .../svgs/solid/person-running.svg | 0 .../svgs/solid/person-shelter.svg | 0 .../svgs/solid/person-skating.svg | 0 .../svgs/solid/person-skiing-nordic.svg | 0 .../svgs/solid/person-skiing.svg | 0 .../svgs/solid/person-snowboarding.svg | 0 .../svgs/solid/person-swimming.svg | 0 .../svgs/solid/person-through-window.svg | 0 .../svgs/solid/person-walking-arrow-loop-left.svg | 0 .../svgs/solid/person-walking-arrow-right.svg | 0 .../person-walking-dashed-line-arrow-right.svg | 0 .../svgs/solid/person-walking-luggage.svg | 0 .../svgs/solid/person-walking-with-cane.svg | 0 .../svgs/solid/person-walking.svg | 0 .../svgs/solid/person.svg | 0 .../svgs/solid/peseta-sign.svg | 0 .../svgs/solid/peso-sign.svg | 0 .../svgs/solid/phone-flip.svg | 0 .../svgs/solid/phone-slash.svg | 0 .../svgs/solid/phone-volume.svg | 0 .../svgs/solid/phone.svg | 0 .../svgs/solid/photo-film.svg | 0 .../svgs/solid/piggy-bank.svg | 0 .../svgs/solid/pills.svg | 0 .../svgs/solid/pizza-slice.svg | 0 .../svgs/solid/place-of-worship.svg | 0 .../svgs/solid/plane-arrival.svg | 0 .../svgs/solid/plane-circle-check.svg | 0 .../svgs/solid/plane-circle-exclamation.svg | 0 .../svgs/solid/plane-circle-xmark.svg | 0 .../svgs/solid/plane-departure.svg | 0 .../svgs/solid/plane-lock.svg | 0 .../svgs/solid/plane-slash.svg | 0 .../svgs/solid/plane-up.svg | 0 .../svgs/solid/plane.svg | 0 .../svgs/solid/plant-wilt.svg | 0 .../svgs/solid/plate-wheat.svg | 0 .../svgs/solid/play.svg | 0 .../svgs/solid/plug-circle-bolt.svg | 0 .../svgs/solid/plug-circle-check.svg | 0 .../svgs/solid/plug-circle-exclamation.svg | 0 .../svgs/solid/plug-circle-minus.svg | 0 .../svgs/solid/plug-circle-plus.svg | 0 .../svgs/solid/plug-circle-xmark.svg | 0 .../svgs/solid/plug.svg | 0 .../svgs/solid/plus-minus.svg | 0 .../svgs/solid/plus.svg | 0 .../svgs/solid/podcast.svg | 0 .../svgs/solid/poo-storm.svg | 0 .../svgs/solid/poo.svg | 0 .../svgs/solid/poop.svg | 0 .../svgs/solid/power-off.svg | 0 .../svgs/solid/prescription-bottle-medical.svg | 0 .../svgs/solid/prescription-bottle.svg | 0 .../svgs/solid/prescription.svg | 0 .../svgs/solid/print.svg | 0 .../svgs/solid/pump-medical.svg | 0 .../svgs/solid/pump-soap.svg | 0 .../svgs/solid/puzzle-piece.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/q.svg | 0 .../svgs/solid/qrcode.svg | 0 .../svgs/solid/question.svg | 0 .../svgs/solid/quote-left.svg | 0 .../svgs/solid/quote-right.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/r.svg | 0 .../svgs/solid/radiation.svg | 0 .../svgs/solid/radio.svg | 0 .../svgs/solid/rainbow.svg | 0 .../svgs/solid/ranking-star.svg | 0 .../svgs/solid/receipt.svg | 0 .../svgs/solid/record-vinyl.svg | 0 .../svgs/solid/rectangle-ad.svg | 0 .../svgs/solid/rectangle-list.svg | 0 .../svgs/solid/rectangle-xmark.svg | 0 .../svgs/solid/recycle.svg | 0 .../svgs/solid/registered.svg | 0 .../svgs/solid/repeat.svg | 0 .../svgs/solid/reply-all.svg | 0 .../svgs/solid/reply.svg | 0 .../svgs/solid/republican.svg | 0 .../svgs/solid/restroom.svg | 0 .../svgs/solid/retweet.svg | 0 .../svgs/solid/ribbon.svg | 0 .../svgs/solid/right-from-bracket.svg | 0 .../svgs/solid/right-left.svg | 0 .../svgs/solid/right-long.svg | 0 .../svgs/solid/right-to-bracket.svg | 0 .../svgs/solid/ring.svg | 0 .../svgs/solid/road-barrier.svg | 0 .../svgs/solid/road-bridge.svg | 0 .../svgs/solid/road-circle-check.svg | 0 .../svgs/solid/road-circle-exclamation.svg | 0 .../svgs/solid/road-circle-xmark.svg | 0 .../svgs/solid/road-lock.svg | 0 .../svgs/solid/road-spikes.svg | 0 .../svgs/solid/road.svg | 0 .../svgs/solid/robot.svg | 0 .../svgs/solid/rocket.svg | 0 .../svgs/solid/rotate-left.svg | 0 .../svgs/solid/rotate-right.svg | 0 .../svgs/solid/rotate.svg | 0 .../svgs/solid/route.svg | 0 .../svgs/solid/rss.svg | 0 .../svgs/solid/ruble-sign.svg | 0 .../svgs/solid/rug.svg | 0 .../svgs/solid/ruler-combined.svg | 0 .../svgs/solid/ruler-horizontal.svg | 0 .../svgs/solid/ruler-vertical.svg | 0 .../svgs/solid/ruler.svg | 0 .../svgs/solid/rupee-sign.svg | 0 .../svgs/solid/rupiah-sign.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/s.svg | 0 .../svgs/solid/sack-dollar.svg | 0 .../svgs/solid/sack-xmark.svg | 0 .../svgs/solid/sailboat.svg | 0 .../svgs/solid/satellite-dish.svg | 0 .../svgs/solid/satellite.svg | 0 .../svgs/solid/scale-balanced.svg | 0 .../svgs/solid/scale-unbalanced-flip.svg | 0 .../svgs/solid/scale-unbalanced.svg | 0 .../svgs/solid/school-circle-check.svg | 0 .../svgs/solid/school-circle-exclamation.svg | 0 .../svgs/solid/school-circle-xmark.svg | 0 .../svgs/solid/school-flag.svg | 0 .../svgs/solid/school-lock.svg | 0 .../svgs/solid/school.svg | 0 .../svgs/solid/scissors.svg | 0 .../svgs/solid/screwdriver-wrench.svg | 0 .../svgs/solid/screwdriver.svg | 0 .../svgs/solid/scroll-torah.svg | 0 .../svgs/solid/scroll.svg | 0 .../svgs/solid/sd-card.svg | 0 .../svgs/solid/section.svg | 0 .../svgs/solid/seedling.svg | 0 .../svgs/solid/server.svg | 0 .../svgs/solid/shapes.svg | 0 .../svgs/solid/share-from-square.svg | 0 .../svgs/solid/share-nodes.svg | 0 .../svgs/solid/share.svg | 0 .../svgs/solid/sheet-plastic.svg | 0 .../svgs/solid/shekel-sign.svg | 0 .../svgs/solid/shield-cat.svg | 0 .../svgs/solid/shield-dog.svg | 0 .../svgs/solid/shield-halved.svg | 0 .../svgs/solid/shield-heart.svg | 0 .../svgs/solid/shield-virus.svg | 0 .../svgs/solid/shield.svg | 0 .../svgs/solid/ship.svg | 0 .../svgs/solid/shirt.svg | 0 .../svgs/solid/shoe-prints.svg | 0 .../svgs/solid/shop-lock.svg | 0 .../svgs/solid/shop-slash.svg | 0 .../svgs/solid/shop.svg | 0 .../svgs/solid/shower.svg | 0 .../svgs/solid/shrimp.svg | 0 .../svgs/solid/shuffle.svg | 0 .../svgs/solid/shuttle-space.svg | 0 .../svgs/solid/sign-hanging.svg | 0 .../svgs/solid/signal.svg | 0 .../svgs/solid/signature.svg | 0 .../svgs/solid/signs-post.svg | 0 .../svgs/solid/sim-card.svg | 0 .../svgs/solid/sink.svg | 0 .../svgs/solid/sitemap.svg | 0 .../svgs/solid/skull-crossbones.svg | 0 .../svgs/solid/skull.svg | 0 .../svgs/solid/slash.svg | 0 .../svgs/solid/sleigh.svg | 0 .../svgs/solid/sliders.svg | 0 .../svgs/solid/smog.svg | 0 .../svgs/solid/smoking.svg | 0 .../svgs/solid/snowflake.svg | 0 .../svgs/solid/snowman.svg | 0 .../svgs/solid/snowplow.svg | 0 .../svgs/solid/soap.svg | 0 .../svgs/solid/socks.svg | 0 .../svgs/solid/solar-panel.svg | 0 .../svgs/solid/sort-down.svg | 0 .../svgs/solid/sort-up.svg | 0 .../svgs/solid/sort.svg | 0 .../svgs/solid/spa.svg | 0 .../svgs/solid/spaghetti-monster-flying.svg | 0 .../svgs/solid/spell-check.svg | 0 .../svgs/solid/spider.svg | 0 .../svgs/solid/spinner.svg | 0 .../svgs/solid/splotch.svg | 0 .../svgs/solid/spoon.svg | 0 .../svgs/solid/spray-can-sparkles.svg | 0 .../svgs/solid/spray-can.svg | 0 .../svgs/solid/square-arrow-up-right.svg | 0 .../svgs/solid/square-caret-down.svg | 0 .../svgs/solid/square-caret-left.svg | 0 .../svgs/solid/square-caret-right.svg | 0 .../svgs/solid/square-caret-up.svg | 0 .../svgs/solid/square-check.svg | 0 .../svgs/solid/square-envelope.svg | 0 .../svgs/solid/square-full.svg | 0 .../svgs/solid/square-h.svg | 0 .../svgs/solid/square-minus.svg | 0 .../svgs/solid/square-nfi.svg | 0 .../svgs/solid/square-parking.svg | 0 .../svgs/solid/square-pen.svg | 0 .../svgs/solid/square-person-confined.svg | 0 .../svgs/solid/square-phone-flip.svg | 0 .../svgs/solid/square-phone.svg | 0 .../svgs/solid/square-plus.svg | 0 .../svgs/solid/square-poll-horizontal.svg | 0 .../svgs/solid/square-poll-vertical.svg | 0 .../svgs/solid/square-root-variable.svg | 0 .../svgs/solid/square-rss.svg | 0 .../svgs/solid/square-share-nodes.svg | 0 .../svgs/solid/square-up-right.svg | 0 .../svgs/solid/square-virus.svg | 0 .../svgs/solid/square-xmark.svg | 0 .../svgs/solid/square.svg | 0 .../svgs/solid/staff-snake.svg | 0 .../svgs/solid/stairs.svg | 0 .../svgs/solid/stamp.svg | 0 .../svgs/solid/stapler.svg | 0 .../svgs/solid/star-and-crescent.svg | 0 .../svgs/solid/star-half-stroke.svg | 0 .../svgs/solid/star-half.svg | 0 .../svgs/solid/star-of-david.svg | 0 .../svgs/solid/star-of-life.svg | 0 .../svgs/solid/star.svg | 0 .../svgs/solid/sterling-sign.svg | 0 .../svgs/solid/stethoscope.svg | 0 .../svgs/solid/stop.svg | 0 .../svgs/solid/stopwatch-20.svg | 0 .../svgs/solid/stopwatch.svg | 0 .../svgs/solid/store-slash.svg | 0 .../svgs/solid/store.svg | 0 .../svgs/solid/street-view.svg | 0 .../svgs/solid/strikethrough.svg | 0 .../svgs/solid/stroopwafel.svg | 0 .../svgs/solid/subscript.svg | 0 .../svgs/solid/suitcase-medical.svg | 0 .../svgs/solid/suitcase-rolling.svg | 0 .../svgs/solid/suitcase.svg | 0 .../svgs/solid/sun-plant-wilt.svg | 0 .../svgs/solid/sun.svg | 0 .../svgs/solid/superscript.svg | 0 .../svgs/solid/swatchbook.svg | 0 .../svgs/solid/synagogue.svg | 0 .../svgs/solid/syringe.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/t.svg | 0 .../svgs/solid/table-cells-large.svg | 0 .../svgs/solid/table-cells.svg | 0 .../svgs/solid/table-columns.svg | 0 .../svgs/solid/table-list.svg | 0 .../svgs/solid/table-tennis-paddle-ball.svg | 0 .../svgs/solid/table.svg | 0 .../svgs/solid/tablet-button.svg | 0 .../svgs/solid/tablet-screen-button.svg | 0 .../svgs/solid/tablet.svg | 0 .../svgs/solid/tablets.svg | 0 .../svgs/solid/tachograph-digital.svg | 0 .../svgs/solid/tag.svg | 0 .../svgs/solid/tags.svg | 0 .../svgs/solid/tape.svg | 0 .../svgs/solid/tarp-droplet.svg | 0 .../svgs/solid/tarp.svg | 0 .../svgs/solid/taxi.svg | 0 .../svgs/solid/teeth-open.svg | 0 .../svgs/solid/teeth.svg | 0 .../svgs/solid/temperature-arrow-down.svg | 0 .../svgs/solid/temperature-arrow-up.svg | 0 .../svgs/solid/temperature-empty.svg | 0 .../svgs/solid/temperature-full.svg | 0 .../svgs/solid/temperature-half.svg | 0 .../svgs/solid/temperature-high.svg | 0 .../svgs/solid/temperature-low.svg | 0 .../svgs/solid/temperature-quarter.svg | 0 .../svgs/solid/temperature-three-quarters.svg | 0 .../svgs/solid/tenge-sign.svg | 0 .../svgs/solid/tent-arrow-down-to-line.svg | 0 .../svgs/solid/tent-arrow-left-right.svg | 0 .../svgs/solid/tent-arrow-turn-left.svg | 0 .../svgs/solid/tent-arrows-down.svg | 0 .../svgs/solid/tent.svg | 0 .../svgs/solid/tents.svg | 0 .../svgs/solid/terminal.svg | 0 .../svgs/solid/text-height.svg | 0 .../svgs/solid/text-slash.svg | 0 .../svgs/solid/text-width.svg | 0 .../svgs/solid/thermometer.svg | 0 .../svgs/solid/thumbs-down.svg | 0 .../svgs/solid/thumbs-up.svg | 0 .../svgs/solid/thumbtack.svg | 0 .../svgs/solid/ticket-simple.svg | 0 .../svgs/solid/ticket.svg | 0 .../svgs/solid/timeline.svg | 0 .../svgs/solid/toggle-off.svg | 0 .../svgs/solid/toggle-on.svg | 0 .../svgs/solid/toilet-paper-slash.svg | 0 .../svgs/solid/toilet-paper.svg | 0 .../svgs/solid/toilet-portable.svg | 0 .../svgs/solid/toilet.svg | 0 .../svgs/solid/toilets-portable.svg | 0 .../svgs/solid/toolbox.svg | 0 .../svgs/solid/tooth.svg | 0 .../svgs/solid/torii-gate.svg | 0 .../svgs/solid/tornado.svg | 0 .../svgs/solid/tower-broadcast.svg | 0 .../svgs/solid/tower-cell.svg | 0 .../svgs/solid/tower-observation.svg | 0 .../svgs/solid/tractor.svg | 0 .../svgs/solid/trademark.svg | 0 .../svgs/solid/traffic-light.svg | 0 .../svgs/solid/trailer.svg | 0 .../svgs/solid/train-subway.svg | 0 .../svgs/solid/train-tram.svg | 0 .../svgs/solid/train.svg | 0 .../svgs/solid/transgender.svg | 0 .../svgs/solid/trash-arrow-up.svg | 0 .../svgs/solid/trash-can-arrow-up.svg | 0 .../svgs/solid/trash-can.svg | 0 .../svgs/solid/trash.svg | 0 .../svgs/solid/tree-city.svg | 0 .../svgs/solid/tree.svg | 0 .../svgs/solid/triangle-exclamation.svg | 0 .../svgs/solid/trophy.svg | 0 .../svgs/solid/trowel-bricks.svg | 0 .../svgs/solid/trowel.svg | 0 .../svgs/solid/truck-arrow-right.svg | 0 .../svgs/solid/truck-droplet.svg | 0 .../svgs/solid/truck-fast.svg | 0 .../svgs/solid/truck-field-un.svg | 0 .../svgs/solid/truck-field.svg | 0 .../svgs/solid/truck-front.svg | 0 .../svgs/solid/truck-medical.svg | 0 .../svgs/solid/truck-monster.svg | 0 .../svgs/solid/truck-moving.svg | 0 .../svgs/solid/truck-pickup.svg | 0 .../svgs/solid/truck-plane.svg | 0 .../svgs/solid/truck-ramp-box.svg | 0 .../svgs/solid/truck.svg | 0 .../svgs/solid/tty.svg | 0 .../svgs/solid/turkish-lira-sign.svg | 0 .../svgs/solid/turn-down.svg | 0 .../svgs/solid/turn-up.svg | 0 .../svgs/solid/tv.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/u.svg | 0 .../svgs/solid/umbrella-beach.svg | 0 .../svgs/solid/umbrella.svg | 0 .../svgs/solid/underline.svg | 0 .../svgs/solid/universal-access.svg | 0 .../svgs/solid/unlock-keyhole.svg | 0 .../svgs/solid/unlock.svg | 0 .../svgs/solid/up-down-left-right.svg | 0 .../svgs/solid/up-down.svg | 0 .../svgs/solid/up-long.svg | 0 .../solid/up-right-and-down-left-from-center.svg | 0 .../svgs/solid/up-right-from-square.svg | 0 .../svgs/solid/upload.svg | 0 .../svgs/solid/user-astronaut.svg | 0 .../svgs/solid/user-check.svg | 0 .../svgs/solid/user-clock.svg | 0 .../svgs/solid/user-doctor.svg | 0 .../svgs/solid/user-gear.svg | 0 .../svgs/solid/user-graduate.svg | 0 .../svgs/solid/user-group.svg | 0 .../svgs/solid/user-injured.svg | 0 .../svgs/solid/user-large-slash.svg | 0 .../svgs/solid/user-large.svg | 0 .../svgs/solid/user-lock.svg | 0 .../svgs/solid/user-minus.svg | 0 .../svgs/solid/user-ninja.svg | 0 .../svgs/solid/user-nurse.svg | 0 .../svgs/solid/user-pen.svg | 0 .../svgs/solid/user-plus.svg | 0 .../svgs/solid/user-secret.svg | 0 .../svgs/solid/user-shield.svg | 0 .../svgs/solid/user-slash.svg | 0 .../svgs/solid/user-tag.svg | 0 .../svgs/solid/user-tie.svg | 0 .../svgs/solid/user-xmark.svg | 0 .../svgs/solid/user.svg | 0 .../svgs/solid/users-between-lines.svg | 0 .../svgs/solid/users-gear.svg | 0 .../svgs/solid/users-line.svg | 0 .../svgs/solid/users-rays.svg | 0 .../svgs/solid/users-rectangle.svg | 0 .../svgs/solid/users-slash.svg | 0 .../svgs/solid/users-viewfinder.svg | 0 .../svgs/solid/users.svg | 0 .../svgs/solid/utensils.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/v.svg | 0 .../svgs/solid/van-shuttle.svg | 0 .../svgs/solid/vault.svg | 0 .../svgs/solid/vector-square.svg | 0 .../svgs/solid/venus-double.svg | 0 .../svgs/solid/venus-mars.svg | 0 .../svgs/solid/venus.svg | 0 .../svgs/solid/vest-patches.svg | 0 .../svgs/solid/vest.svg | 0 .../svgs/solid/vial-circle-check.svg | 0 .../svgs/solid/vial-virus.svg | 0 .../svgs/solid/vial.svg | 0 .../svgs/solid/vials.svg | 0 .../svgs/solid/video-slash.svg | 0 .../svgs/solid/video.svg | 0 .../svgs/solid/vihara.svg | 0 .../svgs/solid/virus-covid-slash.svg | 0 .../svgs/solid/virus-covid.svg | 0 .../svgs/solid/virus-slash.svg | 0 .../svgs/solid/virus.svg | 0 .../svgs/solid/viruses.svg | 0 .../svgs/solid/voicemail.svg | 0 .../svgs/solid/volcano.svg | 0 .../svgs/solid/volleyball.svg | 0 .../svgs/solid/volume-high.svg | 0 .../svgs/solid/volume-low.svg | 0 .../svgs/solid/volume-off.svg | 0 .../svgs/solid/volume-xmark.svg | 0 .../svgs/solid/vr-cardboard.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/w.svg | 0 .../svgs/solid/walkie-talkie.svg | 0 .../svgs/solid/wallet.svg | 0 .../svgs/solid/wand-magic-sparkles.svg | 0 .../svgs/solid/wand-magic.svg | 0 .../svgs/solid/wand-sparkles.svg | 0 .../svgs/solid/warehouse.svg | 0 .../svgs/solid/water-ladder.svg | 0 .../svgs/solid/water.svg | 0 .../svgs/solid/wave-square.svg | 0 .../svgs/solid/weight-hanging.svg | 0 .../svgs/solid/weight-scale.svg | 0 .../svgs/solid/wheat-awn-circle-exclamation.svg | 0 .../svgs/solid/wheat-awn.svg | 0 .../svgs/solid/wheelchair-move.svg | 0 .../svgs/solid/wheelchair.svg | 0 .../svgs/solid/whiskey-glass.svg | 0 .../svgs/solid/wifi.svg | 0 .../svgs/solid/wind.svg | 0 .../svgs/solid/window-maximize.svg | 0 .../svgs/solid/window-minimize.svg | 0 .../svgs/solid/window-restore.svg | 0 .../svgs/solid/wine-bottle.svg | 0 .../svgs/solid/wine-glass-empty.svg | 0 .../svgs/solid/wine-glass.svg | 0 .../svgs/solid/won-sign.svg | 0 .../svgs/solid/worm.svg | 0 .../svgs/solid/wrench.svg | 0 .../svgs/solid/x-ray.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/x.svg | 0 .../svgs/solid/xmark.svg | 0 .../svgs/solid/xmarks-lines.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/y.svg | 0 .../svgs/solid/yen-sign.svg | 0 .../svgs/solid/yin-yang.svg | 0 .../fontawesome-free-6.2.0-desktop/svgs/solid/z.svg | 0 .../{ => lib}/font-awesome-as-a-crate/released.sh | 0 crates/{ => lib}/font-awesome-as-a-crate/src/lib.rs | 0 crates/{ => lib}/metadata/Cargo.toml | 0 crates/{ => lib}/metadata/build.rs | 0 crates/{ => lib}/metadata/lib.rs | 0 3619 files changed, 4 insertions(+), 1 deletion(-) rename crates/{ => bin}/docs_rs_builder/Cargo.toml (100%) rename crates/{ => bin}/docs_rs_builder/src/config.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/db/add_package.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/db/blacklist.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/db/mod.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/docbuilder/mod.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/docbuilder/rustwide_builder.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/lib.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/metrics.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/utils/copy.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/utils/mod.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/utils/queue_builder.rs (100%) rename crates/{ => bin}/docs_rs_builder/src/utils/version.rs (100%) rename crates/{ => bin}/docs_rs_watcher/Cargo.toml (100%) rename crates/{ => bin}/docs_rs_watcher/src/build_queue.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/config.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/consistency/data.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/consistency/db.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/consistency/diff.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/consistency/index.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/consistency/mod.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/db/delete.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/db/mod.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/index.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/lib.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/main.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/priorities.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/rebuilds.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/service_metrics.rs (100%) rename crates/{ => bin}/docs_rs_watcher/src/utils.rs (100%) rename crates/{ => bin}/docs_rs_web/Cargo.toml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/README.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/.travis.yml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LICENSE (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/README.md (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml (100%) rename crates/{ => bin}/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml (100%) rename crates/{ => bin}/docs_rs_web/build.rs (100%) rename crates/{ => bin}/docs_rs_web/src/build_details.rs (100%) rename crates/{ => bin}/docs_rs_web/src/builds.rs (100%) rename crates/{ => bin}/docs_rs_web/src/cache.rs (100%) rename crates/{ => bin}/docs_rs_web/src/config.rs (100%) rename crates/{ => bin}/docs_rs_web/src/crate_details.rs (100%) rename crates/{ => bin}/docs_rs_web/src/csp.rs (100%) rename crates/{ => bin}/docs_rs_web/src/error.rs (100%) rename crates/{ => bin}/docs_rs_web/src/extractors/context.rs (100%) rename crates/{ => bin}/docs_rs_web/src/extractors/mod.rs (100%) rename crates/{ => bin}/docs_rs_web/src/extractors/path.rs (100%) rename crates/{ => bin}/docs_rs_web/src/extractors/rustdoc.rs (100%) rename crates/{ => bin}/docs_rs_web/src/features.rs (100%) rename crates/{ => bin}/docs_rs_web/src/file.rs (100%) rename crates/{ => bin}/docs_rs_web/src/highlight.rs (100%) rename crates/{ => bin}/docs_rs_web/src/lib.rs (100%) rename crates/{ => bin}/docs_rs_web/src/licenses.rs (100%) rename crates/{ => bin}/docs_rs_web/src/markdown.rs (100%) rename crates/{ => bin}/docs_rs_web/src/metrics.rs (100%) rename crates/{ => bin}/docs_rs_web/src/page/mod.rs (100%) rename crates/{ => bin}/docs_rs_web/src/page/templates.rs (100%) rename crates/{ => bin}/docs_rs_web/src/page/web_page.rs (100%) rename crates/{ => bin}/docs_rs_web/src/releases.rs (100%) rename crates/{ => bin}/docs_rs_web/src/routes.rs (100%) rename crates/{ => bin}/docs_rs_web/src/rustdoc.rs (100%) rename crates/{ => bin}/docs_rs_web/src/sitemap.rs (100%) rename crates/{ => bin}/docs_rs_web/src/source.rs (100%) rename crates/{ => bin}/docs_rs_web/src/statics.rs (100%) rename crates/{ => bin}/docs_rs_web/src/status.rs (100%) rename crates/{ => bin}/docs_rs_web/src/utils/html.rs (100%) rename crates/{ => bin}/docs_rs_web/src/utils/mod.rs (100%) rename crates/{ => bin}/docs_rs_web/src/utils/rustc_version.rs (100%) rename crates/{ => bin}/docs_rs_web/static/FiraSans-LICENSE.txt (100%) rename crates/{ => bin}/docs_rs_web/static/FiraSans-Medium.woff (100%) rename crates/{ => bin}/docs_rs_web/static/FiraSans-Medium.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/FiraSans-Regular.woff (100%) rename crates/{ => bin}/docs_rs_web/static/FiraSans-Regular.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-It.ttf.woff (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-It.ttf.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-LICENSE.md (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-Regular.ttf.woff (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff (100%) rename crates/{ => bin}/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-Bold.ttf.woff (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-It.ttf.woff (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-It.ttf.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-LICENSE.md (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-Regular.ttf.woff (100%) rename crates/{ => bin}/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/clipboard.svg (100%) rename crates/{ => bin}/docs_rs_web/static/fa-brands-400.ttf (100%) rename crates/{ => bin}/docs_rs_web/static/fa-brands-400.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/fa-regular-400.ttf (100%) rename crates/{ => bin}/docs_rs_web/static/fa-regular-400.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/fa-solid-900.ttf (100%) rename crates/{ => bin}/docs_rs_web/static/fa-solid-900.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/fa-v4compatibility.ttf (100%) rename crates/{ => bin}/docs_rs_web/static/fa-v4compatibility.woff2 (100%) rename crates/{ => bin}/docs_rs_web/static/favicon.ico (100%) rename crates/{ => bin}/docs_rs_web/static/index.js (100%) rename crates/{ => bin}/docs_rs_web/static/keyboard.js (100%) rename crates/{ => bin}/docs_rs_web/static/menu.js (100%) rename crates/{ => bin}/docs_rs_web/static/opensearch.xml (100%) rename crates/{ => bin}/docs_rs_web/static/robots.txt (100%) rename crates/{ => bin}/docs_rs_web/static/source.js (100%) rename crates/{ => bin}/docs_rs_web/static/trigger-rebuild.png (100%) rename crates/{ => bin}/docs_rs_web/templates/about-base.html (100%) rename crates/{ => bin}/docs_rs_web/templates/base.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/Cargo.toml.example (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/badges.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/builds.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/download.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/index.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/metadata.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/redirections.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/about/rustdoc-json.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/home.html (100%) rename crates/{ => bin}/docs_rs_web/templates/core/sitemap/_item.xml (100%) rename crates/{ => bin}/docs_rs_web/templates/core/sitemap/index.xml (100%) rename crates/{ => bin}/docs_rs_web/templates/crate/build_details.html (100%) rename crates/{ => bin}/docs_rs_web/templates/crate/builds.html (100%) rename crates/{ => bin}/docs_rs_web/templates/crate/details.html (100%) rename crates/{ => bin}/docs_rs_web/templates/crate/features.html (100%) rename crates/{ => bin}/docs_rs_web/templates/crate/source.html (100%) rename crates/{ => bin}/docs_rs_web/templates/error.html (100%) rename crates/{ => bin}/docs_rs_web/templates/header/global_alert.html (100%) rename crates/{ => bin}/docs_rs_web/templates/header/package_navigation.html (100%) rename crates/{ => bin}/docs_rs_web/templates/header/topbar.html (100%) rename crates/{ => bin}/docs_rs_web/templates/header/topbar_begin.html (100%) rename crates/{ => bin}/docs_rs_web/templates/header/topbar_end.html (100%) rename crates/{ => bin}/docs_rs_web/templates/macros.html (100%) rename crates/{ => bin}/docs_rs_web/templates/releases/activity.html (100%) rename crates/{ => bin}/docs_rs_web/templates/releases/build_queue.html (100%) rename crates/{ => bin}/docs_rs_web/templates/releases/feed.xml (100%) rename crates/{ => bin}/docs_rs_web/templates/releases/header.html (100%) rename crates/{ => bin}/docs_rs_web/templates/releases/releases.html (100%) rename crates/{ => bin}/docs_rs_web/templates/releases/search_results.html (100%) rename crates/{ => bin}/docs_rs_web/templates/rustdoc/body.html (100%) rename crates/{ => bin}/docs_rs_web/templates/rustdoc/head.html (100%) rename crates/{ => bin}/docs_rs_web/templates/rustdoc/platforms.html (100%) rename crates/{ => bin}/docs_rs_web/templates/rustdoc/releases.html (100%) rename crates/{ => bin}/docs_rs_web/templates/rustdoc/topbar.html (100%) rename crates/{ => bin}/docs_rs_web/templates/rustdoc/vendored.html (100%) rename crates/{ => bin}/docs_rs_web/templates/storage-change-detection.html (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_navbar.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_rustdoc-common.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_syntax-themes.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_syntax.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_themes.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_utils.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/_vars.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/rustdoc-2021-12-05.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/rustdoc-2025-08-20.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/rustdoc.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/style/style.scss (100%) rename crates/{ => bin}/docs_rs_web/templates/theme.js (100%) rename crates/{ => bin}/docs_rs_web/vendor/chartjs/LICENSE (100%) rename crates/{ => bin}/docs_rs_web/vendor/chartjs/chart.min.js (100%) rename crates/{ => bin}/docs_rs_web/vendor/pure-css/LICENSE (100%) rename crates/{ => lib}/docs_rs_build_queue/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_build_queue/src/config.rs (100%) rename crates/{ => lib}/docs_rs_build_queue/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_build_queue/src/metrics.rs (100%) rename crates/{ => lib}/docs_rs_build_queue/src/rebuilds.rs (100%) rename crates/{ => lib}/docs_rs_build_utils/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_build_utils/src/config.rs (100%) rename crates/{ => lib}/docs_rs_build_utils/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_build_utils/src/limits.rs (100%) rename crates/{ => lib}/docs_rs_build_utils/src/overrides.rs (100%) rename crates/{ => lib}/docs_rs_cargo_metadata/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_cargo_metadata/src/db.rs (100%) rename crates/{ => lib}/docs_rs_cargo_metadata/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_context/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_context/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_database/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_database/build.rs (100%) rename crates/{ => lib}/docs_rs_database/migrations/20231021111635_initial.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20231021111635_initial.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240313184911_build_errors.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240313184911_build_errors.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241018031600_documentation_size.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241018031600_documentation_size.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql (100%) rename crates/{ => lib}/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql (100%) rename crates/{ => lib}/docs_rs_database/src/config.rs (100%) rename crates/{ => lib}/docs_rs_database/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_database/src/migrate.rs (100%) rename crates/{ => lib}/docs_rs_database/src/mimes.rs (100%) rename crates/{ => lib}/docs_rs_database/src/service_config.rs (100%) rename crates/{ => lib}/docs_rs_database/src/types/krate_name.rs (100%) rename crates/{ => lib}/docs_rs_database/src/types/mod.rs (100%) rename crates/{ => lib}/docs_rs_database/src/types/version.rs (100%) rename crates/{ => lib}/docs_rs_env_vars/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_env_vars/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_fastly/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_fastly/src/config.rs (100%) rename crates/{ => lib}/docs_rs_fastly/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_fastly/src/metrics.rs (100%) rename crates/{ => lib}/docs_rs_headers/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_headers/src/canonical_url.rs (100%) rename crates/{ => lib}/docs_rs_headers/src/etag.rs (100%) rename crates/{ => lib}/docs_rs_headers/src/if_none_match.rs (100%) rename crates/{ => lib}/docs_rs_headers/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_headers/src/surrogate_key.rs (100%) rename crates/{ => lib}/docs_rs_logging/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_logging/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_opentelemetry/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_opentelemetry/src/config.rs (100%) rename crates/{ => lib}/docs_rs_opentelemetry/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_registry_api/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_registry_api/src/config.rs (100%) rename crates/{ => lib}/docs_rs_registry_api/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_repository_stats/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_repository_stats/src/config.rs (100%) rename crates/{ => lib}/docs_rs_repository_stats/src/github.rs (100%) rename crates/{ => lib}/docs_rs_repository_stats/src/gitlab.rs (100%) rename crates/{ => lib}/docs_rs_repository_stats/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_repository_stats/src/mod.rs (100%) rename crates/{ => lib}/docs_rs_repository_stats/src/updater.rs (100%) rename crates/{ => lib}/docs_rs_storage/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_storage/benches/compression.rs (100%) rename crates/{ => lib}/docs_rs_storage/benches/struct.CaptureMatches.html (100%) rename crates/{ => lib}/docs_rs_storage/src/archive_index.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/compression.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/config.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/database.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/errors.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/file.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/s3.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/utils/mod.rs (100%) rename crates/{ => lib}/docs_rs_storage/src/utils/sized_buffer.rs (100%) rename crates/{ => lib}/docs_rs_utils/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_utils/build.rs (100%) rename crates/{ => lib}/docs_rs_utils/src/lib.rs (100%) rename crates/{ => lib}/docs_rs_utils/src/rustc_version.rs (100%) rename crates/{ => lib}/docs_rs_web_utils/Cargo.toml (100%) rename crates/{ => lib}/docs_rs_web_utils/src/escaped_uri.rs (100%) rename crates/{ => lib}/docs_rs_web_utils/src/lib.rs (100%) rename crates/{ => lib}/font-awesome-as-a-crate/.gitignore (100%) rename crates/{ => lib}/font-awesome-as-a-crate/Cargo.toml (100%) rename crates/{ => lib}/font-awesome-as-a-crate/README.md (100%) rename crates/{ => lib}/font-awesome-as-a-crate/build.rs (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/LICENSE.txt (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/VENDOR.md (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/42-group.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/500px.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accessible-icon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accusoft.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adversal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/affiliatetheme.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/airbnb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/algolia.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/alipay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon-pay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amilia.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/android.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angellist.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angrycreative.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angular.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store-ios.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple-pay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/artstation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/asymmetrik.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/atlassian.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/audible.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/autoprefixer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/avianex.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aviato.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aws.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bandcamp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/battle-net.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/behance.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bilibili.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bimobject.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitbucket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitcoin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bity.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/black-tie.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blackberry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger-b.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth-b.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bootstrap.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bots.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/btc.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buffer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buromobelexperte.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buy-n-large.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buysellads.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/canadian-maple-leaf.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amazon-pay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amex.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-apple-pay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-diners-club.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-discover.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-jcb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-mastercard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-paypal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-stripe.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-visa.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centercode.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centos.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chrome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chromecast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudflare.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudscale.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudsmith.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudversify.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cmplid.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codepen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codiepie.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/confluence.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/connectdevelop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/contao.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cotton-bureau.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cpanel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-by.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-eu.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-jp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nd.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-remix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sa.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-share.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-zero.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/critical-role.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cuttlefish.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d-beyond.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dailymotion.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dashcube.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deezer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/delicious.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deploydog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deskpro.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dev.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deviantart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dhl.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/diaspora.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digg.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digital-ocean.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discord.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discourse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dochub.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/docker.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/draft2digital.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dribbble.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dropbox.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/drupal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dyalog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/earlybirds.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ebay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge-legacy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/elementor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ello.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ember.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/empire.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/envira.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/erlang.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ethereum.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/etsy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/evernote.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/expeditedssl.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-f.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-messenger.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fantasy-flight-games.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedex.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedora.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/figma.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox-browser.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firstdraft.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flickr.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flipboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fly.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/font-awesome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons-fi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/forumbee.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/foursquare.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/free-code-camp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/freebsd.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fulcrum.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-republic.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-senate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/get-pocket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg-circle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitkraken.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitlab.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide-g.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gofore.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/golang.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads-g.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-drive.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-pay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-play.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus-g.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-wallet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gratipay.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grav.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gripfire.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grunt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/guilded.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gulp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hacker-news.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hackerrank.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hashnode.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hips.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hire-a-helper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hive.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hooli.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hornbill.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hotjar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/houzz.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/html5.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hubspot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ideal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/imdb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instagram.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instalod.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/intercom.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/internet-explorer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/invision.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ioxhost.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itch-io.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes-note.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/java.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jedi-order.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jenkins.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jira.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joget.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joomla.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/js.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jsfiddle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kaggle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keybase.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keycdn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter-k.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/korvue.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/laravel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lastfm.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/leanpub.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/less.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin-in.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linode.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linux.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lyft.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/magento.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mailchimp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mandalorian.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/markdown.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mastodon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/maxcdn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mdb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medapps.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medium.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medrt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meetup.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/megaport.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mendeley.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meta.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microblog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microsoft.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixcloud.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mizuni.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/modx.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/monero.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/napster.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/neos.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-directional.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-symbol.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nimblr.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node-js.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/npm.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ns8.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nutritionix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/octopus-deploy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/odnoklassniki.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/old-republic.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opencart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/openid.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opera.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/optin-monster.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/orcid.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/osi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/padlet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/page4.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pagelines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/palfed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/patreon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/paypal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/perbyte.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/periscope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phabricator.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-framework.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-squadron.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/php.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-alt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-hat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-pp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest-p.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/playstation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/product-hunt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pushed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/python.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/qq.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quinscape.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quora.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/r-project.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/raspberry-pi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ravelry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/react.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reacteurope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/readme.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rebel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/red-river.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit-alien.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/redhat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/renren.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/replyd.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/researchgate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/resolving.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rev.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rocketchat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rockrms.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rust.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/safari.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/salesforce.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/schlix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/screenpal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/scribd.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/searchengin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellcast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellsy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/servicestack.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shirtsinbulk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopify.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopware.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/simplybuilt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sistrix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sith.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sitrox.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sketch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skyatlas.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skype.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slack.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slideshare.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/snapchat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/soundcloud.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sourcetree.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/space-awesome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speakap.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speaker-deck.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/spotify.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-behance.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-dribbble.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-facebook.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome-stroke.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-git.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-github.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-gitlab.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-google-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-hacker-news.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-instagram.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-js.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-lastfm.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-odnoklassniki.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pied-piper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pinterest.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-reddit.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-snapchat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-steam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-tumblr.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-twitter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-viadeo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-vimeo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-whatsapp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-xing.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-youtube.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/squarespace.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-exchange.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-overflow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stackpath.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/staylinked.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam-symbol.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sticker-mule.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/strava.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe-s.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/studiovinari.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon-circle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/superpowers.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/supple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/suse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/swift.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/symfony.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/teamspeak.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/telegram.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tencent-weibo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/the-red-yeti.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeco.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeisle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/think-peaks.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tiktok.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trade-federation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trello.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tumblr.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/typo3.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uber.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ubuntu.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uikit.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/umbraco.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uncharted.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uniregistry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unity.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unsplash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/untappd.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ups.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usps.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ussunnah.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vaadin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viacoin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viadeo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viber.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo-v.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vine.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vnv.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vuejs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/watchman-monitoring.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/waze.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weebly.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weibo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weixin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whatsapp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whmcs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wikipedia-w.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/windows.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wirsindhandwerk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wix.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wizards-of-the-coast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wodu.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wolf-pack-battalion.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpbeginner.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpexplorer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpforms.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpressr.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xbox.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xing.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/y-combinator.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yahoo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yammer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex-international.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yarn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yelp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yoast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/youtube.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/zhihu.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-book.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bookmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/building.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-days.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chart-bar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-bishop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-king.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-knight.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-pawn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-queen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-rook.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-dot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-pause.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-play.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-question.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-stop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clipboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/closed-captioning.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment-dots.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comments.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/compass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copyright.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/credit-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-angry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-dizzy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-flushed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grimace.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam-sweat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-hearts.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint-tears.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-stars.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tears.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-squint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-wink-heart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-squint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh-blank.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-rolling-eyes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-cry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-tear.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-surprise.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-tired.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-audio.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-code.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-excel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-image.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-lines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-pdf.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-powerpoint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-video.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-word.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-zipper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/floppy-disk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-closed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/font-awesome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/futbol.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/gem.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-back-fist.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-lizard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-peace.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-pointer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-scissors.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-spock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/handshake.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hard-drive.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/heart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hospital.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass-half.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-badge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/image.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/images.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/keyboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lemon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/life-ring.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lightbulb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/map.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/message.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/money-bill-1.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/moon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/newspaper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/note-sticky.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-group.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-ungroup.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paper-plane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paste.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/pen-to-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-list.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/registered.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/share-from-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/snowflake.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-full.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half-stroke.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/sun.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/trash-can.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-maximize.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-minimize.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-restore.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/0.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/1.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/2.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/3.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/4.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/5.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/6.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/7.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/8.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/9.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/a.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-book.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-center.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-justify.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ankh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/apple-whole.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/archway.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-1-9.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-9-1.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-a-z.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-short-wide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-across-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-wide-short.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-z-a.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-pointer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-arrow-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-from-bracket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-bracket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-city.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-1-9.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-9-1.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-a-z.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-bracket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-ground-water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-water-pump.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-dots.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-from-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-short-wide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-wide-short.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-z-a.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-people.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right-to-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-rotate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-spin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-split-up-and-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-circle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-dot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-eye.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-to-dots.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down-left-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-to-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/asterisk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/at.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/atom.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/audio-description.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/austral-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/award.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/b.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby-carriage.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-fast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-step.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacteria.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacterium.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bag-shopping.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bahai.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baht-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban-smoking.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bandage.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/barcode.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-progress.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-staggered.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball-bat-ball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basket-shopping.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basketball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bath.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-empty.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-full.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-half.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-quarter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-three-quarters.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed-pulse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/beer-mug-empty.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-concierge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bezier-curve.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bicycle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/binoculars.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/biohazard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bitcoin-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender-phone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bold.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt-lightning.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bomb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bong.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-atlas.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bible.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bookmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-journal-whills.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open-reader.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-quran.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-skull.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-tanakh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bookmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-all.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-none.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-top-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bore-hole.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-droplet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-food.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-rice.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowling-ball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-archive.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-tissue.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-packing.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-stacked.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/braille.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brain.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brazilian-real-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bread-slice.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom-ball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brush.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bucket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bugs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-columns.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-ngo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-shield.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-un.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-wheat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullhorn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullseye.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burger.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burst.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/business-time.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/c.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cable-car.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cake-candles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calculator.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-day.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-days.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-week.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-retro.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-rotate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/campground.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/candy-cane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cannabis.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/capsules.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-battery.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-burst.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-on.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-rear.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-side.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-tunnel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caravan.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/carrot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-arrow-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed-suitcase.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-shopping.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cash-register.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cedi-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cent-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/certificate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chair.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/champagne-glasses.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/charging-station.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-area.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-bar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-column.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-gantt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-pie.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-double.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-to-slot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cheese.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-bishop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-board.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-king.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-knight.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-pawn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-queen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-rook.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-dress.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-reaching.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-rifle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/children.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/church.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dollar-to-slot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-h.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-half-stroke.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-info.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-nodes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-notch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-pause.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-play.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-question.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-radiation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-stop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/city.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clapperboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-list.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-question.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock-rotate-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/closed-captioning.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-bolt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-meatball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon-rain.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-rain.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-heavy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun-rain.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clover.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-branch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-commit.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-compare.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-fork.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-merge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-pull-request.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/coins.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/colon-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dots.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-sms.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compact-disc.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass-drafting.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compress.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer-mouse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie-bite.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copyright.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/couch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/credit-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cross.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crosshairs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crown.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crutch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cruzeiro-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cube.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes-stacked.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/d.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/database.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/delete-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/democrat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/desktop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dharmachakra.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-next.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-predecessor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-project.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-successor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond-turn-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d20.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d6.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-five.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-four.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-one.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-six.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-three.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-two.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/disease.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/display.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/divide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dna.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dollar-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dolly.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dong-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-closed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dove.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-left-and-up-right-to-center.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/download.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dragon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/draw-polygon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum-steelpan.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drumstick-bite.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumbbell.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster-fire.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dungeon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/e.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-deaf.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-listen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-africa.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-americas.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-asia.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-europe.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-oceania.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/egg.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eject.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/elevator.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis-vertical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open-text.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelopes-bulk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/equals.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eraser.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ethernet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/euro-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/expand.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/explosion.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-dropper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-low-vision.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/f.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-angry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-dizzy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-flushed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grimace.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam-sweat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-hearts.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint-tears.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-stars.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tears.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-squint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-wink-heart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-squint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh-blank.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-rolling-eyes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-cry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-tear.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-beam.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-wink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-surprise.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-tired.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fan.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet-drip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fax.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather-pointed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ferry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-audio.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-question.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-code.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-contract.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-csv.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-excel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-export.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-image.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-import.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-lines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pdf.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-powerpoint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-prescription.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-shield.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-signature.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-video.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-waveform.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-word.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-zipper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill-drip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/film.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fingerprint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-burner.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-extinguisher.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-curved.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish-fins.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-checkered.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-usa.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask-vial.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/floppy-disk.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/florin-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-closed.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-tree.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font-awesome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/football.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-fast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-step.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/franc-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/frog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/futbol.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/g.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gamepad.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gas-pump.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-high.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple-high.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gavel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gear.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gears.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gem.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/genderless.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ghost.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gift.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gifts.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water-droplet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glasses.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/globe.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/golf-ball-tee.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gopuram.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/graduation-cap.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than-equal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines-vertical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-vertical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/group-arrows-rotate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guarani-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guitar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gun.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/h.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hammer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hamsa.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-back-fist.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-dots.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-fist.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-droplet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-hand.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-heart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-lizard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-middle-finger.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-peace.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-pointer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-scissors.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-sparkles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-spock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handcuffs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-asl-interpreting.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bound.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bubbles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-clapping.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-child.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-circle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-praying.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-angle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hanukiah.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hard-drive.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hashtag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy-side.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-wizard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-mask.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-virus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heading.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headset.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-bolt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-crack.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-pulse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter-symbol.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-safety.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-un.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/highlighter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-avalanche.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-rockslide.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hippo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hockey-puck.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/holly-berry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse-head.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hot-tub-person.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotdog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-end.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-half.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-start.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-crack.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-window.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-crack.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-fire.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water-circle-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-laptop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-signal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-tsunami.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hryvnia-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hurricane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i-cursor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ice-cream.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icicles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icons.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-badge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card-clip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/igloo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image-portrait.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/images.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/inbox.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indent.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indian-rupee-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/industry.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/infinity.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/info.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/italic.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/j.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar-wheat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jedi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/joint.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jug-detergent.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/k.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kaaba.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/key.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/keyboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/khanda.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kip-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kit-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kitchen-set.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kiwi-bird.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/l.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/land-mine-on.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-dome.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/language.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-code.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-file.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lari-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/layer-group.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/leaf.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lemon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than-equal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/life-ring.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lightbulb.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lines-leaning.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lira-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ol.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ul.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/litecoin-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-arrow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-crosshairs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-dot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/locust.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs-virus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/m.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-chart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-location.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/manat-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location-dot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-pin.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/marker.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus-burst.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-double.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-citrus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-empty.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-face.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-ventilator.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/masks-theater.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mattress-pillow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/maximize.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/medal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/memory.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/menorah.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mercury.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/message.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/meteor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microchip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microscope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mill-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minimize.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mitten.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-button.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-retro.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen-button.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1-wave.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-transfer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-trend-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wave.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wheat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bills.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/monument.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/moon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mortar-pestle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosque.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito-net.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/motorcycle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mound.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-city.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-sun.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-hot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-saucer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/music.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/n.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/naira-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/network-wired.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/neuter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/newspaper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/not-equal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/note-sticky.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/notes-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/o.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-group.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-ungroup.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-can.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-well.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/om.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/otter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/outdent.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/p.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pager.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paint-roller.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paintbrush.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/palette.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pallet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/panorama.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paper-plane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paperclip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/parachute-box.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paragraph.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/passport.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paste.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pause.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paw.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peace.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-clip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-fancy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-nib.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-ruler.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-to-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pencil.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-arrows.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-carry-box.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-group.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-pulling.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-robbery.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-roof.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pepper-hot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/percent.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-down-to-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-up-from-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-biking.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-booth.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-breastfeeding.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-burst.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-cane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-chalkboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-question.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-digging.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dots-from-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress-burst.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-drowning.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling-burst.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-half-dress.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-harassing.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-hiking.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-pointing.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-rifle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-to-person.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-praying.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-pregnant.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rays.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rifle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-running.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-shelter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skating.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing-nordic.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-snowboarding.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-swimming.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-through-window.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-loop-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-dashed-line-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-luggage.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-with-cane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peseta-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peso-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-flip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-volume.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/photo-film.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/piggy-bank.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pills.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pizza-slice.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/place-of-worship.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-arrival.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-departure.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plant-wilt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plate-wheat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/play.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-bolt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/podcast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo-storm.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/power-off.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/print.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-soap.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/puzzle-piece.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/q.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/qrcode.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/question.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/r.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radiation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radio.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rainbow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ranking-star.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/receipt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/record-vinyl.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-ad.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-list.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/recycle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/registered.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/repeat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply-all.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/republican.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/restroom.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/retweet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ribbon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-from-bracket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-to-bracket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ring.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-barrier.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-bridge.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-spikes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/robot.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rocket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/route.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rss.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruble-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rug.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-combined.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-horizontal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-vertical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupee-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupiah-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/s.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-dollar.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sailboat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite-dish.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-balanced.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced-flip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-flag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scissors.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver-wrench.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll-torah.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sd-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/section.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/seedling.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/server.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shapes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-from-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-nodes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sheet-plastic.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shekel-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-cat.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-dog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-halved.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-heart.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-virus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ship.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shirt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shoe-prints.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shower.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shrimp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuffle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuttle-space.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sign-hanging.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signature.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signs-post.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sim-card.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sink.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sitemap.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull-crossbones.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sleigh.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sliders.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smog.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smoking.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowflake.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowman.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowplow.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/soap.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/socks.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/solar-panel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spa.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spaghetti-monster-flying.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spell-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spider.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spinner.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/splotch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spoon.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can-sparkles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-arrow-up-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-envelope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-full.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-h.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-nfi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-parking.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-pen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-person-confined.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone-flip.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-horizontal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-vertical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-root-variable.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-rss.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-share-nodes.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-up-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-virus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/staff-snake.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stairs.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stamp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stapler.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-and-crescent.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half-stroke.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-david.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-life.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sterling-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stethoscope.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stop.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch-20.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/street-view.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/strikethrough.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stroopwafel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/subscript.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-rolling.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun-plant-wilt.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/superscript.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/swatchbook.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/synagogue.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/syringe.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/t.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells-large.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-columns.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-list.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-tennis-paddle-ball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-button.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-screen-button.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablets.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tachograph-digital.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tags.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tape.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp-droplet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/taxi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth-open.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-empty.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-full.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-half.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-high.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-low.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-quarter.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-three-quarters.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tenge-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-down-to-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-left-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-turn-left.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrows-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tents.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/terminal.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-height.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-width.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thermometer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbtack.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket-simple.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/timeline.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-off.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-on.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-portable.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilets-portable.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toolbox.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tooth.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/torii-gate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tornado.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-broadcast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-cell.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-observation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tractor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trademark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/traffic-light.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trailer.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-subway.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-tram.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/transgender.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can-arrow-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree-city.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/triangle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trophy.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel-bricks.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-arrow-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-droplet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-fast.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field-un.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-front.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-medical.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-monster.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-moving.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-pickup.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-plane.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-ramp-box.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tty.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turkish-lira-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-up.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tv.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/u.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella-beach.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/underline.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/universal-access.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock-keyhole.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down-left-right.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-long.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-and-down-left-from-center.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-from-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/upload.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-astronaut.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-clock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-doctor.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-gear.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-graduate.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-group.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-injured.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-lock.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-minus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-ninja.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-nurse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-pen.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-plus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-secret.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-shield.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tag.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tie.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-between-lines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-gear.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-line.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rays.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rectangle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-viewfinder.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/utensils.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/v.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/van-shuttle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vault.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vector-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-double.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-mars.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest-patches.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-circle-check.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-virus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vials.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vihara.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-slash.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/viruses.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/voicemail.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volcano.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volleyball.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-high.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-low.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-off.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vr-cardboard.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/w.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/walkie-talkie.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wallet.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic-sparkles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-sparkles.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/warehouse.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water-ladder.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wave-square.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-hanging.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-scale.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn-circle-exclamation.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair-move.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/whiskey-glass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wifi.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wind.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-maximize.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-minimize.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-restore.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-bottle.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass-empty.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/won-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/worm.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wrench.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x-ray.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmark.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmarks-lines.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/y.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yen-sign.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yin-yang.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/z.svg (100%) rename crates/{ => lib}/font-awesome-as-a-crate/released.sh (100%) rename crates/{ => lib}/font-awesome-as-a-crate/src/lib.rs (100%) rename crates/{ => lib}/metadata/Cargo.toml (100%) rename crates/{ => lib}/metadata/build.rs (100%) rename crates/{ => lib}/metadata/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index 2f0fd6a94..0fd6137c0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,10 @@ edition = "2024" [workspace] resolver = "2" -members = ["crates/*"] +members = [ + "crates/lib/*", + "crates/bin/*" +] [workspace.dependencies] anyhow = { version = "1.0.42", features = ["backtrace"]} diff --git a/crates/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml similarity index 100% rename from crates/docs_rs_builder/Cargo.toml rename to crates/bin/docs_rs_builder/Cargo.toml diff --git a/crates/docs_rs_builder/src/config.rs b/crates/bin/docs_rs_builder/src/config.rs similarity index 100% rename from crates/docs_rs_builder/src/config.rs rename to crates/bin/docs_rs_builder/src/config.rs diff --git a/crates/docs_rs_builder/src/db/add_package.rs b/crates/bin/docs_rs_builder/src/db/add_package.rs similarity index 100% rename from crates/docs_rs_builder/src/db/add_package.rs rename to crates/bin/docs_rs_builder/src/db/add_package.rs diff --git a/crates/docs_rs_builder/src/db/blacklist.rs b/crates/bin/docs_rs_builder/src/db/blacklist.rs similarity index 100% rename from crates/docs_rs_builder/src/db/blacklist.rs rename to crates/bin/docs_rs_builder/src/db/blacklist.rs diff --git a/crates/docs_rs_builder/src/db/mod.rs b/crates/bin/docs_rs_builder/src/db/mod.rs similarity index 100% rename from crates/docs_rs_builder/src/db/mod.rs rename to crates/bin/docs_rs_builder/src/db/mod.rs diff --git a/crates/docs_rs_builder/src/docbuilder/mod.rs b/crates/bin/docs_rs_builder/src/docbuilder/mod.rs similarity index 100% rename from crates/docs_rs_builder/src/docbuilder/mod.rs rename to crates/bin/docs_rs_builder/src/docbuilder/mod.rs diff --git a/crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs similarity index 100% rename from crates/docs_rs_builder/src/docbuilder/rustwide_builder.rs rename to crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs diff --git a/crates/docs_rs_builder/src/lib.rs b/crates/bin/docs_rs_builder/src/lib.rs similarity index 100% rename from crates/docs_rs_builder/src/lib.rs rename to crates/bin/docs_rs_builder/src/lib.rs diff --git a/crates/docs_rs_builder/src/metrics.rs b/crates/bin/docs_rs_builder/src/metrics.rs similarity index 100% rename from crates/docs_rs_builder/src/metrics.rs rename to crates/bin/docs_rs_builder/src/metrics.rs diff --git a/crates/docs_rs_builder/src/utils/copy.rs b/crates/bin/docs_rs_builder/src/utils/copy.rs similarity index 100% rename from crates/docs_rs_builder/src/utils/copy.rs rename to crates/bin/docs_rs_builder/src/utils/copy.rs diff --git a/crates/docs_rs_builder/src/utils/mod.rs b/crates/bin/docs_rs_builder/src/utils/mod.rs similarity index 100% rename from crates/docs_rs_builder/src/utils/mod.rs rename to crates/bin/docs_rs_builder/src/utils/mod.rs diff --git a/crates/docs_rs_builder/src/utils/queue_builder.rs b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs similarity index 100% rename from crates/docs_rs_builder/src/utils/queue_builder.rs rename to crates/bin/docs_rs_builder/src/utils/queue_builder.rs diff --git a/crates/docs_rs_builder/src/utils/version.rs b/crates/bin/docs_rs_builder/src/utils/version.rs similarity index 100% rename from crates/docs_rs_builder/src/utils/version.rs rename to crates/bin/docs_rs_builder/src/utils/version.rs diff --git a/crates/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml similarity index 100% rename from crates/docs_rs_watcher/Cargo.toml rename to crates/bin/docs_rs_watcher/Cargo.toml diff --git a/crates/docs_rs_watcher/src/build_queue.rs b/crates/bin/docs_rs_watcher/src/build_queue.rs similarity index 100% rename from crates/docs_rs_watcher/src/build_queue.rs rename to crates/bin/docs_rs_watcher/src/build_queue.rs diff --git a/crates/docs_rs_watcher/src/config.rs b/crates/bin/docs_rs_watcher/src/config.rs similarity index 100% rename from crates/docs_rs_watcher/src/config.rs rename to crates/bin/docs_rs_watcher/src/config.rs diff --git a/crates/docs_rs_watcher/src/consistency/data.rs b/crates/bin/docs_rs_watcher/src/consistency/data.rs similarity index 100% rename from crates/docs_rs_watcher/src/consistency/data.rs rename to crates/bin/docs_rs_watcher/src/consistency/data.rs diff --git a/crates/docs_rs_watcher/src/consistency/db.rs b/crates/bin/docs_rs_watcher/src/consistency/db.rs similarity index 100% rename from crates/docs_rs_watcher/src/consistency/db.rs rename to crates/bin/docs_rs_watcher/src/consistency/db.rs diff --git a/crates/docs_rs_watcher/src/consistency/diff.rs b/crates/bin/docs_rs_watcher/src/consistency/diff.rs similarity index 100% rename from crates/docs_rs_watcher/src/consistency/diff.rs rename to crates/bin/docs_rs_watcher/src/consistency/diff.rs diff --git a/crates/docs_rs_watcher/src/consistency/index.rs b/crates/bin/docs_rs_watcher/src/consistency/index.rs similarity index 100% rename from crates/docs_rs_watcher/src/consistency/index.rs rename to crates/bin/docs_rs_watcher/src/consistency/index.rs diff --git a/crates/docs_rs_watcher/src/consistency/mod.rs b/crates/bin/docs_rs_watcher/src/consistency/mod.rs similarity index 100% rename from crates/docs_rs_watcher/src/consistency/mod.rs rename to crates/bin/docs_rs_watcher/src/consistency/mod.rs diff --git a/crates/docs_rs_watcher/src/db/delete.rs b/crates/bin/docs_rs_watcher/src/db/delete.rs similarity index 100% rename from crates/docs_rs_watcher/src/db/delete.rs rename to crates/bin/docs_rs_watcher/src/db/delete.rs diff --git a/crates/docs_rs_watcher/src/db/mod.rs b/crates/bin/docs_rs_watcher/src/db/mod.rs similarity index 100% rename from crates/docs_rs_watcher/src/db/mod.rs rename to crates/bin/docs_rs_watcher/src/db/mod.rs diff --git a/crates/docs_rs_watcher/src/index.rs b/crates/bin/docs_rs_watcher/src/index.rs similarity index 100% rename from crates/docs_rs_watcher/src/index.rs rename to crates/bin/docs_rs_watcher/src/index.rs diff --git a/crates/docs_rs_watcher/src/lib.rs b/crates/bin/docs_rs_watcher/src/lib.rs similarity index 100% rename from crates/docs_rs_watcher/src/lib.rs rename to crates/bin/docs_rs_watcher/src/lib.rs diff --git a/crates/docs_rs_watcher/src/main.rs b/crates/bin/docs_rs_watcher/src/main.rs similarity index 100% rename from crates/docs_rs_watcher/src/main.rs rename to crates/bin/docs_rs_watcher/src/main.rs diff --git a/crates/docs_rs_watcher/src/priorities.rs b/crates/bin/docs_rs_watcher/src/priorities.rs similarity index 100% rename from crates/docs_rs_watcher/src/priorities.rs rename to crates/bin/docs_rs_watcher/src/priorities.rs diff --git a/crates/docs_rs_watcher/src/rebuilds.rs b/crates/bin/docs_rs_watcher/src/rebuilds.rs similarity index 100% rename from crates/docs_rs_watcher/src/rebuilds.rs rename to crates/bin/docs_rs_watcher/src/rebuilds.rs diff --git a/crates/docs_rs_watcher/src/service_metrics.rs b/crates/bin/docs_rs_watcher/src/service_metrics.rs similarity index 100% rename from crates/docs_rs_watcher/src/service_metrics.rs rename to crates/bin/docs_rs_watcher/src/service_metrics.rs diff --git a/crates/docs_rs_watcher/src/utils.rs b/crates/bin/docs_rs_watcher/src/utils.rs similarity index 100% rename from crates/docs_rs_watcher/src/utils.rs rename to crates/bin/docs_rs_watcher/src/utils.rs diff --git a/crates/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml similarity index 100% rename from crates/docs_rs_web/Cargo.toml rename to crates/bin/docs_rs_web/Cargo.toml diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/JavaScript (Babel).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/.gitignore diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.YAML-tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/LICENSE diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/README.md b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/README.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/README.md rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/README.md diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/TOML.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml b/crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml rename to crates/bin/docs_rs_web/assets/syntaxes/Extras/TOML/syntax_test_toml.toml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/01-bug-syntax-highlighting.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/02-bug-file-indexing.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/ISSUE_TEMPLATE/03-supporting-files.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/01-syntax-update-small.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/02-syntax-update-significant.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.github/PULL_REQUEST_TEMPLATE/03-supporting-files.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/.travis.yml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/.travis.yml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/.travis.yml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/.travis.yml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/ASP.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/HTML-ASP.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Indexed Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ASP/syntax_test_asp.asp diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ActionScript/ActionScript.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ActionScript/syntax_test_as.as diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/AppleScript/AppleScript.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/AppleScript/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Batch File.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Batch File/syntax_test_batch_file.bat diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Build.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/C#.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Indentation.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Constructors.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Enums.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Index Constructors.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Inner Function.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Interfaces.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Namespace.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Region.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/Symbol List Structs.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/doc_params.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/doc_see.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/doc_summary.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_C#7.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Comments.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_GeneralStructure.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Generics.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_HelloWorld.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Operators.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_PreprocessorDirectives.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Strings.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_Using.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_c#.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C#/tests/syntax_test_query.cs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C Single File.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C Standard Includes.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++ Single File.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++ Standard Includes.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C++.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/C.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Comments (C++).tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#ifndef-#define-#endif.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc angle).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(#inc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc angle).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/#include-(inc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/$1.begin()-$1.end()-(beginend).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/030-for-int-loop-(fori).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Enumeration.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/Typedef.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/class-..-(class).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/do...while-loop-(do).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/forv.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/fprintf.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/if-..-(if).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(int main).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/main()-(main).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/namespace-..-(namespace).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/printf-..-(printf).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/read-file-(readF).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-map-(map).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/std-vector-(v).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/struct.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Snippets/template-typename-..-(template).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Hide Ctors.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index Include Constants.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol Index.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Indent Class Methods.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Namespace Spacing.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List - Prefix Banner Items.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List Hide Forward Decls.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.c diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_accessor.cpp diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_c.c diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp b/crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/C++/syntax_test_cpp.cpp diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/CSS.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol Index.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List Group.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/CSS/css_completions.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Clojure.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/ClojureSymbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Comment.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure.clj diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Clojure/tests/syntax_test_clojure_old.clj diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/D dub.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/D.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/DMD Output.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/constant.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/critical.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/debugm.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/enum.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error-format.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/error.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/fatal.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach-reverse.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/foreach.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if-else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/import.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/info.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log-format.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/log.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main-with-args.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/main.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/return.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/struct.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/trace.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch-finally.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-catch.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/try-finally.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/unittest.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/version.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/warning.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Snippets/while.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Symbol Index Hide Special Functions.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_d.d diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_old.d diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d b/crates/bin/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/D/tests/syntax_test_shebang.d diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/Context.sublime-menu diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/Diff.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/Side Bar.sublime-menu diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/diff.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Diff/syntax_test_diff.diff diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Erlang.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/HTML (Erlang).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Reference List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Indexed Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Behaviour-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Case-Expression.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Define-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Export-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Fun-Expression.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/If-Expression.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifdef-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Ifndef-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Import-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Include-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Module-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Receive-Expression.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Record-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Try-Expression.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Snippets/Undef-Directive.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Exports.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Function Specification.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Imports.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Macro.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Record.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/Symbol List - Type.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.erl diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Erlang/syntax_test_erlang.yaws diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Attributes.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Diff.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - EOL.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Encoding.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Filter.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Merge.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Text.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Completions/Git Attributes - Whitespace.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Attributes.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Commit.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Common.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config - Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Config.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Ignore.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Link.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Log.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap - Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Mailmap.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Git Rebase.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/Snippets/Git Config - Section.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_attributes diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_commit diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_config diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_ignore diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_link diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_log diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_mailmap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_merge diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_rebase diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Git Formats/syntax_test_git_tag diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Go.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/GoCommentRules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoCommentIndent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoIndent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Indents/GoStringIndent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-defun.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-fori.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-gofun.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Snippets/go-iferr.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoConstSymbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoFuncSymbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoTypeSymbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/Symbols/GoVarSymbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Go/syntax_test_go.go diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attribute Values.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Attributes.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Graphs.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Completions/Objects.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/DOT.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Graphviz.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Graphviz/syntax_test_dot.dot diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Groovy.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/#!-usr-local-bin-groovy-w.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Ant-__-replace.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Block-Comment.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Constructor.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Hash-Pair.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_start-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/Thread_startDaemon-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/all{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/any{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigDecimal.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-BigInteger.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Double.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Float.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Immutable.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Set.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-String.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Synchronized.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/as-Writable.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assert(__).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertEquals(__).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertFalse.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotEquals(__).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNotNull(__).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertNull(__).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertSame.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/assertTrue.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__-singleton.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-__.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/class-___-TestCase.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/collect-{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-file.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset-include-exclude.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/copy__-fileset.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-closure-=-{__}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/def-__-method()-{__}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/downto(num)-{-n-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/each-{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachByte-{-byte-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDir-{-dir-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirMatch.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachDirRecurse.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFile-{-file-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileMatch-{-file-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachFileRecurse-{-file-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachKey-{-key-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachLine-{-line-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachMatch(regex)-{-match-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachObject-{-obj-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachValue-{-val-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/eachWithIndex-{-e-i-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/elseif-___.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/every-{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/final-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/find-{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/findAll-{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/for-in.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/grep(-pattern-)-{-match-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if-else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/import.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/mkdir.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/new-File(__)_eachLine-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/package.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/print.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/println.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-final-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-String.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-final-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-static-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/private-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/replaceAll(regex)-{-match-__}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/reverseEach-{-e-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/run-after.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/setUp().sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/shouldFail(__)-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs)-{-__-on-interrupt-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sleep(secs).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/sort-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/splitEachLine(separator)-{-line-__-}-copy.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-final-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-main-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/static-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/step(to-amount)-{-n-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/switch__case__default.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/tearDown().sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/test-case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/times-{-n-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Array.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigDecimal.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-BigInteger.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Boolean.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Character.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Double.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Float.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-Integer.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-List.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-String.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URI.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/to-URL.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__-finally.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/try-__-catch__.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/upto(num)-{-n-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/while-___-{___}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withInputStream-{-in-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withOutputStream-{-out-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withPrintWriter-{-pw-__}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withReader-{-r-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStream-{-in-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withStreams-{-Socket-s-__}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter(charset)-{-w-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriter-{-w-__}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Snippets/withWriterAppend(charset)-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Class Variables.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Methods.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/Symbol List%3A Variables.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/syntax_test_groovy.groovy diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Groovy/tests/syntax_test_Strings.groovy diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/HTML.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html (begin tag).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Snippets/html.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/Symbol List - ID.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/encode_html_entities.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/html_completions.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html b/crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/HTML/syntax_test_html.html diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Haskell.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Indent Patterns.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Literate Haskell.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Instance.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Lambda.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/Main.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Snippets/module.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Haskell/syntax_test_haskell.hs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JSON/JSON Indent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JSON/JSON.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JSON/syntax_test_json.json diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Ant.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Comments - Properties.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules Annex.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Indexed Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Java Server Pages (JSP).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Java.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/JavaC.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/JavaDoc.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/JavaProperties.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/abstract.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/assert.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/break.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/catch.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant-string.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/constant.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/default.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/final.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for-(each).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/for.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import-junit_framework_TestCase;.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/import.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/interface.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_beans_.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_io.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_math.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_net_.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/java_util_.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method-(main).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/package.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/print.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/println.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/private.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/protected.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/public.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/return.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/static.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/switch.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/synchronized.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test-case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/test.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/throw.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/variable.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Snippets/while.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Constants.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Class Methods.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Class Methods.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Inner Inner Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Method.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Modules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/Symbol List - Properties.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java.java diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_java_properties.properties diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Java/syntax_test_jsp.jsp diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Indexed Symbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript Indent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/JavaScript.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Regular Expressions (JavaScript).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Get-Elements.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-Value-JS.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Object-key-key-value.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/Prototype-(proto).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}-(faster).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/for-()-{}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function-(fun).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if-___-else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Snippets/setTimeout-function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Banned.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/Symbol List Function.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_bindings.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_regexp.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_builtin.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_console.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_dom.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js b/crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/JavaScript/tests/syntax_test_js_support_node.js diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LICENSE b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LICENSE similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LICENSE rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LICENSE diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Bibtex.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX Log.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/LaTeX.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Cases.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Chapter.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Description.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Displaymath-($$).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Enumerate.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Equation.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Figure.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Item[description].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Itemize.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Listing.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Matrix.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Page.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Paragraph.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Part.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Section.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Split.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Sub-Paragraph.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Table.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/Tabular.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/begin{}-end{}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/section-..-(section).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsection-..-(sub).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Snippets/subsubsection-..-(ssub).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Commands.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Labels.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/Symbol List - Sections.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/TeX.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex b/crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/LaTeX/syntax_test_latex.tex diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Lisp.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/'(.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defconstant.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defmacro.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defparameter.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defun.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/defvar.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/let1.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/Snippets/setf.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lisp/syntax_test_lisp.lisp diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Indent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Lua.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i-v-in-ipairs().sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-i=1-10.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/for-k-v-in-pairs().sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(fun).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/function-(function).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/local-x-=-1.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.concat.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Snippets/table.sort.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua.lua diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Lua/tests/syntax_test_lua_support.lua diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Make Output.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Make.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Makefile.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Makefile/syntax_test_makefile.mak diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Indent%3A Raw.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Markdown.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/MultiMarkdown.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Heading.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/Symbol List - Reference Link.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_markdown.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Markdown/syntax_test_multimarkdown.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Indent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Matlab.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/Octave-function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/^.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/clear.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp-sprintf.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/disp.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/dlmwrite.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/elseif.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/error.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/exp.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/fprintf.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/get.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/griddata.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/if-elseif.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/line.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/set.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/small-function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/sprintf.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/switch___case___otherwise___end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/title.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unix.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/unwind_protect-cleanup-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/warning.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/while.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xlabel.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/xtick.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ylabel.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/ytick.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Snippets/zlabel.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/Symbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Matlab/syntax_test_matlab.m diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Indent rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/OCaml.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamllex.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/OCamlyacc.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/Document.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/For-Loop.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/While-Loop.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/begin.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/fun.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/func.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/function-label.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let-in.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/let.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match-pattern.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/match.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/method-(method).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-signature.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module-type.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/module.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/try.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/type-(type).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Snippets/untitled.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Exceptions.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex pattern references.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamllex rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc non-terminal reference.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Ocamlyacc token reference.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Types.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List%3A Variants.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Classes.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Exceptions.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex pattern references.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamllex rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc non-terminal reference.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token definition.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Ocamlyacc token reference.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Types.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/Symbol List_ Variants.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/camlp4.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/OCaml/syntax_test_ml.ml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C++.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Objective-C.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index Include Constants.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/Symbol Index.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.m diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_accessor.mm diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc++.mm diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Objective-C/syntax_test_objc.m diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules - heredoc end.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules Annex.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/PHP Source.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-completions diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/PHP.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Regular Expressions (PHP).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$GLOBALS[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_COOKIE[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_ENV[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_FILES[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_GET[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_POST[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_REQUEST[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SERVER[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/$_SESSION[''].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Constructor.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class-var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-constant-definition.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function-signature.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/PHPDoc-interface.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/Start-Docblock.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/class-{-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/define(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/defined(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/do-while(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/echo-___.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/elseif(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/for(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/foreach(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/function-xx(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-)-else(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/if-a-b;.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/include_once(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/new-array(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-$this.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-$this.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-___.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-echo-htmlentities(___).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-foreach-(___)-___-php-endforeach.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-else-___-php-endif.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php-if-(___)-___-php-endif.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/php.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/require_once(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-$retVal;.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-FALSE;.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/return-TRUE;.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-)-case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/switch(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/throw.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/try-{-___-}-catch-(___)-{-___-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Snippets/while(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php b/crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/PHP/syntax_test_php.php diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Pascal/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Pascal/Pascal.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Pascal/syntax_test.pas diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Perl.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if-(if).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..else-(ife).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-if..elsif..else-(ifee).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xif).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xunless).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-one-line-(xwhen).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless-(unless).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..else-(unlesse).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-unless..elsif..else-(unlessee).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Conditional-when-(when).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Function-(sub).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-for-(for).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-foreach-(fore).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfor).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xfore).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xuntil).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-one-line-(xwhile).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Loop-while-(while).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/Test.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/eval.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/Snippets/slurp.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Perl/syntax_test_perl.pl diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Python.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Regular Expressions (Python).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/New-Property.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else-Finally.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except-Finally.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/Try-Except.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/__magic__.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/for.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if-__name__-==-'__main__'.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/method.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Snippets/while.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Symbol Index.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Python/syntax_test_python_strings.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R Console.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/R.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Rd (R Documentation).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Add-Tick-Marks.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Attach.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Cummulative.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Density.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Detach.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Divide-Into-Intervals.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Factor.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/For-Loop.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Function.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Ifelse.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Length.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Load-Dataset.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Polygonal-Line.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Read-From-File.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sequence-(from-to-by).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Sort.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/Source.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Snippets/na_omit.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Methods.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections (Rd Documentation).tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/Symbol List - Sections.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R b/crates/bin/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/R/syntax_test_r.R diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/README.md b/crates/bin/docs_rs_web/assets/syntaxes/Packages/README.md similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/README.md rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/README.md diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/HTML (Rails).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/JavaScript (Rails).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby Haml.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Ruby on Rails.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/SQL (Rails).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/$LABEL.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/%3C%=-Fixtures_identify(%3Asymbol)-%%3E.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/180-rails-form_tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-binary-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-boolean-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-controller-class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-date-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-datetime-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-decimal-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-float-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-functional-test-class.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-integer-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-lock_version-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-references-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-string-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-text-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-time-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamp-column.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Create-timestamps-columns.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-(mcc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Create-Column-Continue-(mccc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Drop-Create-Table-(mdct).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Migration-Remove-and-Add-Column-(mrac).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/RAILS_DEFAULT_LOGGER.debug-(rdb).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Table-column(s)-rename.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Redirected-To-(art).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/Test-Assert-Response-(are).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_create.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_destroy.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_save.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_update.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_create.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/after_validation_on_update.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert(var-=-assigns(%3Avar)).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_difference.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_no_difference.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(nested-path-plural).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_redirected_to-(path-plural).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/assert_select.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_create.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_destroy.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_save.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_update.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_create.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/before_validation_on_update.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/belongs_to-(bt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/cattr_accessor.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-create-resource.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-get-request.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/def-post-request.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/find(id).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/for-loop-erb.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-check_box.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-checkbox.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-file_field.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-hidden_field.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-label.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-password_field.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-radio_box.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-submit.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_area.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-text_field.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for-with-errors.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/form_for.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_and_belongs_to_many-(habtm).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(hm).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-(through).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_many-dependent-=-destroy.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/has_one-(ho).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/image_submit_tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/javascript_include_tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lia.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/liai.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lic.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/lica.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/licai.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(nested-path-plural).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-(path-plural).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/link_to-model.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_debug.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_error.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_fatal.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_info.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/logger_warn.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map(-%3Asym_proc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_catch_all.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_named_route.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resource.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_resources.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/map_with_options.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/mattr_accessor.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope-lambda.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/named_scope.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rails-flash.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rea.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reai.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/rec.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/reca.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/recai.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(nested-path-plural).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/redirect_to-(path-plural).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action)...-(ra).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(action-layout)-(ral).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file)-(rf).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(file-use_full_path)-(rfu).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline)-(ri).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-locals)-(ril).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(inline-type)-(rit).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(layout)-(rl).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing)-(rn).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(nothing-status)-(rns).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial)-(rp).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-collection)-(rpc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-locals)-(rpl).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-object)-(rpo).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(partial-status)-(rps).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text)-(rt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout)-(rtl).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-layout=%3Etrue)-(rtlt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(text-status)-(rts).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/render-(update).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/respond_to.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/returning-do-%7Cvariable%7C-%E2%80%A6-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/stylesheet_link_tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/submit_tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_binary-(tcbi).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_boolean-(tcb).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_date-(tcda).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_datetime-(tcdt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_decimal-(tcd).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_float-(tcf).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_integer-(tci).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_lock_version-(tcl).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_references-(tcr).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_rename-(tre).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_string-(tcs).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_text-(tct).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_time-(tcti).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamp-(tcts).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/t_timestamps-(tctss).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_acceptance_of.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-(va).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_associated-if-(vaif).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-(vc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_confirmation_of-if-(vcif).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-(ve).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_exclusion_of-if-(veif).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_format_of.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_inclusion_of.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-(vl).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_length_of-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_numericality_of.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-(vp).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_presence_of-if-(vpif)-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-(vu).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/validates_uniqueness_of-if-(vuif).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-(verify).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/verify-redirect-(verify).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/wants_format.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-delete.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-get.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-post.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Snippets/xhr-put.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Template (ERB).tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/Template (Haml).tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_html_rails.html.erb diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rails/syntax_test_rails.rb diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/RegExp.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Regular Expressions/syntax_test_regexp.re diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/RestructuredText/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/RestructuredText/reStructuredText.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst b/crates/bin/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/RestructuredText/syntax_test_restructuredtext.rst diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Ruby.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/#!;usr;local;bin;ruby-w.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/060-ruby-if-else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/070-ruby-if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/080-ruby-case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Add-'#-=-'-Marker.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Array.new(10)-{-i-..-}-(Arr).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Benchmark_bmbm(__)-do-__-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir.glob(-..-)-do-file-..-end-(Dir).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Dir[-__-].sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File.foreach-(-..-)-do-line-..-end-(File).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_open(-__-)-{-file-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/File_read(-__-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Hash.new-{-hash-key-hash[key]-=-..-}-(Has).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.dump(obj-file)-(Md).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Marshal.load(obj)-(Ml).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/PStore_new(-__-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/RDoc-documentation-block.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/Wrap-in-Begin-Rescue-End.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.dump(..-file)-(Yd-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/YAML.load(file)-(Yl-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/alias_method-..-(am).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/all-{-e-..-}-(all).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/any-{-e-..-}-(any).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/application_code-..-(app).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert(..)-(as).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_equal.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_in_delta(..)-(asid).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_instance_of(..)-(asio).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_kind_of(..)-(asko).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_match(..)-(asm).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nil(..)-(asn).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_no_match(..)-(asnm).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_equal(..)-(asne).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_nil(..)-(asnn).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_not_same(..)-(asns).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_raised(..)-{-..-}-(asnr).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_nothing_thrown-{-..-}-(asnt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_operator(..)-(aso).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_raise(..)-{-..-}-(asr).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_respond_to(..)-(asrt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_same(..)-(ass).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_send(..)-(ass).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/assert_throws(..)-{-..-}-(ast).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_accessor-..-(rw).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_reader-..-(r).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/attr_writer-..-(w).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-DelegateClass-..-initialize-..-end-(class).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-ParentClass-..-initialize-..-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Struct-..-initialize-..-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-Test;;Unit;;TestCase-..-end-(tc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-end-(cla).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-initialize-..-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-..-instance_methods-..-undef-..-initialize-..-end-(class).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class-self-__-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/class_from_name()-(clafn).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/classify-{-e-..-}-(clas).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/collect-{-e-..-}-(col).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/deep_copy(..)-(dee).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-method_missing-..-end-(mm).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-self-..-end-(defs).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def-test_-..-end-(t).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegator-..-(defd).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_delegators-..-(defds).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/def_initialize.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/delete_if-{-e-..-}-(deli).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/detect-{-e-..-}-(det).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/directory().sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/do-obj-..-end-(doo).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/downto(0)-{-n-..-}-(dow).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each-{-e-..-}-(ea).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_byte-{-byte-..-}-(eab).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_char-{-chr-..-}-(eac-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_cons(..)-{-group-..-}-(eac-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_index-{-i-..-}-(eai).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_key-{-key-..-}-(eak).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_line-{-line-..-}-(eal).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_pair-{-name-val-..-}-(eap).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_slice-{-group-..-}-(eas).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_value-{-val-..-}-(eav).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/each_with_index-{-e-i-..-}-(eawi).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/elsif-___.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/extend-Forwardable-(Forw).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fetch(name)-{-key-..-}-(fet).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/fill(range)-{-i-..-}-(fil).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find-{-e-..-}-(fin).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/find_all-{-e-..-}-(fina).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flatten_once-(fla).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/flunk(..)-(fl).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/grep(;pattern;)-{-match-..-}-(gre).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/gsub(;..;)-{-match-..-}-(gsu).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/hash-pair-(-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Comparable-..-(Comp).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/include-Enumerable-..-(Enum).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/inject(init)-{-mem-var-..-}-(inj).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/lambda-{-args-..-}-(lam).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/loop-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map-{-e-..-}-(map).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/map_with_index-{-e-i-..-}-(mapwi).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/max-{-a-b-..-}-(max).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/min-{-a-b-..-}-(min).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-ClassMethods-..-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/module-..-module_function-..-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/namespace-__-do-__-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open(-path;or;url-w-)-do-doc-..-end-(ope).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/open-yield-block-({).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/option_parse-{-..-}-(optp).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/partition-{-e-..-}-(par).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/path_from_here(-__-).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/randomize-(ran).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reject-{-e-..-}-(rej).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-..-(req).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require-tc_..-..-(ts).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/require_gem-__.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/results_report(__)-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/reverse_each-{-e-..-}-(rea).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/scan(;..;)-{-match-..-}-(sca).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/select-{-e-..-}-(sel).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/service_object.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/singleton_class().sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort-{-a-b-..-}-(sor).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sort_by-{-e-..-}-(sorb).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/step(2)-{-e-..-}-(ste).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/sub(;..;)-{-match-..-}-(sub).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/task-task_name-=-[-dependent-tasks]-do-__-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/times-{-n-..-}-(tim).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/transaction(-__-)-do-__-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unix_filter-..-(uni).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/unless-(unless).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/until-___-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/untitled.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/upto(1.0;0.0)-{-n-..-}-(upt).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_if()-(usai).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/usage_unless()-(usau).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/when.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/while-___-end.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xmlread(__).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/xpath(__)-{-__-}.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/yields-RDoc-comment.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Snippets/zip(enums)-{-row-..-}-(zip).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Classes - Modules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/Symbols - Methods.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Ruby/syntax_test_ruby.rb diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Cargo.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Default.sublime-keymap diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/LICENSE.txt diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Rust.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/RustComment.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/RustIndent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/RustSymbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Err.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Ok.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/Some.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/assert_eq.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/bench.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/const.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/else.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/enum.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-crate.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-fn.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/extern-mod.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fmt.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/fn.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/for.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if-let.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/if.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl-trait.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/impl.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/let.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/loop.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/macro_rules.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/main.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/match.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/mod.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/panic.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/print.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/println.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/static.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-tuple.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct-unit.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/struct.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/test.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/trait.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/type.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while-let.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/Snippets/while.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Rust/syntax_test_rust.rs diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/SQL.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql b/crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/SQL/syntax_test_sql.sql diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Dedent-case.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Indent-case.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Indent.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Scala.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/adt.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/app.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/case.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/cc.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/co.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/def.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/match.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/p.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/try.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/tryf.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/val.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Snippets/var.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-class.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-def.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-namespace.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-type.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-val.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols-var.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/Symbols.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/info.plist diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Scala/syntax_test_scala.scala diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Bash.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Completion Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Indentation.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Makefile diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Shell-Unix-Generic.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/ShellScript.sublime-build diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/#!-usr-bin-env-(!env).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/case-..-esac-(case).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/elif-..-(elif).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-...-done-(for).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/for-in-done-(forin).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/if-...-then-(if).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/until-(done).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Snippets/while-(done).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Aliases.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Expansions.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Functions.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/Symbol List - Variables.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/commands-builtin-shell-bash.yml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_bash.sh diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/test/syntax_test_shell_unix_generic.sh diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py b/crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/ShellScript/tools/update-commands.py diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/HTML (Tcl).sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/for...-(for).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/foreach...-(foreach).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/if...-(if).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/proc...-(proc).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/switch...-(switch).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Snippets/while...-(while).sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List Indent NS Proc.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/Tcl.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl b/crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/TCL/syntax_test_tcl.tcl diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Text/Plain text.tmLanguage diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Text/Snippets/lorem.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Acronym.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Block-Quotes.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-1.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-2.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-3.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-4.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-5.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Heading-6.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Image.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Snippets/Linked-Image.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/Textile.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile b/crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/Textile/syntax_test_textile.textile diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Miscellaneous.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-cdata.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-declaration.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-long-tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-model.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-short-tag.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/Snippets/xml-stylesheet.sublime-snippet diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/XML.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/XML/syntax_test_xml.xml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/Comments.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/Indentation Rules.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/Symbol List.tmPreferences diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-settings diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/YAML.sublime-syntax diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/preview.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_block.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_directives.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow-plain.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_flow.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_general.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_properties.yaml diff --git a/crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml b/crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml similarity index 100% rename from crates/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml rename to crates/bin/docs_rs_web/assets/syntaxes/Packages/YAML/tests/syntax_test_types.yaml diff --git a/crates/docs_rs_web/build.rs b/crates/bin/docs_rs_web/build.rs similarity index 100% rename from crates/docs_rs_web/build.rs rename to crates/bin/docs_rs_web/build.rs diff --git a/crates/docs_rs_web/src/build_details.rs b/crates/bin/docs_rs_web/src/build_details.rs similarity index 100% rename from crates/docs_rs_web/src/build_details.rs rename to crates/bin/docs_rs_web/src/build_details.rs diff --git a/crates/docs_rs_web/src/builds.rs b/crates/bin/docs_rs_web/src/builds.rs similarity index 100% rename from crates/docs_rs_web/src/builds.rs rename to crates/bin/docs_rs_web/src/builds.rs diff --git a/crates/docs_rs_web/src/cache.rs b/crates/bin/docs_rs_web/src/cache.rs similarity index 100% rename from crates/docs_rs_web/src/cache.rs rename to crates/bin/docs_rs_web/src/cache.rs diff --git a/crates/docs_rs_web/src/config.rs b/crates/bin/docs_rs_web/src/config.rs similarity index 100% rename from crates/docs_rs_web/src/config.rs rename to crates/bin/docs_rs_web/src/config.rs diff --git a/crates/docs_rs_web/src/crate_details.rs b/crates/bin/docs_rs_web/src/crate_details.rs similarity index 100% rename from crates/docs_rs_web/src/crate_details.rs rename to crates/bin/docs_rs_web/src/crate_details.rs diff --git a/crates/docs_rs_web/src/csp.rs b/crates/bin/docs_rs_web/src/csp.rs similarity index 100% rename from crates/docs_rs_web/src/csp.rs rename to crates/bin/docs_rs_web/src/csp.rs diff --git a/crates/docs_rs_web/src/error.rs b/crates/bin/docs_rs_web/src/error.rs similarity index 100% rename from crates/docs_rs_web/src/error.rs rename to crates/bin/docs_rs_web/src/error.rs diff --git a/crates/docs_rs_web/src/extractors/context.rs b/crates/bin/docs_rs_web/src/extractors/context.rs similarity index 100% rename from crates/docs_rs_web/src/extractors/context.rs rename to crates/bin/docs_rs_web/src/extractors/context.rs diff --git a/crates/docs_rs_web/src/extractors/mod.rs b/crates/bin/docs_rs_web/src/extractors/mod.rs similarity index 100% rename from crates/docs_rs_web/src/extractors/mod.rs rename to crates/bin/docs_rs_web/src/extractors/mod.rs diff --git a/crates/docs_rs_web/src/extractors/path.rs b/crates/bin/docs_rs_web/src/extractors/path.rs similarity index 100% rename from crates/docs_rs_web/src/extractors/path.rs rename to crates/bin/docs_rs_web/src/extractors/path.rs diff --git a/crates/docs_rs_web/src/extractors/rustdoc.rs b/crates/bin/docs_rs_web/src/extractors/rustdoc.rs similarity index 100% rename from crates/docs_rs_web/src/extractors/rustdoc.rs rename to crates/bin/docs_rs_web/src/extractors/rustdoc.rs diff --git a/crates/docs_rs_web/src/features.rs b/crates/bin/docs_rs_web/src/features.rs similarity index 100% rename from crates/docs_rs_web/src/features.rs rename to crates/bin/docs_rs_web/src/features.rs diff --git a/crates/docs_rs_web/src/file.rs b/crates/bin/docs_rs_web/src/file.rs similarity index 100% rename from crates/docs_rs_web/src/file.rs rename to crates/bin/docs_rs_web/src/file.rs diff --git a/crates/docs_rs_web/src/highlight.rs b/crates/bin/docs_rs_web/src/highlight.rs similarity index 100% rename from crates/docs_rs_web/src/highlight.rs rename to crates/bin/docs_rs_web/src/highlight.rs diff --git a/crates/docs_rs_web/src/lib.rs b/crates/bin/docs_rs_web/src/lib.rs similarity index 100% rename from crates/docs_rs_web/src/lib.rs rename to crates/bin/docs_rs_web/src/lib.rs diff --git a/crates/docs_rs_web/src/licenses.rs b/crates/bin/docs_rs_web/src/licenses.rs similarity index 100% rename from crates/docs_rs_web/src/licenses.rs rename to crates/bin/docs_rs_web/src/licenses.rs diff --git a/crates/docs_rs_web/src/markdown.rs b/crates/bin/docs_rs_web/src/markdown.rs similarity index 100% rename from crates/docs_rs_web/src/markdown.rs rename to crates/bin/docs_rs_web/src/markdown.rs diff --git a/crates/docs_rs_web/src/metrics.rs b/crates/bin/docs_rs_web/src/metrics.rs similarity index 100% rename from crates/docs_rs_web/src/metrics.rs rename to crates/bin/docs_rs_web/src/metrics.rs diff --git a/crates/docs_rs_web/src/page/mod.rs b/crates/bin/docs_rs_web/src/page/mod.rs similarity index 100% rename from crates/docs_rs_web/src/page/mod.rs rename to crates/bin/docs_rs_web/src/page/mod.rs diff --git a/crates/docs_rs_web/src/page/templates.rs b/crates/bin/docs_rs_web/src/page/templates.rs similarity index 100% rename from crates/docs_rs_web/src/page/templates.rs rename to crates/bin/docs_rs_web/src/page/templates.rs diff --git a/crates/docs_rs_web/src/page/web_page.rs b/crates/bin/docs_rs_web/src/page/web_page.rs similarity index 100% rename from crates/docs_rs_web/src/page/web_page.rs rename to crates/bin/docs_rs_web/src/page/web_page.rs diff --git a/crates/docs_rs_web/src/releases.rs b/crates/bin/docs_rs_web/src/releases.rs similarity index 100% rename from crates/docs_rs_web/src/releases.rs rename to crates/bin/docs_rs_web/src/releases.rs diff --git a/crates/docs_rs_web/src/routes.rs b/crates/bin/docs_rs_web/src/routes.rs similarity index 100% rename from crates/docs_rs_web/src/routes.rs rename to crates/bin/docs_rs_web/src/routes.rs diff --git a/crates/docs_rs_web/src/rustdoc.rs b/crates/bin/docs_rs_web/src/rustdoc.rs similarity index 100% rename from crates/docs_rs_web/src/rustdoc.rs rename to crates/bin/docs_rs_web/src/rustdoc.rs diff --git a/crates/docs_rs_web/src/sitemap.rs b/crates/bin/docs_rs_web/src/sitemap.rs similarity index 100% rename from crates/docs_rs_web/src/sitemap.rs rename to crates/bin/docs_rs_web/src/sitemap.rs diff --git a/crates/docs_rs_web/src/source.rs b/crates/bin/docs_rs_web/src/source.rs similarity index 100% rename from crates/docs_rs_web/src/source.rs rename to crates/bin/docs_rs_web/src/source.rs diff --git a/crates/docs_rs_web/src/statics.rs b/crates/bin/docs_rs_web/src/statics.rs similarity index 100% rename from crates/docs_rs_web/src/statics.rs rename to crates/bin/docs_rs_web/src/statics.rs diff --git a/crates/docs_rs_web/src/status.rs b/crates/bin/docs_rs_web/src/status.rs similarity index 100% rename from crates/docs_rs_web/src/status.rs rename to crates/bin/docs_rs_web/src/status.rs diff --git a/crates/docs_rs_web/src/utils/html.rs b/crates/bin/docs_rs_web/src/utils/html.rs similarity index 100% rename from crates/docs_rs_web/src/utils/html.rs rename to crates/bin/docs_rs_web/src/utils/html.rs diff --git a/crates/docs_rs_web/src/utils/mod.rs b/crates/bin/docs_rs_web/src/utils/mod.rs similarity index 100% rename from crates/docs_rs_web/src/utils/mod.rs rename to crates/bin/docs_rs_web/src/utils/mod.rs diff --git a/crates/docs_rs_web/src/utils/rustc_version.rs b/crates/bin/docs_rs_web/src/utils/rustc_version.rs similarity index 100% rename from crates/docs_rs_web/src/utils/rustc_version.rs rename to crates/bin/docs_rs_web/src/utils/rustc_version.rs diff --git a/crates/docs_rs_web/static/FiraSans-LICENSE.txt b/crates/bin/docs_rs_web/static/FiraSans-LICENSE.txt similarity index 100% rename from crates/docs_rs_web/static/FiraSans-LICENSE.txt rename to crates/bin/docs_rs_web/static/FiraSans-LICENSE.txt diff --git a/crates/docs_rs_web/static/FiraSans-Medium.woff b/crates/bin/docs_rs_web/static/FiraSans-Medium.woff similarity index 100% rename from crates/docs_rs_web/static/FiraSans-Medium.woff rename to crates/bin/docs_rs_web/static/FiraSans-Medium.woff diff --git a/crates/docs_rs_web/static/FiraSans-Medium.woff2 b/crates/bin/docs_rs_web/static/FiraSans-Medium.woff2 similarity index 100% rename from crates/docs_rs_web/static/FiraSans-Medium.woff2 rename to crates/bin/docs_rs_web/static/FiraSans-Medium.woff2 diff --git a/crates/docs_rs_web/static/FiraSans-Regular.woff b/crates/bin/docs_rs_web/static/FiraSans-Regular.woff similarity index 100% rename from crates/docs_rs_web/static/FiraSans-Regular.woff rename to crates/bin/docs_rs_web/static/FiraSans-Regular.woff diff --git a/crates/docs_rs_web/static/FiraSans-Regular.woff2 b/crates/bin/docs_rs_web/static/FiraSans-Regular.woff2 similarity index 100% rename from crates/docs_rs_web/static/FiraSans-Regular.woff2 rename to crates/bin/docs_rs_web/static/FiraSans-Regular.woff2 diff --git a/crates/docs_rs_web/static/SourceCodePro-It.ttf.woff b/crates/bin/docs_rs_web/static/SourceCodePro-It.ttf.woff similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-It.ttf.woff rename to crates/bin/docs_rs_web/static/SourceCodePro-It.ttf.woff diff --git a/crates/docs_rs_web/static/SourceCodePro-It.ttf.woff2 b/crates/bin/docs_rs_web/static/SourceCodePro-It.ttf.woff2 similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-It.ttf.woff2 rename to crates/bin/docs_rs_web/static/SourceCodePro-It.ttf.woff2 diff --git a/crates/docs_rs_web/static/SourceCodePro-LICENSE.md b/crates/bin/docs_rs_web/static/SourceCodePro-LICENSE.md similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-LICENSE.md rename to crates/bin/docs_rs_web/static/SourceCodePro-LICENSE.md diff --git a/crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff b/crates/bin/docs_rs_web/static/SourceCodePro-Regular.ttf.woff similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff rename to crates/bin/docs_rs_web/static/SourceCodePro-Regular.ttf.woff diff --git a/crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 b/crates/bin/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 rename to crates/bin/docs_rs_web/static/SourceCodePro-Regular.ttf.woff2 diff --git a/crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff b/crates/bin/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff rename to crates/bin/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff diff --git a/crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 b/crates/bin/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 similarity index 100% rename from crates/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 rename to crates/bin/docs_rs_web/static/SourceCodePro-Semibold.ttf.woff2 diff --git a/crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff b/crates/bin/docs_rs_web/static/SourceSerif4-Bold.ttf.woff similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff rename to crates/bin/docs_rs_web/static/SourceSerif4-Bold.ttf.woff diff --git a/crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 b/crates/bin/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 rename to crates/bin/docs_rs_web/static/SourceSerif4-Bold.ttf.woff2 diff --git a/crates/docs_rs_web/static/SourceSerif4-It.ttf.woff b/crates/bin/docs_rs_web/static/SourceSerif4-It.ttf.woff similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-It.ttf.woff rename to crates/bin/docs_rs_web/static/SourceSerif4-It.ttf.woff diff --git a/crates/docs_rs_web/static/SourceSerif4-It.ttf.woff2 b/crates/bin/docs_rs_web/static/SourceSerif4-It.ttf.woff2 similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-It.ttf.woff2 rename to crates/bin/docs_rs_web/static/SourceSerif4-It.ttf.woff2 diff --git a/crates/docs_rs_web/static/SourceSerif4-LICENSE.md b/crates/bin/docs_rs_web/static/SourceSerif4-LICENSE.md similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-LICENSE.md rename to crates/bin/docs_rs_web/static/SourceSerif4-LICENSE.md diff --git a/crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff b/crates/bin/docs_rs_web/static/SourceSerif4-Regular.ttf.woff similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff rename to crates/bin/docs_rs_web/static/SourceSerif4-Regular.ttf.woff diff --git a/crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 b/crates/bin/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 similarity index 100% rename from crates/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 rename to crates/bin/docs_rs_web/static/SourceSerif4-Regular.ttf.woff2 diff --git a/crates/docs_rs_web/static/clipboard.svg b/crates/bin/docs_rs_web/static/clipboard.svg similarity index 100% rename from crates/docs_rs_web/static/clipboard.svg rename to crates/bin/docs_rs_web/static/clipboard.svg diff --git a/crates/docs_rs_web/static/fa-brands-400.ttf b/crates/bin/docs_rs_web/static/fa-brands-400.ttf similarity index 100% rename from crates/docs_rs_web/static/fa-brands-400.ttf rename to crates/bin/docs_rs_web/static/fa-brands-400.ttf diff --git a/crates/docs_rs_web/static/fa-brands-400.woff2 b/crates/bin/docs_rs_web/static/fa-brands-400.woff2 similarity index 100% rename from crates/docs_rs_web/static/fa-brands-400.woff2 rename to crates/bin/docs_rs_web/static/fa-brands-400.woff2 diff --git a/crates/docs_rs_web/static/fa-regular-400.ttf b/crates/bin/docs_rs_web/static/fa-regular-400.ttf similarity index 100% rename from crates/docs_rs_web/static/fa-regular-400.ttf rename to crates/bin/docs_rs_web/static/fa-regular-400.ttf diff --git a/crates/docs_rs_web/static/fa-regular-400.woff2 b/crates/bin/docs_rs_web/static/fa-regular-400.woff2 similarity index 100% rename from crates/docs_rs_web/static/fa-regular-400.woff2 rename to crates/bin/docs_rs_web/static/fa-regular-400.woff2 diff --git a/crates/docs_rs_web/static/fa-solid-900.ttf b/crates/bin/docs_rs_web/static/fa-solid-900.ttf similarity index 100% rename from crates/docs_rs_web/static/fa-solid-900.ttf rename to crates/bin/docs_rs_web/static/fa-solid-900.ttf diff --git a/crates/docs_rs_web/static/fa-solid-900.woff2 b/crates/bin/docs_rs_web/static/fa-solid-900.woff2 similarity index 100% rename from crates/docs_rs_web/static/fa-solid-900.woff2 rename to crates/bin/docs_rs_web/static/fa-solid-900.woff2 diff --git a/crates/docs_rs_web/static/fa-v4compatibility.ttf b/crates/bin/docs_rs_web/static/fa-v4compatibility.ttf similarity index 100% rename from crates/docs_rs_web/static/fa-v4compatibility.ttf rename to crates/bin/docs_rs_web/static/fa-v4compatibility.ttf diff --git a/crates/docs_rs_web/static/fa-v4compatibility.woff2 b/crates/bin/docs_rs_web/static/fa-v4compatibility.woff2 similarity index 100% rename from crates/docs_rs_web/static/fa-v4compatibility.woff2 rename to crates/bin/docs_rs_web/static/fa-v4compatibility.woff2 diff --git a/crates/docs_rs_web/static/favicon.ico b/crates/bin/docs_rs_web/static/favicon.ico similarity index 100% rename from crates/docs_rs_web/static/favicon.ico rename to crates/bin/docs_rs_web/static/favicon.ico diff --git a/crates/docs_rs_web/static/index.js b/crates/bin/docs_rs_web/static/index.js similarity index 100% rename from crates/docs_rs_web/static/index.js rename to crates/bin/docs_rs_web/static/index.js diff --git a/crates/docs_rs_web/static/keyboard.js b/crates/bin/docs_rs_web/static/keyboard.js similarity index 100% rename from crates/docs_rs_web/static/keyboard.js rename to crates/bin/docs_rs_web/static/keyboard.js diff --git a/crates/docs_rs_web/static/menu.js b/crates/bin/docs_rs_web/static/menu.js similarity index 100% rename from crates/docs_rs_web/static/menu.js rename to crates/bin/docs_rs_web/static/menu.js diff --git a/crates/docs_rs_web/static/opensearch.xml b/crates/bin/docs_rs_web/static/opensearch.xml similarity index 100% rename from crates/docs_rs_web/static/opensearch.xml rename to crates/bin/docs_rs_web/static/opensearch.xml diff --git a/crates/docs_rs_web/static/robots.txt b/crates/bin/docs_rs_web/static/robots.txt similarity index 100% rename from crates/docs_rs_web/static/robots.txt rename to crates/bin/docs_rs_web/static/robots.txt diff --git a/crates/docs_rs_web/static/source.js b/crates/bin/docs_rs_web/static/source.js similarity index 100% rename from crates/docs_rs_web/static/source.js rename to crates/bin/docs_rs_web/static/source.js diff --git a/crates/docs_rs_web/static/trigger-rebuild.png b/crates/bin/docs_rs_web/static/trigger-rebuild.png similarity index 100% rename from crates/docs_rs_web/static/trigger-rebuild.png rename to crates/bin/docs_rs_web/static/trigger-rebuild.png diff --git a/crates/docs_rs_web/templates/about-base.html b/crates/bin/docs_rs_web/templates/about-base.html similarity index 100% rename from crates/docs_rs_web/templates/about-base.html rename to crates/bin/docs_rs_web/templates/about-base.html diff --git a/crates/docs_rs_web/templates/base.html b/crates/bin/docs_rs_web/templates/base.html similarity index 100% rename from crates/docs_rs_web/templates/base.html rename to crates/bin/docs_rs_web/templates/base.html diff --git a/crates/docs_rs_web/templates/core/Cargo.toml.example b/crates/bin/docs_rs_web/templates/core/Cargo.toml.example similarity index 100% rename from crates/docs_rs_web/templates/core/Cargo.toml.example rename to crates/bin/docs_rs_web/templates/core/Cargo.toml.example diff --git a/crates/docs_rs_web/templates/core/about/badges.html b/crates/bin/docs_rs_web/templates/core/about/badges.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/badges.html rename to crates/bin/docs_rs_web/templates/core/about/badges.html diff --git a/crates/docs_rs_web/templates/core/about/builds.html b/crates/bin/docs_rs_web/templates/core/about/builds.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/builds.html rename to crates/bin/docs_rs_web/templates/core/about/builds.html diff --git a/crates/docs_rs_web/templates/core/about/download.html b/crates/bin/docs_rs_web/templates/core/about/download.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/download.html rename to crates/bin/docs_rs_web/templates/core/about/download.html diff --git a/crates/docs_rs_web/templates/core/about/index.html b/crates/bin/docs_rs_web/templates/core/about/index.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/index.html rename to crates/bin/docs_rs_web/templates/core/about/index.html diff --git a/crates/docs_rs_web/templates/core/about/metadata.html b/crates/bin/docs_rs_web/templates/core/about/metadata.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/metadata.html rename to crates/bin/docs_rs_web/templates/core/about/metadata.html diff --git a/crates/docs_rs_web/templates/core/about/redirections.html b/crates/bin/docs_rs_web/templates/core/about/redirections.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/redirections.html rename to crates/bin/docs_rs_web/templates/core/about/redirections.html diff --git a/crates/docs_rs_web/templates/core/about/rustdoc-json.html b/crates/bin/docs_rs_web/templates/core/about/rustdoc-json.html similarity index 100% rename from crates/docs_rs_web/templates/core/about/rustdoc-json.html rename to crates/bin/docs_rs_web/templates/core/about/rustdoc-json.html diff --git a/crates/docs_rs_web/templates/core/home.html b/crates/bin/docs_rs_web/templates/core/home.html similarity index 100% rename from crates/docs_rs_web/templates/core/home.html rename to crates/bin/docs_rs_web/templates/core/home.html diff --git a/crates/docs_rs_web/templates/core/sitemap/_item.xml b/crates/bin/docs_rs_web/templates/core/sitemap/_item.xml similarity index 100% rename from crates/docs_rs_web/templates/core/sitemap/_item.xml rename to crates/bin/docs_rs_web/templates/core/sitemap/_item.xml diff --git a/crates/docs_rs_web/templates/core/sitemap/index.xml b/crates/bin/docs_rs_web/templates/core/sitemap/index.xml similarity index 100% rename from crates/docs_rs_web/templates/core/sitemap/index.xml rename to crates/bin/docs_rs_web/templates/core/sitemap/index.xml diff --git a/crates/docs_rs_web/templates/crate/build_details.html b/crates/bin/docs_rs_web/templates/crate/build_details.html similarity index 100% rename from crates/docs_rs_web/templates/crate/build_details.html rename to crates/bin/docs_rs_web/templates/crate/build_details.html diff --git a/crates/docs_rs_web/templates/crate/builds.html b/crates/bin/docs_rs_web/templates/crate/builds.html similarity index 100% rename from crates/docs_rs_web/templates/crate/builds.html rename to crates/bin/docs_rs_web/templates/crate/builds.html diff --git a/crates/docs_rs_web/templates/crate/details.html b/crates/bin/docs_rs_web/templates/crate/details.html similarity index 100% rename from crates/docs_rs_web/templates/crate/details.html rename to crates/bin/docs_rs_web/templates/crate/details.html diff --git a/crates/docs_rs_web/templates/crate/features.html b/crates/bin/docs_rs_web/templates/crate/features.html similarity index 100% rename from crates/docs_rs_web/templates/crate/features.html rename to crates/bin/docs_rs_web/templates/crate/features.html diff --git a/crates/docs_rs_web/templates/crate/source.html b/crates/bin/docs_rs_web/templates/crate/source.html similarity index 100% rename from crates/docs_rs_web/templates/crate/source.html rename to crates/bin/docs_rs_web/templates/crate/source.html diff --git a/crates/docs_rs_web/templates/error.html b/crates/bin/docs_rs_web/templates/error.html similarity index 100% rename from crates/docs_rs_web/templates/error.html rename to crates/bin/docs_rs_web/templates/error.html diff --git a/crates/docs_rs_web/templates/header/global_alert.html b/crates/bin/docs_rs_web/templates/header/global_alert.html similarity index 100% rename from crates/docs_rs_web/templates/header/global_alert.html rename to crates/bin/docs_rs_web/templates/header/global_alert.html diff --git a/crates/docs_rs_web/templates/header/package_navigation.html b/crates/bin/docs_rs_web/templates/header/package_navigation.html similarity index 100% rename from crates/docs_rs_web/templates/header/package_navigation.html rename to crates/bin/docs_rs_web/templates/header/package_navigation.html diff --git a/crates/docs_rs_web/templates/header/topbar.html b/crates/bin/docs_rs_web/templates/header/topbar.html similarity index 100% rename from crates/docs_rs_web/templates/header/topbar.html rename to crates/bin/docs_rs_web/templates/header/topbar.html diff --git a/crates/docs_rs_web/templates/header/topbar_begin.html b/crates/bin/docs_rs_web/templates/header/topbar_begin.html similarity index 100% rename from crates/docs_rs_web/templates/header/topbar_begin.html rename to crates/bin/docs_rs_web/templates/header/topbar_begin.html diff --git a/crates/docs_rs_web/templates/header/topbar_end.html b/crates/bin/docs_rs_web/templates/header/topbar_end.html similarity index 100% rename from crates/docs_rs_web/templates/header/topbar_end.html rename to crates/bin/docs_rs_web/templates/header/topbar_end.html diff --git a/crates/docs_rs_web/templates/macros.html b/crates/bin/docs_rs_web/templates/macros.html similarity index 100% rename from crates/docs_rs_web/templates/macros.html rename to crates/bin/docs_rs_web/templates/macros.html diff --git a/crates/docs_rs_web/templates/releases/activity.html b/crates/bin/docs_rs_web/templates/releases/activity.html similarity index 100% rename from crates/docs_rs_web/templates/releases/activity.html rename to crates/bin/docs_rs_web/templates/releases/activity.html diff --git a/crates/docs_rs_web/templates/releases/build_queue.html b/crates/bin/docs_rs_web/templates/releases/build_queue.html similarity index 100% rename from crates/docs_rs_web/templates/releases/build_queue.html rename to crates/bin/docs_rs_web/templates/releases/build_queue.html diff --git a/crates/docs_rs_web/templates/releases/feed.xml b/crates/bin/docs_rs_web/templates/releases/feed.xml similarity index 100% rename from crates/docs_rs_web/templates/releases/feed.xml rename to crates/bin/docs_rs_web/templates/releases/feed.xml diff --git a/crates/docs_rs_web/templates/releases/header.html b/crates/bin/docs_rs_web/templates/releases/header.html similarity index 100% rename from crates/docs_rs_web/templates/releases/header.html rename to crates/bin/docs_rs_web/templates/releases/header.html diff --git a/crates/docs_rs_web/templates/releases/releases.html b/crates/bin/docs_rs_web/templates/releases/releases.html similarity index 100% rename from crates/docs_rs_web/templates/releases/releases.html rename to crates/bin/docs_rs_web/templates/releases/releases.html diff --git a/crates/docs_rs_web/templates/releases/search_results.html b/crates/bin/docs_rs_web/templates/releases/search_results.html similarity index 100% rename from crates/docs_rs_web/templates/releases/search_results.html rename to crates/bin/docs_rs_web/templates/releases/search_results.html diff --git a/crates/docs_rs_web/templates/rustdoc/body.html b/crates/bin/docs_rs_web/templates/rustdoc/body.html similarity index 100% rename from crates/docs_rs_web/templates/rustdoc/body.html rename to crates/bin/docs_rs_web/templates/rustdoc/body.html diff --git a/crates/docs_rs_web/templates/rustdoc/head.html b/crates/bin/docs_rs_web/templates/rustdoc/head.html similarity index 100% rename from crates/docs_rs_web/templates/rustdoc/head.html rename to crates/bin/docs_rs_web/templates/rustdoc/head.html diff --git a/crates/docs_rs_web/templates/rustdoc/platforms.html b/crates/bin/docs_rs_web/templates/rustdoc/platforms.html similarity index 100% rename from crates/docs_rs_web/templates/rustdoc/platforms.html rename to crates/bin/docs_rs_web/templates/rustdoc/platforms.html diff --git a/crates/docs_rs_web/templates/rustdoc/releases.html b/crates/bin/docs_rs_web/templates/rustdoc/releases.html similarity index 100% rename from crates/docs_rs_web/templates/rustdoc/releases.html rename to crates/bin/docs_rs_web/templates/rustdoc/releases.html diff --git a/crates/docs_rs_web/templates/rustdoc/topbar.html b/crates/bin/docs_rs_web/templates/rustdoc/topbar.html similarity index 100% rename from crates/docs_rs_web/templates/rustdoc/topbar.html rename to crates/bin/docs_rs_web/templates/rustdoc/topbar.html diff --git a/crates/docs_rs_web/templates/rustdoc/vendored.html b/crates/bin/docs_rs_web/templates/rustdoc/vendored.html similarity index 100% rename from crates/docs_rs_web/templates/rustdoc/vendored.html rename to crates/bin/docs_rs_web/templates/rustdoc/vendored.html diff --git a/crates/docs_rs_web/templates/storage-change-detection.html b/crates/bin/docs_rs_web/templates/storage-change-detection.html similarity index 100% rename from crates/docs_rs_web/templates/storage-change-detection.html rename to crates/bin/docs_rs_web/templates/storage-change-detection.html diff --git a/crates/docs_rs_web/templates/style/_navbar.scss b/crates/bin/docs_rs_web/templates/style/_navbar.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_navbar.scss rename to crates/bin/docs_rs_web/templates/style/_navbar.scss diff --git a/crates/docs_rs_web/templates/style/_rustdoc-common.scss b/crates/bin/docs_rs_web/templates/style/_rustdoc-common.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_rustdoc-common.scss rename to crates/bin/docs_rs_web/templates/style/_rustdoc-common.scss diff --git a/crates/docs_rs_web/templates/style/_syntax-themes.scss b/crates/bin/docs_rs_web/templates/style/_syntax-themes.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_syntax-themes.scss rename to crates/bin/docs_rs_web/templates/style/_syntax-themes.scss diff --git a/crates/docs_rs_web/templates/style/_syntax.scss b/crates/bin/docs_rs_web/templates/style/_syntax.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_syntax.scss rename to crates/bin/docs_rs_web/templates/style/_syntax.scss diff --git a/crates/docs_rs_web/templates/style/_themes.scss b/crates/bin/docs_rs_web/templates/style/_themes.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_themes.scss rename to crates/bin/docs_rs_web/templates/style/_themes.scss diff --git a/crates/docs_rs_web/templates/style/_utils.scss b/crates/bin/docs_rs_web/templates/style/_utils.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_utils.scss rename to crates/bin/docs_rs_web/templates/style/_utils.scss diff --git a/crates/docs_rs_web/templates/style/_vars.scss b/crates/bin/docs_rs_web/templates/style/_vars.scss similarity index 100% rename from crates/docs_rs_web/templates/style/_vars.scss rename to crates/bin/docs_rs_web/templates/style/_vars.scss diff --git a/crates/docs_rs_web/templates/style/rustdoc-2021-12-05.scss b/crates/bin/docs_rs_web/templates/style/rustdoc-2021-12-05.scss similarity index 100% rename from crates/docs_rs_web/templates/style/rustdoc-2021-12-05.scss rename to crates/bin/docs_rs_web/templates/style/rustdoc-2021-12-05.scss diff --git a/crates/docs_rs_web/templates/style/rustdoc-2025-08-20.scss b/crates/bin/docs_rs_web/templates/style/rustdoc-2025-08-20.scss similarity index 100% rename from crates/docs_rs_web/templates/style/rustdoc-2025-08-20.scss rename to crates/bin/docs_rs_web/templates/style/rustdoc-2025-08-20.scss diff --git a/crates/docs_rs_web/templates/style/rustdoc.scss b/crates/bin/docs_rs_web/templates/style/rustdoc.scss similarity index 100% rename from crates/docs_rs_web/templates/style/rustdoc.scss rename to crates/bin/docs_rs_web/templates/style/rustdoc.scss diff --git a/crates/docs_rs_web/templates/style/style.scss b/crates/bin/docs_rs_web/templates/style/style.scss similarity index 100% rename from crates/docs_rs_web/templates/style/style.scss rename to crates/bin/docs_rs_web/templates/style/style.scss diff --git a/crates/docs_rs_web/templates/theme.js b/crates/bin/docs_rs_web/templates/theme.js similarity index 100% rename from crates/docs_rs_web/templates/theme.js rename to crates/bin/docs_rs_web/templates/theme.js diff --git a/crates/docs_rs_web/vendor/chartjs/LICENSE b/crates/bin/docs_rs_web/vendor/chartjs/LICENSE similarity index 100% rename from crates/docs_rs_web/vendor/chartjs/LICENSE rename to crates/bin/docs_rs_web/vendor/chartjs/LICENSE diff --git a/crates/docs_rs_web/vendor/chartjs/chart.min.js b/crates/bin/docs_rs_web/vendor/chartjs/chart.min.js similarity index 100% rename from crates/docs_rs_web/vendor/chartjs/chart.min.js rename to crates/bin/docs_rs_web/vendor/chartjs/chart.min.js diff --git a/crates/docs_rs_web/vendor/pure-css/LICENSE b/crates/bin/docs_rs_web/vendor/pure-css/LICENSE similarity index 100% rename from crates/docs_rs_web/vendor/pure-css/LICENSE rename to crates/bin/docs_rs_web/vendor/pure-css/LICENSE diff --git a/crates/docs_rs_build_queue/Cargo.toml b/crates/lib/docs_rs_build_queue/Cargo.toml similarity index 100% rename from crates/docs_rs_build_queue/Cargo.toml rename to crates/lib/docs_rs_build_queue/Cargo.toml diff --git a/crates/docs_rs_build_queue/src/config.rs b/crates/lib/docs_rs_build_queue/src/config.rs similarity index 100% rename from crates/docs_rs_build_queue/src/config.rs rename to crates/lib/docs_rs_build_queue/src/config.rs diff --git a/crates/docs_rs_build_queue/src/lib.rs b/crates/lib/docs_rs_build_queue/src/lib.rs similarity index 100% rename from crates/docs_rs_build_queue/src/lib.rs rename to crates/lib/docs_rs_build_queue/src/lib.rs diff --git a/crates/docs_rs_build_queue/src/metrics.rs b/crates/lib/docs_rs_build_queue/src/metrics.rs similarity index 100% rename from crates/docs_rs_build_queue/src/metrics.rs rename to crates/lib/docs_rs_build_queue/src/metrics.rs diff --git a/crates/docs_rs_build_queue/src/rebuilds.rs b/crates/lib/docs_rs_build_queue/src/rebuilds.rs similarity index 100% rename from crates/docs_rs_build_queue/src/rebuilds.rs rename to crates/lib/docs_rs_build_queue/src/rebuilds.rs diff --git a/crates/docs_rs_build_utils/Cargo.toml b/crates/lib/docs_rs_build_utils/Cargo.toml similarity index 100% rename from crates/docs_rs_build_utils/Cargo.toml rename to crates/lib/docs_rs_build_utils/Cargo.toml diff --git a/crates/docs_rs_build_utils/src/config.rs b/crates/lib/docs_rs_build_utils/src/config.rs similarity index 100% rename from crates/docs_rs_build_utils/src/config.rs rename to crates/lib/docs_rs_build_utils/src/config.rs diff --git a/crates/docs_rs_build_utils/src/lib.rs b/crates/lib/docs_rs_build_utils/src/lib.rs similarity index 100% rename from crates/docs_rs_build_utils/src/lib.rs rename to crates/lib/docs_rs_build_utils/src/lib.rs diff --git a/crates/docs_rs_build_utils/src/limits.rs b/crates/lib/docs_rs_build_utils/src/limits.rs similarity index 100% rename from crates/docs_rs_build_utils/src/limits.rs rename to crates/lib/docs_rs_build_utils/src/limits.rs diff --git a/crates/docs_rs_build_utils/src/overrides.rs b/crates/lib/docs_rs_build_utils/src/overrides.rs similarity index 100% rename from crates/docs_rs_build_utils/src/overrides.rs rename to crates/lib/docs_rs_build_utils/src/overrides.rs diff --git a/crates/docs_rs_cargo_metadata/Cargo.toml b/crates/lib/docs_rs_cargo_metadata/Cargo.toml similarity index 100% rename from crates/docs_rs_cargo_metadata/Cargo.toml rename to crates/lib/docs_rs_cargo_metadata/Cargo.toml diff --git a/crates/docs_rs_cargo_metadata/src/db.rs b/crates/lib/docs_rs_cargo_metadata/src/db.rs similarity index 100% rename from crates/docs_rs_cargo_metadata/src/db.rs rename to crates/lib/docs_rs_cargo_metadata/src/db.rs diff --git a/crates/docs_rs_cargo_metadata/src/lib.rs b/crates/lib/docs_rs_cargo_metadata/src/lib.rs similarity index 100% rename from crates/docs_rs_cargo_metadata/src/lib.rs rename to crates/lib/docs_rs_cargo_metadata/src/lib.rs diff --git a/crates/docs_rs_context/Cargo.toml b/crates/lib/docs_rs_context/Cargo.toml similarity index 100% rename from crates/docs_rs_context/Cargo.toml rename to crates/lib/docs_rs_context/Cargo.toml diff --git a/crates/docs_rs_context/src/lib.rs b/crates/lib/docs_rs_context/src/lib.rs similarity index 100% rename from crates/docs_rs_context/src/lib.rs rename to crates/lib/docs_rs_context/src/lib.rs diff --git a/crates/docs_rs_database/Cargo.toml b/crates/lib/docs_rs_database/Cargo.toml similarity index 100% rename from crates/docs_rs_database/Cargo.toml rename to crates/lib/docs_rs_database/Cargo.toml diff --git a/crates/docs_rs_database/build.rs b/crates/lib/docs_rs_database/build.rs similarity index 100% rename from crates/docs_rs_database/build.rs rename to crates/lib/docs_rs_database/build.rs diff --git a/crates/docs_rs_database/migrations/20231021111635_initial.down.sql b/crates/lib/docs_rs_database/migrations/20231021111635_initial.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20231021111635_initial.down.sql rename to crates/lib/docs_rs_database/migrations/20231021111635_initial.down.sql diff --git a/crates/docs_rs_database/migrations/20231021111635_initial.up.sql b/crates/lib/docs_rs_database/migrations/20231021111635_initial.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20231021111635_initial.up.sql rename to crates/lib/docs_rs_database/migrations/20231021111635_initial.up.sql diff --git a/crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql b/crates/lib/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql rename to crates/lib/docs_rs_database/migrations/20240221104457_drop_releases_build_status.down.sql diff --git a/crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql b/crates/lib/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql rename to crates/lib/docs_rs_database/migrations/20240221104457_drop_releases_build_status.up.sql diff --git a/crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql b/crates/lib/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql rename to crates/lib/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.down.sql diff --git a/crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql b/crates/lib/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql rename to crates/lib/docs_rs_database/migrations/20240221113734_drop_releases_rustc_version.up.sql diff --git a/crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql b/crates/lib/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql rename to crates/lib/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.down.sql diff --git a/crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql b/crates/lib/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql rename to crates/lib/docs_rs_database/migrations/20240221114302_ensure_no_buildless_releases_exist.up.sql diff --git a/crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql b/crates/lib/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql rename to crates/lib/docs_rs_database/migrations/20240221124844_multi_stage_build_status.down.sql diff --git a/crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql b/crates/lib/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql rename to crates/lib/docs_rs_database/migrations/20240221124844_multi_stage_build_status.up.sql diff --git a/crates/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql b/crates/lib/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql rename to crates/lib/docs_rs_database/migrations/20240227040753_add_owner_kind.down.sql diff --git a/crates/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql b/crates/lib/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql rename to crates/lib/docs_rs_database/migrations/20240227040753_add_owner_kind.up.sql diff --git a/crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql b/crates/lib/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql rename to crates/lib/docs_rs_database/migrations/20240309082057_release_status_view.sql.down.sql diff --git a/crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql b/crates/lib/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql rename to crates/lib/docs_rs_database/migrations/20240309082057_release_status_view.sql.up.sql diff --git a/crates/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql b/crates/lib/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql rename to crates/lib/docs_rs_database/migrations/20240311202914_release_status_materialized.down.sql diff --git a/crates/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql b/crates/lib/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql rename to crates/lib/docs_rs_database/migrations/20240311202914_release_status_materialized.up.sql diff --git a/crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql b/crates/lib/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql rename to crates/lib/docs_rs_database/migrations/20240313103708_make_release_fields_optional.down.sql diff --git a/crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql b/crates/lib/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql rename to crates/lib/docs_rs_database/migrations/20240313103708_make_release_fields_optional.up.sql diff --git a/crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql b/crates/lib/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql rename to crates/lib/docs_rs_database/migrations/20240313182623_make_build_fields_optional.down.sql diff --git a/crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql b/crates/lib/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql rename to crates/lib/docs_rs_database/migrations/20240313182623_make_build_fields_optional.up.sql diff --git a/crates/docs_rs_database/migrations/20240313184911_build_errors.down.sql b/crates/lib/docs_rs_database/migrations/20240313184911_build_errors.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240313184911_build_errors.down.sql rename to crates/lib/docs_rs_database/migrations/20240313184911_build_errors.down.sql diff --git a/crates/docs_rs_database/migrations/20240313184911_build_errors.up.sql b/crates/lib/docs_rs_database/migrations/20240313184911_build_errors.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240313184911_build_errors.up.sql rename to crates/lib/docs_rs_database/migrations/20240313184911_build_errors.up.sql diff --git a/crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql b/crates/lib/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql rename to crates/lib/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.down.sql diff --git a/crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql b/crates/lib/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql rename to crates/lib/docs_rs_database/migrations/20240519141105_crate-version-name-field-length.up.sql diff --git a/crates/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql b/crates/lib/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql rename to crates/lib/docs_rs_database/migrations/20240624085737_build-status-idx.down.sql diff --git a/crates/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql b/crates/lib/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql rename to crates/lib/docs_rs_database/migrations/20240624085737_build-status-idx.up.sql diff --git a/crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql b/crates/lib/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql rename to crates/lib/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.down.sql diff --git a/crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql b/crates/lib/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql rename to crates/lib/docs_rs_database/migrations/20241015054153_buildqueue-queue-crate-version-name-field-length.up.sql diff --git a/crates/docs_rs_database/migrations/20241018031600_documentation_size.down.sql b/crates/lib/docs_rs_database/migrations/20241018031600_documentation_size.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241018031600_documentation_size.down.sql rename to crates/lib/docs_rs_database/migrations/20241018031600_documentation_size.down.sql diff --git a/crates/docs_rs_database/migrations/20241018031600_documentation_size.up.sql b/crates/lib/docs_rs_database/migrations/20241018031600_documentation_size.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241018031600_documentation_size.up.sql rename to crates/lib/docs_rs_database/migrations/20241018031600_documentation_size.up.sql diff --git a/crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql b/crates/lib/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql rename to crates/lib/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.down.sql diff --git a/crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql b/crates/lib/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql rename to crates/lib/docs_rs_database/migrations/20241018052241_builds-rustc-nightly-date.up.sql diff --git a/crates/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql b/crates/lib/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql rename to crates/lib/docs_rs_database/migrations/20241021050229_builds-started-finished.down.sql diff --git a/crates/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql b/crates/lib/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql rename to crates/lib/docs_rs_database/migrations/20241021050229_builds-started-finished.up.sql diff --git a/crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql b/crates/lib/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql rename to crates/lib/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.down.sql diff --git a/crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql b/crates/lib/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql rename to crates/lib/docs_rs_database/migrations/20241106085600_releases-rustdoc-status-idx.up.sql diff --git a/crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql b/crates/lib/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql rename to crates/lib/docs_rs_database/migrations/20241219091521_owner-avatar-longer.down.sql diff --git a/crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql b/crates/lib/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql rename to crates/lib/docs_rs_database/migrations/20241219091521_owner-avatar-longer.up.sql diff --git a/crates/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql b/crates/lib/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql rename to crates/lib/docs_rs_database/migrations/20251202020754_remove-file-public.down.sql diff --git a/crates/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql b/crates/lib/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql rename to crates/lib/docs_rs_database/migrations/20251202020754_remove-file-public.up.sql diff --git a/crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql b/crates/lib/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql similarity index 100% rename from crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql rename to crates/lib/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.down.sql diff --git a/crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql b/crates/lib/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql similarity index 100% rename from crates/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql rename to crates/lib/docs_rs_database/migrations/20251202040858_remove-cdn-invalidation-queue.up.sql diff --git a/crates/docs_rs_database/src/config.rs b/crates/lib/docs_rs_database/src/config.rs similarity index 100% rename from crates/docs_rs_database/src/config.rs rename to crates/lib/docs_rs_database/src/config.rs diff --git a/crates/docs_rs_database/src/lib.rs b/crates/lib/docs_rs_database/src/lib.rs similarity index 100% rename from crates/docs_rs_database/src/lib.rs rename to crates/lib/docs_rs_database/src/lib.rs diff --git a/crates/docs_rs_database/src/migrate.rs b/crates/lib/docs_rs_database/src/migrate.rs similarity index 100% rename from crates/docs_rs_database/src/migrate.rs rename to crates/lib/docs_rs_database/src/migrate.rs diff --git a/crates/docs_rs_database/src/mimes.rs b/crates/lib/docs_rs_database/src/mimes.rs similarity index 100% rename from crates/docs_rs_database/src/mimes.rs rename to crates/lib/docs_rs_database/src/mimes.rs diff --git a/crates/docs_rs_database/src/service_config.rs b/crates/lib/docs_rs_database/src/service_config.rs similarity index 100% rename from crates/docs_rs_database/src/service_config.rs rename to crates/lib/docs_rs_database/src/service_config.rs diff --git a/crates/docs_rs_database/src/types/krate_name.rs b/crates/lib/docs_rs_database/src/types/krate_name.rs similarity index 100% rename from crates/docs_rs_database/src/types/krate_name.rs rename to crates/lib/docs_rs_database/src/types/krate_name.rs diff --git a/crates/docs_rs_database/src/types/mod.rs b/crates/lib/docs_rs_database/src/types/mod.rs similarity index 100% rename from crates/docs_rs_database/src/types/mod.rs rename to crates/lib/docs_rs_database/src/types/mod.rs diff --git a/crates/docs_rs_database/src/types/version.rs b/crates/lib/docs_rs_database/src/types/version.rs similarity index 100% rename from crates/docs_rs_database/src/types/version.rs rename to crates/lib/docs_rs_database/src/types/version.rs diff --git a/crates/docs_rs_env_vars/Cargo.toml b/crates/lib/docs_rs_env_vars/Cargo.toml similarity index 100% rename from crates/docs_rs_env_vars/Cargo.toml rename to crates/lib/docs_rs_env_vars/Cargo.toml diff --git a/crates/docs_rs_env_vars/src/lib.rs b/crates/lib/docs_rs_env_vars/src/lib.rs similarity index 100% rename from crates/docs_rs_env_vars/src/lib.rs rename to crates/lib/docs_rs_env_vars/src/lib.rs diff --git a/crates/docs_rs_fastly/Cargo.toml b/crates/lib/docs_rs_fastly/Cargo.toml similarity index 100% rename from crates/docs_rs_fastly/Cargo.toml rename to crates/lib/docs_rs_fastly/Cargo.toml diff --git a/crates/docs_rs_fastly/src/config.rs b/crates/lib/docs_rs_fastly/src/config.rs similarity index 100% rename from crates/docs_rs_fastly/src/config.rs rename to crates/lib/docs_rs_fastly/src/config.rs diff --git a/crates/docs_rs_fastly/src/lib.rs b/crates/lib/docs_rs_fastly/src/lib.rs similarity index 100% rename from crates/docs_rs_fastly/src/lib.rs rename to crates/lib/docs_rs_fastly/src/lib.rs diff --git a/crates/docs_rs_fastly/src/metrics.rs b/crates/lib/docs_rs_fastly/src/metrics.rs similarity index 100% rename from crates/docs_rs_fastly/src/metrics.rs rename to crates/lib/docs_rs_fastly/src/metrics.rs diff --git a/crates/docs_rs_headers/Cargo.toml b/crates/lib/docs_rs_headers/Cargo.toml similarity index 100% rename from crates/docs_rs_headers/Cargo.toml rename to crates/lib/docs_rs_headers/Cargo.toml diff --git a/crates/docs_rs_headers/src/canonical_url.rs b/crates/lib/docs_rs_headers/src/canonical_url.rs similarity index 100% rename from crates/docs_rs_headers/src/canonical_url.rs rename to crates/lib/docs_rs_headers/src/canonical_url.rs diff --git a/crates/docs_rs_headers/src/etag.rs b/crates/lib/docs_rs_headers/src/etag.rs similarity index 100% rename from crates/docs_rs_headers/src/etag.rs rename to crates/lib/docs_rs_headers/src/etag.rs diff --git a/crates/docs_rs_headers/src/if_none_match.rs b/crates/lib/docs_rs_headers/src/if_none_match.rs similarity index 100% rename from crates/docs_rs_headers/src/if_none_match.rs rename to crates/lib/docs_rs_headers/src/if_none_match.rs diff --git a/crates/docs_rs_headers/src/lib.rs b/crates/lib/docs_rs_headers/src/lib.rs similarity index 100% rename from crates/docs_rs_headers/src/lib.rs rename to crates/lib/docs_rs_headers/src/lib.rs diff --git a/crates/docs_rs_headers/src/surrogate_key.rs b/crates/lib/docs_rs_headers/src/surrogate_key.rs similarity index 100% rename from crates/docs_rs_headers/src/surrogate_key.rs rename to crates/lib/docs_rs_headers/src/surrogate_key.rs diff --git a/crates/docs_rs_logging/Cargo.toml b/crates/lib/docs_rs_logging/Cargo.toml similarity index 100% rename from crates/docs_rs_logging/Cargo.toml rename to crates/lib/docs_rs_logging/Cargo.toml diff --git a/crates/docs_rs_logging/src/lib.rs b/crates/lib/docs_rs_logging/src/lib.rs similarity index 100% rename from crates/docs_rs_logging/src/lib.rs rename to crates/lib/docs_rs_logging/src/lib.rs diff --git a/crates/docs_rs_opentelemetry/Cargo.toml b/crates/lib/docs_rs_opentelemetry/Cargo.toml similarity index 100% rename from crates/docs_rs_opentelemetry/Cargo.toml rename to crates/lib/docs_rs_opentelemetry/Cargo.toml diff --git a/crates/docs_rs_opentelemetry/src/config.rs b/crates/lib/docs_rs_opentelemetry/src/config.rs similarity index 100% rename from crates/docs_rs_opentelemetry/src/config.rs rename to crates/lib/docs_rs_opentelemetry/src/config.rs diff --git a/crates/docs_rs_opentelemetry/src/lib.rs b/crates/lib/docs_rs_opentelemetry/src/lib.rs similarity index 100% rename from crates/docs_rs_opentelemetry/src/lib.rs rename to crates/lib/docs_rs_opentelemetry/src/lib.rs diff --git a/crates/docs_rs_registry_api/Cargo.toml b/crates/lib/docs_rs_registry_api/Cargo.toml similarity index 100% rename from crates/docs_rs_registry_api/Cargo.toml rename to crates/lib/docs_rs_registry_api/Cargo.toml diff --git a/crates/docs_rs_registry_api/src/config.rs b/crates/lib/docs_rs_registry_api/src/config.rs similarity index 100% rename from crates/docs_rs_registry_api/src/config.rs rename to crates/lib/docs_rs_registry_api/src/config.rs diff --git a/crates/docs_rs_registry_api/src/lib.rs b/crates/lib/docs_rs_registry_api/src/lib.rs similarity index 100% rename from crates/docs_rs_registry_api/src/lib.rs rename to crates/lib/docs_rs_registry_api/src/lib.rs diff --git a/crates/docs_rs_repository_stats/Cargo.toml b/crates/lib/docs_rs_repository_stats/Cargo.toml similarity index 100% rename from crates/docs_rs_repository_stats/Cargo.toml rename to crates/lib/docs_rs_repository_stats/Cargo.toml diff --git a/crates/docs_rs_repository_stats/src/config.rs b/crates/lib/docs_rs_repository_stats/src/config.rs similarity index 100% rename from crates/docs_rs_repository_stats/src/config.rs rename to crates/lib/docs_rs_repository_stats/src/config.rs diff --git a/crates/docs_rs_repository_stats/src/github.rs b/crates/lib/docs_rs_repository_stats/src/github.rs similarity index 100% rename from crates/docs_rs_repository_stats/src/github.rs rename to crates/lib/docs_rs_repository_stats/src/github.rs diff --git a/crates/docs_rs_repository_stats/src/gitlab.rs b/crates/lib/docs_rs_repository_stats/src/gitlab.rs similarity index 100% rename from crates/docs_rs_repository_stats/src/gitlab.rs rename to crates/lib/docs_rs_repository_stats/src/gitlab.rs diff --git a/crates/docs_rs_repository_stats/src/lib.rs b/crates/lib/docs_rs_repository_stats/src/lib.rs similarity index 100% rename from crates/docs_rs_repository_stats/src/lib.rs rename to crates/lib/docs_rs_repository_stats/src/lib.rs diff --git a/crates/docs_rs_repository_stats/src/mod.rs b/crates/lib/docs_rs_repository_stats/src/mod.rs similarity index 100% rename from crates/docs_rs_repository_stats/src/mod.rs rename to crates/lib/docs_rs_repository_stats/src/mod.rs diff --git a/crates/docs_rs_repository_stats/src/updater.rs b/crates/lib/docs_rs_repository_stats/src/updater.rs similarity index 100% rename from crates/docs_rs_repository_stats/src/updater.rs rename to crates/lib/docs_rs_repository_stats/src/updater.rs diff --git a/crates/docs_rs_storage/Cargo.toml b/crates/lib/docs_rs_storage/Cargo.toml similarity index 100% rename from crates/docs_rs_storage/Cargo.toml rename to crates/lib/docs_rs_storage/Cargo.toml diff --git a/crates/docs_rs_storage/benches/compression.rs b/crates/lib/docs_rs_storage/benches/compression.rs similarity index 100% rename from crates/docs_rs_storage/benches/compression.rs rename to crates/lib/docs_rs_storage/benches/compression.rs diff --git a/crates/docs_rs_storage/benches/struct.CaptureMatches.html b/crates/lib/docs_rs_storage/benches/struct.CaptureMatches.html similarity index 100% rename from crates/docs_rs_storage/benches/struct.CaptureMatches.html rename to crates/lib/docs_rs_storage/benches/struct.CaptureMatches.html diff --git a/crates/docs_rs_storage/src/archive_index.rs b/crates/lib/docs_rs_storage/src/archive_index.rs similarity index 100% rename from crates/docs_rs_storage/src/archive_index.rs rename to crates/lib/docs_rs_storage/src/archive_index.rs diff --git a/crates/docs_rs_storage/src/compression.rs b/crates/lib/docs_rs_storage/src/compression.rs similarity index 100% rename from crates/docs_rs_storage/src/compression.rs rename to crates/lib/docs_rs_storage/src/compression.rs diff --git a/crates/docs_rs_storage/src/config.rs b/crates/lib/docs_rs_storage/src/config.rs similarity index 100% rename from crates/docs_rs_storage/src/config.rs rename to crates/lib/docs_rs_storage/src/config.rs diff --git a/crates/docs_rs_storage/src/database.rs b/crates/lib/docs_rs_storage/src/database.rs similarity index 100% rename from crates/docs_rs_storage/src/database.rs rename to crates/lib/docs_rs_storage/src/database.rs diff --git a/crates/docs_rs_storage/src/errors.rs b/crates/lib/docs_rs_storage/src/errors.rs similarity index 100% rename from crates/docs_rs_storage/src/errors.rs rename to crates/lib/docs_rs_storage/src/errors.rs diff --git a/crates/docs_rs_storage/src/file.rs b/crates/lib/docs_rs_storage/src/file.rs similarity index 100% rename from crates/docs_rs_storage/src/file.rs rename to crates/lib/docs_rs_storage/src/file.rs diff --git a/crates/docs_rs_storage/src/lib.rs b/crates/lib/docs_rs_storage/src/lib.rs similarity index 100% rename from crates/docs_rs_storage/src/lib.rs rename to crates/lib/docs_rs_storage/src/lib.rs diff --git a/crates/docs_rs_storage/src/s3.rs b/crates/lib/docs_rs_storage/src/s3.rs similarity index 100% rename from crates/docs_rs_storage/src/s3.rs rename to crates/lib/docs_rs_storage/src/s3.rs diff --git a/crates/docs_rs_storage/src/utils/mod.rs b/crates/lib/docs_rs_storage/src/utils/mod.rs similarity index 100% rename from crates/docs_rs_storage/src/utils/mod.rs rename to crates/lib/docs_rs_storage/src/utils/mod.rs diff --git a/crates/docs_rs_storage/src/utils/sized_buffer.rs b/crates/lib/docs_rs_storage/src/utils/sized_buffer.rs similarity index 100% rename from crates/docs_rs_storage/src/utils/sized_buffer.rs rename to crates/lib/docs_rs_storage/src/utils/sized_buffer.rs diff --git a/crates/docs_rs_utils/Cargo.toml b/crates/lib/docs_rs_utils/Cargo.toml similarity index 100% rename from crates/docs_rs_utils/Cargo.toml rename to crates/lib/docs_rs_utils/Cargo.toml diff --git a/crates/docs_rs_utils/build.rs b/crates/lib/docs_rs_utils/build.rs similarity index 100% rename from crates/docs_rs_utils/build.rs rename to crates/lib/docs_rs_utils/build.rs diff --git a/crates/docs_rs_utils/src/lib.rs b/crates/lib/docs_rs_utils/src/lib.rs similarity index 100% rename from crates/docs_rs_utils/src/lib.rs rename to crates/lib/docs_rs_utils/src/lib.rs diff --git a/crates/docs_rs_utils/src/rustc_version.rs b/crates/lib/docs_rs_utils/src/rustc_version.rs similarity index 100% rename from crates/docs_rs_utils/src/rustc_version.rs rename to crates/lib/docs_rs_utils/src/rustc_version.rs diff --git a/crates/docs_rs_web_utils/Cargo.toml b/crates/lib/docs_rs_web_utils/Cargo.toml similarity index 100% rename from crates/docs_rs_web_utils/Cargo.toml rename to crates/lib/docs_rs_web_utils/Cargo.toml diff --git a/crates/docs_rs_web_utils/src/escaped_uri.rs b/crates/lib/docs_rs_web_utils/src/escaped_uri.rs similarity index 100% rename from crates/docs_rs_web_utils/src/escaped_uri.rs rename to crates/lib/docs_rs_web_utils/src/escaped_uri.rs diff --git a/crates/docs_rs_web_utils/src/lib.rs b/crates/lib/docs_rs_web_utils/src/lib.rs similarity index 100% rename from crates/docs_rs_web_utils/src/lib.rs rename to crates/lib/docs_rs_web_utils/src/lib.rs diff --git a/crates/font-awesome-as-a-crate/.gitignore b/crates/lib/font-awesome-as-a-crate/.gitignore similarity index 100% rename from crates/font-awesome-as-a-crate/.gitignore rename to crates/lib/font-awesome-as-a-crate/.gitignore diff --git a/crates/font-awesome-as-a-crate/Cargo.toml b/crates/lib/font-awesome-as-a-crate/Cargo.toml similarity index 100% rename from crates/font-awesome-as-a-crate/Cargo.toml rename to crates/lib/font-awesome-as-a-crate/Cargo.toml diff --git a/crates/font-awesome-as-a-crate/README.md b/crates/lib/font-awesome-as-a-crate/README.md similarity index 100% rename from crates/font-awesome-as-a-crate/README.md rename to crates/lib/font-awesome-as-a-crate/README.md diff --git a/crates/font-awesome-as-a-crate/build.rs b/crates/lib/font-awesome-as-a-crate/build.rs similarity index 100% rename from crates/font-awesome-as-a-crate/build.rs rename to crates/lib/font-awesome-as-a-crate/build.rs diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/LICENSE.txt b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/LICENSE.txt similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/LICENSE.txt rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/LICENSE.txt diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/VENDOR.md b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/VENDOR.md similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/VENDOR.md rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/VENDOR.md diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/42-group.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/42-group.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/42-group.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/42-group.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/500px.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/500px.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/500px.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/500px.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accessible-icon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accessible-icon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accessible-icon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accessible-icon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accusoft.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accusoft.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accusoft.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/accusoft.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adversal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adversal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adversal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/adversal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/affiliatetheme.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/affiliatetheme.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/affiliatetheme.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/affiliatetheme.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/airbnb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/airbnb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/airbnb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/airbnb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/algolia.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/algolia.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/algolia.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/algolia.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/alipay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/alipay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/alipay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/alipay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon-pay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon-pay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon-pay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon-pay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amazon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amilia.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amilia.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amilia.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/amilia.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/android.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/android.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/android.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/android.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angellist.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angellist.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angellist.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angellist.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angrycreative.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angrycreative.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angrycreative.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angrycreative.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angular.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angular.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angular.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/angular.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store-ios.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store-ios.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store-ios.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store-ios.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/app-store.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple-pay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple-pay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple-pay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple-pay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/apple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/artstation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/artstation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/artstation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/artstation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/asymmetrik.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/asymmetrik.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/asymmetrik.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/asymmetrik.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/atlassian.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/atlassian.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/atlassian.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/atlassian.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/audible.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/audible.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/audible.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/audible.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/autoprefixer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/autoprefixer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/autoprefixer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/autoprefixer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/avianex.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/avianex.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/avianex.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/avianex.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aviato.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aviato.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aviato.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aviato.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aws.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aws.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aws.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/aws.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bandcamp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bandcamp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bandcamp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bandcamp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/battle-net.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/battle-net.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/battle-net.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/battle-net.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/behance.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/behance.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/behance.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/behance.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bilibili.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bilibili.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bilibili.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bilibili.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bimobject.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bimobject.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bimobject.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bimobject.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitbucket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitbucket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitbucket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitbucket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitcoin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitcoin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitcoin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bitcoin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bity.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bity.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bity.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bity.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/black-tie.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/black-tie.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/black-tie.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/black-tie.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blackberry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blackberry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blackberry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blackberry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger-b.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger-b.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger-b.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger-b.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/blogger.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth-b.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth-b.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth-b.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth-b.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bluetooth.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bootstrap.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bootstrap.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bootstrap.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bootstrap.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bots.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bots.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bots.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/bots.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/btc.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/btc.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/btc.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/btc.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buffer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buffer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buffer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buffer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buromobelexperte.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buromobelexperte.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buromobelexperte.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buromobelexperte.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buy-n-large.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buy-n-large.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buy-n-large.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buy-n-large.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buysellads.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buysellads.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buysellads.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/buysellads.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/canadian-maple-leaf.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/canadian-maple-leaf.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/canadian-maple-leaf.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/canadian-maple-leaf.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amazon-pay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amazon-pay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amazon-pay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amazon-pay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amex.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amex.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amex.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-amex.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-apple-pay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-apple-pay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-apple-pay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-apple-pay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-diners-club.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-diners-club.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-diners-club.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-diners-club.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-discover.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-discover.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-discover.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-discover.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-jcb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-jcb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-jcb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-jcb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-mastercard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-mastercard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-mastercard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-mastercard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-paypal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-paypal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-paypal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-paypal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-stripe.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-stripe.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-stripe.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-stripe.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-visa.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-visa.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-visa.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cc-visa.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centercode.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centercode.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centercode.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centercode.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centos.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centos.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centos.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/centos.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chrome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chrome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chrome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chrome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chromecast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chromecast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chromecast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/chromecast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudflare.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudflare.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudflare.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudflare.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudscale.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudscale.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudscale.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudscale.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudsmith.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudsmith.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudsmith.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudsmith.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudversify.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudversify.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudversify.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cloudversify.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cmplid.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cmplid.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cmplid.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cmplid.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codepen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codepen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codepen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codepen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codiepie.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codiepie.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codiepie.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/codiepie.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/confluence.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/confluence.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/confluence.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/confluence.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/connectdevelop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/connectdevelop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/connectdevelop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/connectdevelop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/contao.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/contao.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/contao.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/contao.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cotton-bureau.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cotton-bureau.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cotton-bureau.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cotton-bureau.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cpanel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cpanel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cpanel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cpanel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-by.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-by.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-by.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-by.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-eu.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-eu.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-eu.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-eu.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-jp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-jp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-jp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc-jp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nc.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nd.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nd.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nd.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-nd.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-pd.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-remix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-remix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-remix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-remix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sa.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sa.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sa.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sa.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-sampling.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-share.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-share.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-share.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-share.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-zero.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-zero.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-zero.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons-zero.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/creative-commons.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/critical-role.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/critical-role.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/critical-role.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/critical-role.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/css3.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cuttlefish.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cuttlefish.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cuttlefish.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/cuttlefish.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d-beyond.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d-beyond.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d-beyond.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d-beyond.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/d-and-d.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dailymotion.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dailymotion.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dailymotion.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dailymotion.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dashcube.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dashcube.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dashcube.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dashcube.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deezer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deezer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deezer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deezer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/delicious.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/delicious.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/delicious.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/delicious.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deploydog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deploydog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deploydog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deploydog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deskpro.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deskpro.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deskpro.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deskpro.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dev.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dev.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dev.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dev.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deviantart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deviantart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deviantart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/deviantart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dhl.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dhl.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dhl.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dhl.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/diaspora.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/diaspora.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/diaspora.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/diaspora.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digg.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digg.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digg.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digg.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digital-ocean.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digital-ocean.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digital-ocean.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/digital-ocean.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discord.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discord.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discord.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discord.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discourse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discourse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discourse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/discourse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dochub.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dochub.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dochub.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dochub.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/docker.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/docker.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/docker.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/docker.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/draft2digital.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/draft2digital.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/draft2digital.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/draft2digital.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dribbble.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dribbble.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dribbble.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dribbble.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dropbox.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dropbox.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dropbox.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dropbox.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/drupal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/drupal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/drupal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/drupal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dyalog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dyalog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dyalog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/dyalog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/earlybirds.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/earlybirds.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/earlybirds.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/earlybirds.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ebay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ebay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ebay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ebay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge-legacy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge-legacy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge-legacy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge-legacy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/edge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/elementor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/elementor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/elementor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/elementor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ello.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ello.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ello.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ello.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ember.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ember.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ember.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ember.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/empire.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/empire.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/empire.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/empire.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/envira.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/envira.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/envira.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/envira.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/erlang.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/erlang.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/erlang.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/erlang.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ethereum.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ethereum.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ethereum.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ethereum.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/etsy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/etsy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/etsy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/etsy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/evernote.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/evernote.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/evernote.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/evernote.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/expeditedssl.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/expeditedssl.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/expeditedssl.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/expeditedssl.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-f.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-f.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-f.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-f.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-messenger.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-messenger.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-messenger.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook-messenger.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/facebook.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fantasy-flight-games.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fantasy-flight-games.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fantasy-flight-games.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fantasy-flight-games.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedex.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedex.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedex.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedex.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedora.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedora.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedora.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fedora.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/figma.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/figma.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/figma.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/figma.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox-browser.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox-browser.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox-browser.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox-browser.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firefox.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/first-order.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firstdraft.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firstdraft.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firstdraft.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/firstdraft.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flickr.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flickr.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flickr.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flickr.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flipboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flipboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flipboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/flipboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fly.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fly.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fly.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fly.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/font-awesome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/font-awesome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/font-awesome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/font-awesome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons-fi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons-fi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons-fi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons-fi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fonticons.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fort-awesome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/forumbee.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/forumbee.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/forumbee.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/forumbee.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/foursquare.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/foursquare.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/foursquare.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/foursquare.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/free-code-camp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/free-code-camp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/free-code-camp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/free-code-camp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/freebsd.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/freebsd.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/freebsd.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/freebsd.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fulcrum.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fulcrum.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fulcrum.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/fulcrum.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-republic.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-republic.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-republic.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-republic.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-senate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-senate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-senate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/galactic-senate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/get-pocket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/get-pocket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/get-pocket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/get-pocket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg-circle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg-circle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg-circle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg-circle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gg.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/git.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/github.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitkraken.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitkraken.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitkraken.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitkraken.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitlab.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitlab.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitlab.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitlab.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gitter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide-g.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide-g.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide-g.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide-g.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/glide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gofore.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gofore.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gofore.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gofore.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/golang.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/golang.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/golang.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/golang.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads-g.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads-g.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads-g.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads-g.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/goodreads.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-drive.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-drive.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-drive.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-drive.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-pay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-pay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-pay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-pay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-play.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-play.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-play.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-play.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus-g.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus-g.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus-g.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus-g.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-wallet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-wallet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-wallet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google-wallet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/google.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gratipay.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gratipay.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gratipay.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gratipay.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grav.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grav.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grav.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grav.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gripfire.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gripfire.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gripfire.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gripfire.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grunt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grunt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grunt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/grunt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/guilded.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/guilded.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/guilded.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/guilded.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gulp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gulp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gulp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/gulp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hacker-news.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hacker-news.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hacker-news.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hacker-news.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hackerrank.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hackerrank.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hackerrank.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hackerrank.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hashnode.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hashnode.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hashnode.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hashnode.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hips.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hips.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hips.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hips.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hire-a-helper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hire-a-helper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hire-a-helper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hire-a-helper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hive.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hive.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hive.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hive.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hooli.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hooli.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hooli.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hooli.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hornbill.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hornbill.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hornbill.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hornbill.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hotjar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hotjar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hotjar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hotjar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/houzz.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/houzz.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/houzz.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/houzz.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/html5.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/html5.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/html5.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/html5.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hubspot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hubspot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hubspot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/hubspot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ideal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ideal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ideal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ideal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/imdb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/imdb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/imdb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/imdb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instagram.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instagram.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instagram.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instagram.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instalod.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instalod.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instalod.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/instalod.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/intercom.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/intercom.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/intercom.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/intercom.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/internet-explorer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/internet-explorer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/internet-explorer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/internet-explorer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/invision.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/invision.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/invision.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/invision.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ioxhost.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ioxhost.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ioxhost.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ioxhost.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itch-io.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itch-io.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itch-io.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itch-io.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes-note.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes-note.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes-note.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes-note.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/itunes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/java.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/java.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/java.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/java.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jedi-order.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jedi-order.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jedi-order.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jedi-order.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jenkins.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jenkins.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jenkins.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jenkins.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jira.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jira.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jira.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jira.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joget.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joget.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joget.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joget.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joomla.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joomla.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joomla.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/joomla.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/js.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/js.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/js.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/js.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jsfiddle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jsfiddle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jsfiddle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/jsfiddle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kaggle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kaggle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kaggle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kaggle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keybase.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keybase.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keybase.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keybase.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keycdn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keycdn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keycdn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/keycdn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter-k.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter-k.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter-k.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter-k.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/kickstarter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/korvue.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/korvue.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/korvue.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/korvue.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/laravel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/laravel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/laravel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/laravel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lastfm.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lastfm.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lastfm.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lastfm.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/leanpub.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/leanpub.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/leanpub.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/leanpub.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/less.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/less.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/less.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/less.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin-in.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin-in.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin-in.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin-in.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linkedin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linode.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linode.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linode.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linode.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linux.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linux.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linux.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/linux.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lyft.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lyft.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lyft.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/lyft.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/magento.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/magento.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/magento.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/magento.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mailchimp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mailchimp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mailchimp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mailchimp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mandalorian.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mandalorian.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mandalorian.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mandalorian.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/markdown.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/markdown.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/markdown.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/markdown.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mastodon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mastodon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mastodon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mastodon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/maxcdn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/maxcdn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/maxcdn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/maxcdn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mdb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mdb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mdb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mdb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medapps.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medapps.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medapps.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medapps.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medium.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medium.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medium.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medium.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medrt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medrt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medrt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/medrt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meetup.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meetup.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meetup.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meetup.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/megaport.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/megaport.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/megaport.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/megaport.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mendeley.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mendeley.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mendeley.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mendeley.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meta.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meta.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meta.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/meta.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microblog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microblog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microblog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microblog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microsoft.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microsoft.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microsoft.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/microsoft.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixcloud.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixcloud.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixcloud.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixcloud.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mixer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mizuni.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mizuni.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mizuni.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/mizuni.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/modx.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/modx.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/modx.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/modx.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/monero.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/monero.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/monero.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/monero.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/napster.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/napster.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/napster.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/napster.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/neos.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/neos.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/neos.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/neos.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-directional.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-directional.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-directional.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-directional.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-symbol.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-symbol.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-symbol.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nfc-symbol.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nimblr.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nimblr.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nimblr.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nimblr.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node-js.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node-js.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node-js.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node-js.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/node.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/npm.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/npm.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/npm.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/npm.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ns8.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ns8.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ns8.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ns8.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nutritionix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nutritionix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nutritionix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/nutritionix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/octopus-deploy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/octopus-deploy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/octopus-deploy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/octopus-deploy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/odnoklassniki.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/odnoklassniki.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/odnoklassniki.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/odnoklassniki.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/old-republic.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/old-republic.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/old-republic.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/old-republic.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opencart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opencart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opencart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opencart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/openid.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/openid.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/openid.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/openid.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opera.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opera.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opera.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/opera.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/optin-monster.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/optin-monster.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/optin-monster.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/optin-monster.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/orcid.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/orcid.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/orcid.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/orcid.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/osi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/osi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/osi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/osi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/padlet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/padlet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/padlet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/padlet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/page4.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/page4.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/page4.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/page4.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pagelines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pagelines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pagelines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pagelines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/palfed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/palfed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/palfed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/palfed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/patreon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/patreon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/patreon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/patreon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/paypal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/paypal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/paypal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/paypal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/perbyte.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/perbyte.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/perbyte.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/perbyte.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/periscope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/periscope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/periscope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/periscope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phabricator.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phabricator.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phabricator.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phabricator.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-framework.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-framework.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-framework.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-framework.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-squadron.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-squadron.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-squadron.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/phoenix-squadron.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/php.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/php.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/php.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/php.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-alt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-alt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-alt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-alt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-hat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-hat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-hat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-hat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-pp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-pp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-pp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper-pp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pied-piper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest-p.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest-p.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest-p.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest-p.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pinterest.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/playstation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/playstation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/playstation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/playstation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/product-hunt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/product-hunt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/product-hunt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/product-hunt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pushed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pushed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pushed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/pushed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/python.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/python.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/python.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/python.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/qq.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/qq.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/qq.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/qq.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quinscape.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quinscape.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quinscape.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quinscape.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quora.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quora.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quora.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/quora.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/r-project.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/r-project.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/r-project.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/r-project.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/raspberry-pi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/raspberry-pi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/raspberry-pi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/raspberry-pi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ravelry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ravelry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ravelry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ravelry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/react.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/react.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/react.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/react.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reacteurope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reacteurope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reacteurope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reacteurope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/readme.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/readme.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/readme.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/readme.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rebel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rebel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rebel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rebel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/red-river.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/red-river.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/red-river.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/red-river.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit-alien.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit-alien.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit-alien.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit-alien.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/reddit.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/redhat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/redhat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/redhat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/redhat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/renren.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/renren.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/renren.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/renren.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/replyd.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/replyd.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/replyd.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/replyd.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/researchgate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/researchgate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/researchgate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/researchgate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/resolving.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/resolving.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/resolving.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/resolving.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rev.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rev.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rev.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rev.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rocketchat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rocketchat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rocketchat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rocketchat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rockrms.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rockrms.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rockrms.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rockrms.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rust.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rust.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rust.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/rust.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/safari.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/safari.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/safari.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/safari.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/salesforce.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/salesforce.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/salesforce.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/salesforce.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/schlix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/schlix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/schlix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/schlix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/screenpal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/screenpal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/screenpal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/screenpal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/scribd.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/scribd.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/scribd.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/scribd.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/searchengin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/searchengin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/searchengin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/searchengin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellcast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellcast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellcast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellcast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellsy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellsy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellsy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sellsy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/servicestack.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/servicestack.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/servicestack.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/servicestack.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shirtsinbulk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shirtsinbulk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shirtsinbulk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shirtsinbulk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopify.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopify.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopify.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopify.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopware.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopware.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopware.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/shopware.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/simplybuilt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/simplybuilt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/simplybuilt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/simplybuilt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sistrix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sistrix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sistrix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sistrix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sith.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sith.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sith.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sith.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sitrox.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sitrox.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sitrox.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sitrox.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sketch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sketch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sketch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sketch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skyatlas.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skyatlas.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skyatlas.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skyatlas.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skype.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skype.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skype.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/skype.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slack.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slack.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slack.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slack.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slideshare.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slideshare.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slideshare.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/slideshare.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/snapchat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/snapchat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/snapchat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/snapchat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/soundcloud.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/soundcloud.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/soundcloud.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/soundcloud.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sourcetree.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sourcetree.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sourcetree.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sourcetree.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/space-awesome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/space-awesome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/space-awesome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/space-awesome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speakap.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speakap.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speakap.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speakap.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speaker-deck.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speaker-deck.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speaker-deck.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/speaker-deck.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/spotify.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/spotify.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/spotify.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/spotify.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-behance.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-behance.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-behance.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-behance.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-dribbble.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-dribbble.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-dribbble.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-dribbble.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-facebook.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-facebook.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-facebook.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-facebook.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome-stroke.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome-stroke.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome-stroke.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome-stroke.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-font-awesome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-git.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-git.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-git.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-git.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-github.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-github.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-github.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-github.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-gitlab.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-gitlab.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-gitlab.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-gitlab.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-google-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-google-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-google-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-google-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-hacker-news.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-hacker-news.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-hacker-news.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-hacker-news.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-instagram.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-instagram.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-instagram.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-instagram.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-js.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-js.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-js.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-js.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-lastfm.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-lastfm.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-lastfm.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-lastfm.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-odnoklassniki.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-odnoklassniki.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-odnoklassniki.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-odnoklassniki.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pied-piper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pied-piper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pied-piper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pied-piper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pinterest.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pinterest.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pinterest.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-pinterest.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-reddit.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-reddit.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-reddit.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-reddit.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-snapchat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-snapchat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-snapchat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-snapchat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-steam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-steam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-steam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-steam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-tumblr.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-tumblr.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-tumblr.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-tumblr.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-twitter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-twitter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-twitter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-twitter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-viadeo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-viadeo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-viadeo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-viadeo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-vimeo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-vimeo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-vimeo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-vimeo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-whatsapp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-whatsapp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-whatsapp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-whatsapp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-xing.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-xing.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-xing.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-xing.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-youtube.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-youtube.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-youtube.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/square-youtube.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/squarespace.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/squarespace.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/squarespace.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/squarespace.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-exchange.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-exchange.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-exchange.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-exchange.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-overflow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-overflow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-overflow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stack-overflow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stackpath.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stackpath.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stackpath.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stackpath.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/staylinked.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/staylinked.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/staylinked.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/staylinked.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam-symbol.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam-symbol.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam-symbol.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam-symbol.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/steam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sticker-mule.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sticker-mule.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sticker-mule.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/sticker-mule.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/strava.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/strava.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/strava.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/strava.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe-s.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe-s.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe-s.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe-s.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stripe.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/studiovinari.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/studiovinari.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/studiovinari.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/studiovinari.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon-circle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon-circle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon-circle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon-circle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/stumbleupon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/superpowers.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/superpowers.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/superpowers.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/superpowers.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/supple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/supple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/supple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/supple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/suse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/suse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/suse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/suse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/swift.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/swift.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/swift.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/swift.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/symfony.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/symfony.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/symfony.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/symfony.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/teamspeak.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/teamspeak.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/teamspeak.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/teamspeak.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/telegram.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/telegram.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/telegram.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/telegram.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tencent-weibo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tencent-weibo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tencent-weibo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tencent-weibo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/the-red-yeti.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/the-red-yeti.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/the-red-yeti.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/the-red-yeti.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeco.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeco.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeco.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeco.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeisle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeisle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeisle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/themeisle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/think-peaks.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/think-peaks.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/think-peaks.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/think-peaks.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tiktok.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tiktok.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tiktok.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tiktok.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trade-federation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trade-federation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trade-federation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trade-federation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trello.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trello.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trello.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/trello.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tumblr.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tumblr.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tumblr.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/tumblr.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/twitter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/typo3.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/typo3.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/typo3.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/typo3.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uber.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uber.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uber.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uber.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ubuntu.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ubuntu.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ubuntu.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ubuntu.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uikit.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uikit.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uikit.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uikit.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/umbraco.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/umbraco.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/umbraco.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/umbraco.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uncharted.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uncharted.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uncharted.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uncharted.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uniregistry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uniregistry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uniregistry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/uniregistry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unity.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unity.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unity.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unity.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unsplash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unsplash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unsplash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/unsplash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/untappd.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/untappd.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/untappd.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/untappd.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ups.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ups.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ups.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ups.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usps.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usps.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usps.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/usps.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ussunnah.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ussunnah.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ussunnah.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/ussunnah.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vaadin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vaadin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vaadin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vaadin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viacoin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viacoin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viacoin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viacoin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viadeo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viadeo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viadeo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viadeo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viber.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viber.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viber.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/viber.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo-v.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo-v.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo-v.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo-v.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vimeo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vine.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vine.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vine.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vine.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vnv.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vnv.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vnv.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vnv.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vuejs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vuejs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vuejs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/vuejs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/watchman-monitoring.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/watchman-monitoring.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/watchman-monitoring.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/watchman-monitoring.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/waze.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/waze.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/waze.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/waze.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weebly.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weebly.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weebly.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weebly.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weibo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weibo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weibo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weibo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weixin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weixin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weixin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/weixin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whatsapp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whatsapp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whatsapp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whatsapp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whmcs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whmcs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whmcs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/whmcs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wikipedia-w.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wikipedia-w.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wikipedia-w.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wikipedia-w.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/windows.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/windows.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/windows.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/windows.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wirsindhandwerk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wirsindhandwerk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wirsindhandwerk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wirsindhandwerk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wix.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wix.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wix.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wix.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wizards-of-the-coast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wizards-of-the-coast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wizards-of-the-coast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wizards-of-the-coast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wodu.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wodu.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wodu.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wodu.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wolf-pack-battalion.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wolf-pack-battalion.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wolf-pack-battalion.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wolf-pack-battalion.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wordpress.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpbeginner.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpbeginner.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpbeginner.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpbeginner.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpexplorer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpexplorer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpexplorer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpexplorer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpforms.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpforms.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpforms.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpforms.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpressr.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpressr.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpressr.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/wpressr.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xbox.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xbox.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xbox.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xbox.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xing.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xing.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xing.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/xing.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/y-combinator.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/y-combinator.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/y-combinator.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/y-combinator.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yahoo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yahoo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yahoo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yahoo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yammer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yammer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yammer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yammer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex-international.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex-international.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex-international.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex-international.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yandex.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yarn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yarn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yarn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yarn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yelp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yelp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yelp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yelp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yoast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yoast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yoast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/yoast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/youtube.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/youtube.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/youtube.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/youtube.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/zhihu.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/zhihu.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/zhihu.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/brands/zhihu.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-book.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-book.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-book.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-book.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/address-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bell.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bookmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bookmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bookmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/bookmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/building.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/building.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/building.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/building.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-days.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-days.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-days.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-days.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/calendar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chart-bar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chart-bar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chart-bar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chart-bar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-bishop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-bishop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-bishop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-bishop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-king.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-king.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-king.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-king.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-knight.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-knight.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-knight.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-knight.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-pawn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-pawn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-pawn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-pawn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-queen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-queen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-queen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-queen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-rook.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-rook.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-rook.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/chess-rook.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-dot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-dot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-dot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-dot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-pause.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-pause.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-pause.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-pause.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-play.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-play.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-play.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-play.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-question.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-question.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-question.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-question.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-stop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-stop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-stop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-stop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/circle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clipboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clipboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clipboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clipboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/clone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/closed-captioning.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/closed-captioning.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/closed-captioning.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/closed-captioning.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment-dots.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment-dots.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment-dots.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment-dots.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comment.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comments.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comments.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comments.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/comments.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/compass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/compass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/compass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/compass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copyright.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copyright.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copyright.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/copyright.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/credit-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/credit-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/credit-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/credit-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/envelope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/eye.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-angry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-angry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-angry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-angry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-dizzy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-dizzy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-dizzy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-dizzy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-flushed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-flushed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-flushed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-flushed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-frown.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grimace.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grimace.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grimace.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grimace.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam-sweat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam-sweat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam-sweat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam-sweat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-hearts.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-hearts.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-hearts.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-hearts.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint-tears.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint-tears.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint-tears.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint-tears.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-squint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-stars.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-stars.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-stars.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-stars.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tears.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tears.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tears.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tears.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-squint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-squint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-squint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-squint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-tongue.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-grin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-wink-heart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-wink-heart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-wink-heart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss-wink-heart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-kiss.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-squint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-squint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-squint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-squint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-laugh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh-blank.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh-blank.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh-blank.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh-blank.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-meh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-rolling-eyes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-rolling-eyes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-rolling-eyes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-rolling-eyes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-cry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-cry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-cry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-cry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-tear.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-tear.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-tear.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-sad-tear.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-smile.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-surprise.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-surprise.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-surprise.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-surprise.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-tired.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-tired.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-tired.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/face-tired.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-audio.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-audio.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-audio.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-audio.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-code.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-code.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-code.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-code.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-excel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-excel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-excel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-excel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-image.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-image.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-image.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-image.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-lines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-lines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-lines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-lines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-pdf.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-pdf.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-pdf.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-pdf.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-powerpoint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-powerpoint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-powerpoint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-powerpoint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-video.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-video.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-video.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-video.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-word.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-word.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-word.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-word.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-zipper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-zipper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-zipper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file-zipper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/file.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/floppy-disk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/floppy-disk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/floppy-disk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/floppy-disk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-closed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-closed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-closed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-closed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/folder.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/font-awesome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/font-awesome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/font-awesome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/font-awesome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/futbol.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/futbol.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/futbol.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/futbol.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/gem.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/gem.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/gem.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/gem.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-back-fist.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-back-fist.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-back-fist.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-back-fist.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-lizard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-lizard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-lizard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-lizard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-peace.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-peace.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-peace.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-peace.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-point-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-pointer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-pointer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-pointer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-pointer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-scissors.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-scissors.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-scissors.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-scissors.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-spock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-spock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-spock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand-spock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hand.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/handshake.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/handshake.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/handshake.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/handshake.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hard-drive.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hard-drive.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hard-drive.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hard-drive.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/heart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/heart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/heart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/heart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hospital.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hospital.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hospital.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hospital.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass-half.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass-half.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass-half.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass-half.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/hourglass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-badge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-badge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-badge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-badge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/id-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/image.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/image.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/image.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/image.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/images.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/images.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/images.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/images.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/keyboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/keyboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/keyboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/keyboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lemon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lemon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lemon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lemon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/life-ring.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/life-ring.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/life-ring.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/life-ring.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lightbulb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lightbulb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lightbulb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/lightbulb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/map.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/map.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/map.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/map.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/message.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/message.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/message.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/message.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/money-bill-1.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/money-bill-1.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/money-bill-1.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/money-bill-1.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/moon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/moon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/moon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/moon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/newspaper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/newspaper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/newspaper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/newspaper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/note-sticky.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/note-sticky.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/note-sticky.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/note-sticky.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-group.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-group.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-group.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-group.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-ungroup.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-ungroup.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-ungroup.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/object-ungroup.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paper-plane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paper-plane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paper-plane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paper-plane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paste.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paste.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paste.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/paste.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/pen-to-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/pen-to-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/pen-to-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/pen-to-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-list.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-list.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-list.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-list.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/rectangle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/registered.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/registered.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/registered.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/registered.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/share-from-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/share-from-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/share-from-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/share-from-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/snowflake.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/snowflake.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/snowflake.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/snowflake.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-caret-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-full.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-full.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-full.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-full.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half-stroke.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half-stroke.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half-stroke.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half-stroke.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star-half.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/star.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/sun.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/sun.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/sun.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/sun.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/thumbs-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/trash-can.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/trash-can.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/trash-can.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/trash-can.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-maximize.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-maximize.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-maximize.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-maximize.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-minimize.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-minimize.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-minimize.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-minimize.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-restore.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-restore.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-restore.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/regular/window-restore.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/0.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/0.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/0.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/0.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/1.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/1.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/1.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/1.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/2.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/2.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/2.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/2.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/3.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/3.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/3.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/3.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/4.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/4.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/4.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/4.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/5.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/5.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/5.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/5.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/6.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/6.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/6.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/6.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/7.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/7.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/7.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/7.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/8.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/8.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/8.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/8.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/9.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/9.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/9.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/9.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/a.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/a.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/a.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/a.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-book.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-book.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-book.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-book.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/address-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-center.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-center.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-center.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-center.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-justify.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-justify.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-justify.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-justify.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/align-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/anchor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angle-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/angles-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ankh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ankh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ankh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ankh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/apple-whole.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/apple-whole.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/apple-whole.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/apple-whole.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/archway.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/archway.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/archway.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/archway.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-1-9.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-1-9.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-1-9.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-1-9.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-9-1.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-9-1.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-9-1.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-9-1.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-a-z.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-a-z.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-a-z.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-a-z.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-short-wide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-short-wide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-short-wide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-short-wide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-across-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-across-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-across-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-across-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-up-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-wide-short.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-wide-short.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-wide-short.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-wide-short.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-z-a.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-z-a.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-z-a.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down-z-a.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-pointer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-pointer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-pointer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-pointer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-arrow-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-arrow-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-arrow-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-arrow-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-from-bracket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-from-bracket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-from-bracket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-from-bracket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-bracket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-bracket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-bracket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-bracket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-city.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-city.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-city.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right-to-city.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-rotate-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-trend-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-turn-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-1-9.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-1-9.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-1-9.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-1-9.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-9-1.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-9-1.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-9-1.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-9-1.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-a-z.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-a-z.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-a-z.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-a-z.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-bracket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-bracket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-bracket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-bracket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-ground-water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-ground-water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-ground-water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-ground-water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-water-pump.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-water-pump.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-water-pump.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-from-water-pump.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-dots.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-dots.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-dots.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-dots.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-from-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-from-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-from-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-right-from-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-short-wide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-short-wide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-short-wide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-short-wide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-wide-short.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-wide-short.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-wide-short.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-wide-short.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-z-a.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-z-a.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-z-a.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up-z-a.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-people.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-people.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-people.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-down-to-people.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right-to-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right-to-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right-to-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right-to-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-left-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-rotate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-rotate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-rotate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-rotate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-spin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-spin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-spin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-spin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-split-up-and-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-split-up-and-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-split-up-and-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-split-up-and-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-circle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-circle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-circle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-circle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-dot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-dot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-dot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-dot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-eye.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-eye.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-eye.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-to-eye.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-to-dots.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-to-dots.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-to-dots.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-turn-to-dots.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down-left-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down-left-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down-left-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down-left-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-to-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-to-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-to-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/arrows-up-to-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/asterisk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/asterisk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/asterisk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/asterisk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/at.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/at.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/at.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/at.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/atom.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/atom.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/atom.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/atom.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/audio-description.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/audio-description.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/audio-description.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/audio-description.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/austral-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/austral-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/austral-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/austral-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/award.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/award.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/award.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/award.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/b.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/b.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/b.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/b.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby-carriage.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby-carriage.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby-carriage.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby-carriage.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baby.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-fast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-fast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-fast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-fast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-step.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-step.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-step.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward-step.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/backward.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacteria.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacteria.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacteria.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacteria.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacterium.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacterium.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacterium.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bacterium.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bag-shopping.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bag-shopping.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bag-shopping.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bag-shopping.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bahai.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bahai.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bahai.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bahai.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baht-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baht-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baht-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baht-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban-smoking.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban-smoking.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban-smoking.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban-smoking.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ban.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bandage.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bandage.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bandage.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bandage.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/barcode.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/barcode.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/barcode.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/barcode.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-progress.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-progress.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-progress.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-progress.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-staggered.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-staggered.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-staggered.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars-staggered.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bars.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball-bat-ball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball-bat-ball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball-bat-ball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball-bat-ball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/baseball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basket-shopping.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basket-shopping.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basket-shopping.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basket-shopping.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basketball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basketball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basketball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/basketball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bath.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bath.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bath.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bath.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-empty.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-empty.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-empty.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-empty.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-full.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-full.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-full.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-full.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-half.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-half.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-half.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-half.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-quarter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-quarter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-quarter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-quarter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-three-quarters.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-three-quarters.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-three-quarters.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/battery-three-quarters.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed-pulse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed-pulse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed-pulse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed-pulse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/beer-mug-empty.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/beer-mug-empty.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/beer-mug-empty.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/beer-mug-empty.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-concierge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-concierge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-concierge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-concierge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bell.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bezier-curve.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bezier-curve.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bezier-curve.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bezier-curve.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bicycle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bicycle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bicycle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bicycle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/binoculars.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/binoculars.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/binoculars.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/binoculars.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/biohazard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/biohazard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/biohazard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/biohazard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bitcoin-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bitcoin-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bitcoin-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bitcoin-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender-phone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender-phone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender-phone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender-phone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blender.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/blog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bold.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bold.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bold.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bold.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt-lightning.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt-lightning.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt-lightning.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt-lightning.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bolt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bomb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bomb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bomb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bomb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bong.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bong.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bong.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bong.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-atlas.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-atlas.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-atlas.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-atlas.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bible.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bible.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bible.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bible.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bookmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bookmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bookmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-bookmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-journal-whills.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-journal-whills.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-journal-whills.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-journal-whills.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open-reader.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open-reader.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open-reader.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open-reader.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-quran.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-quran.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-quran.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-quran.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-skull.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-skull.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-skull.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-skull.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-tanakh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-tanakh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-tanakh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book-tanakh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/book.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bookmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bookmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bookmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bookmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-all.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-all.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-all.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-all.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-none.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-none.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-none.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-none.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-top-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-top-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-top-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/border-top-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bore-hole.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bore-hole.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bore-hole.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bore-hole.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-droplet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-droplet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-droplet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-droplet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bottle-water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-food.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-food.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-food.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-food.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-rice.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-rice.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-rice.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowl-rice.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowling-ball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowling-ball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowling-ball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bowling-ball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-archive.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-archive.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-archive.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-archive.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-tissue.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-tissue.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-tissue.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box-tissue.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/box.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-packing.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-packing.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-packing.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-packing.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-stacked.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-stacked.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-stacked.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/boxes-stacked.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/braille.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/braille.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/braille.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/braille.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brain.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brain.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brain.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brain.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brazilian-real-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brazilian-real-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brazilian-real-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brazilian-real-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bread-slice.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bread-slice.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bread-slice.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bread-slice.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge-water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bridge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/briefcase.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom-ball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom-ball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom-ball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom-ball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/broom.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brush.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brush.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brush.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/brush.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bucket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bucket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bucket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bucket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bug.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bugs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bugs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bugs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bugs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-columns.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-columns.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-columns.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-columns.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-ngo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-ngo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-ngo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-ngo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-shield.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-shield.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-shield.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-shield.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-un.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-un.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-un.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-un.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-wheat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-wheat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-wheat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building-wheat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/building.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullhorn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullhorn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullhorn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullhorn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullseye.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullseye.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullseye.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bullseye.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burger.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burger.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burger.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burger.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burst.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burst.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burst.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/burst.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/bus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/business-time.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/business-time.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/business-time.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/business-time.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/c.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/c.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/c.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/c.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cable-car.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cable-car.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cable-car.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cable-car.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cake-candles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cake-candles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cake-candles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cake-candles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calculator.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calculator.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calculator.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calculator.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-day.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-day.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-day.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-day.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-days.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-days.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-days.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-days.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-week.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-week.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-week.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-week.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/calendar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-retro.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-retro.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-retro.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-retro.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-rotate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-rotate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-rotate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera-rotate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/camera.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/campground.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/campground.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/campground.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/campground.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/candy-cane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/candy-cane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/candy-cane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/candy-cane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cannabis.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cannabis.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cannabis.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cannabis.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/capsules.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/capsules.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/capsules.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/capsules.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-battery.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-battery.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-battery.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-battery.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-burst.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-burst.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-burst.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-burst.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-on.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-on.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-on.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-on.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-rear.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-rear.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-rear.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-rear.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-side.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-side.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-side.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-side.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-tunnel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-tunnel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-tunnel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car-tunnel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/car.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caravan.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caravan.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caravan.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caravan.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/caret-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/carrot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/carrot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/carrot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/carrot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-arrow-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-arrow-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-arrow-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-arrow-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed-suitcase.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed-suitcase.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed-suitcase.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed-suitcase.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-flatbed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-shopping.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-shopping.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-shopping.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cart-shopping.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cash-register.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cash-register.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cash-register.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cash-register.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cedi-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cedi-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cedi-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cedi-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cent-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cent-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cent-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cent-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/certificate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/certificate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/certificate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/certificate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chair.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chair.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chair.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chair.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chalkboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/champagne-glasses.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/champagne-glasses.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/champagne-glasses.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/champagne-glasses.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/charging-station.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/charging-station.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/charging-station.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/charging-station.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-area.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-area.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-area.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-area.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-bar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-bar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-bar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-bar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-column.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-column.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-column.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-column.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-gantt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-gantt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-gantt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-gantt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-pie.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-pie.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-pie.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-pie.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chart-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-double.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-double.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-double.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-double.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-to-slot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-to-slot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-to-slot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check-to-slot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cheese.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cheese.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cheese.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cheese.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-bishop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-bishop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-bishop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-bishop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-board.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-board.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-board.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-board.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-king.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-king.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-king.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-king.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-knight.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-knight.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-knight.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-knight.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-pawn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-pawn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-pawn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-pawn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-queen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-queen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-queen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-queen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-rook.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-rook.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-rook.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess-rook.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chess.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/chevron-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-dress.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-dress.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-dress.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-dress.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-reaching.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-reaching.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-reaching.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-reaching.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-rifle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-rifle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-rifle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child-rifle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/child.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/children.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/children.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/children.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/children.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/church.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/church.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/church.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/church.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-chevron-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dollar-to-slot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dollar-to-slot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dollar-to-slot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dollar-to-slot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-dot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-h.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-h.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-h.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-h.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-half-stroke.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-half-stroke.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-half-stroke.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-half-stroke.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-info.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-info.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-info.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-info.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-nodes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-nodes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-nodes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-nodes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-notch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-notch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-notch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-notch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-pause.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-pause.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-pause.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-pause.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-play.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-play.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-play.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-play.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-question.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-question.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-question.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-question.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-radiation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-radiation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-radiation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-radiation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-stop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-stop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-stop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-stop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/circle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/city.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/city.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/city.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/city.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clapperboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clapperboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clapperboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clapperboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-list.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-list.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-list.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-list.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-question.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-question.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-question.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-question.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clipboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock-rotate-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock-rotate-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock-rotate-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock-rotate-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/closed-captioning.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/closed-captioning.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/closed-captioning.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/closed-captioning.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-bolt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-bolt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-bolt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-bolt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-meatball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-meatball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-meatball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-meatball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon-rain.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon-rain.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon-rain.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon-rain.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-moon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-rain.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-rain.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-rain.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-rain.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-heavy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-heavy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-heavy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-heavy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-showers-water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun-rain.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun-rain.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun-rain.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun-rain.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud-sun.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cloud.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clover.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clover.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clover.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/clover.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-branch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-branch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-branch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-branch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-commit.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-commit.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-commit.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-commit.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-compare.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-compare.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-compare.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-compare.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-fork.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-fork.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-fork.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-fork.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-merge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-merge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-merge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-merge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-pull-request.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-pull-request.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-pull-request.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code-pull-request.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/code.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/coins.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/coins.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/coins.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/coins.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/colon-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/colon-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/colon-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/colon-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dots.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dots.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dots.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-dots.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-sms.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-sms.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-sms.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment-sms.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comment.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/comments.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compact-disc.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compact-disc.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compact-disc.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compact-disc.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass-drafting.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass-drafting.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass-drafting.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass-drafting.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compress.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compress.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compress.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/compress.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer-mouse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer-mouse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer-mouse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer-mouse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/computer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie-bite.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie-bite.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie-bite.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie-bite.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cookie.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copyright.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copyright.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copyright.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/copyright.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/couch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/couch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/couch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/couch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/credit-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/credit-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/credit-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/credit-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cross.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cross.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cross.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cross.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crosshairs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crosshairs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crosshairs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crosshairs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crown.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crown.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crown.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crown.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crutch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crutch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crutch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/crutch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cruzeiro-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cruzeiro-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cruzeiro-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cruzeiro-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cube.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cube.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cube.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cube.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes-stacked.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes-stacked.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes-stacked.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes-stacked.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/cubes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/d.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/d.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/d.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/d.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/database.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/database.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/database.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/database.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/delete-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/delete-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/delete-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/delete-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/democrat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/democrat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/democrat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/democrat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/desktop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/desktop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/desktop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/desktop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dharmachakra.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dharmachakra.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dharmachakra.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dharmachakra.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-next.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-next.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-next.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-next.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-predecessor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-predecessor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-predecessor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-predecessor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-project.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-project.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-project.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-project.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-successor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-successor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-successor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diagram-successor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond-turn-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond-turn-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond-turn-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond-turn-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/diamond.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d20.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d20.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d20.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d20.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d6.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d6.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d6.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-d6.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-five.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-five.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-five.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-five.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-four.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-four.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-four.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-four.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-one.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-one.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-one.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-one.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-six.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-six.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-six.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-six.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-three.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-three.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-three.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-three.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-two.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-two.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-two.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice-two.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dice.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/disease.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/disease.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/disease.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/disease.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/display.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/display.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/display.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/display.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/divide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/divide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/divide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/divide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dna.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dna.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dna.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dna.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dollar-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dollar-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dollar-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dollar-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dolly.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dolly.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dolly.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dolly.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dong-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dong-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dong-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dong-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-closed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-closed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-closed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-closed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/door-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dove.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dove.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dove.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dove.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-left-and-up-right-to-center.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-left-and-up-right-to-center.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-left-and-up-right-to-center.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-left-and-up-right-to-center.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/down-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/download.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/download.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/download.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/download.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dragon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dragon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dragon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dragon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/draw-polygon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/draw-polygon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/draw-polygon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/draw-polygon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/droplet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum-steelpan.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum-steelpan.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum-steelpan.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum-steelpan.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drum.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drumstick-bite.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drumstick-bite.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drumstick-bite.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/drumstick-bite.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumbbell.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumbbell.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumbbell.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumbbell.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster-fire.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster-fire.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster-fire.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster-fire.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dumpster.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dungeon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dungeon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dungeon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/dungeon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/e.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/e.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/e.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/e.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-deaf.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-deaf.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-deaf.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-deaf.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-listen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-listen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-listen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ear-listen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-africa.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-africa.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-africa.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-africa.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-americas.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-americas.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-americas.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-americas.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-asia.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-asia.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-asia.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-asia.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-europe.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-europe.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-europe.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-europe.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-oceania.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-oceania.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-oceania.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/earth-oceania.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/egg.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/egg.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/egg.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/egg.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eject.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eject.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eject.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eject.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/elevator.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/elevator.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/elevator.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/elevator.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis-vertical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis-vertical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis-vertical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis-vertical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ellipsis.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open-text.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open-text.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open-text.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open-text.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelopes-bulk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelopes-bulk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelopes-bulk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/envelopes-bulk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/equals.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/equals.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/equals.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/equals.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eraser.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eraser.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eraser.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eraser.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ethernet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ethernet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ethernet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ethernet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/euro-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/euro-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/euro-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/euro-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/expand.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/expand.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/expand.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/expand.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/explosion.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/explosion.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/explosion.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/explosion.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-dropper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-dropper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-dropper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-dropper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-low-vision.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-low-vision.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-low-vision.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-low-vision.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/eye.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/f.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/f.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/f.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/f.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-angry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-angry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-angry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-angry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-dizzy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-dizzy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-dizzy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-dizzy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-flushed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-flushed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-flushed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-flushed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-frown.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grimace.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grimace.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grimace.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grimace.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam-sweat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam-sweat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam-sweat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam-sweat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-hearts.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-hearts.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-hearts.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-hearts.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint-tears.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint-tears.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint-tears.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint-tears.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-squint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-stars.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-stars.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-stars.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-stars.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tears.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tears.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tears.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tears.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-squint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-squint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-squint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-squint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-tongue.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-grin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-wink-heart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-wink-heart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-wink-heart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss-wink-heart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-kiss.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-squint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-squint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-squint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-squint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-laugh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh-blank.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh-blank.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh-blank.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh-blank.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-meh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-rolling-eyes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-rolling-eyes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-rolling-eyes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-rolling-eyes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-cry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-cry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-cry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-cry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-tear.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-tear.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-tear.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-sad-tear.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-beam.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-beam.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-beam.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-beam.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-wink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-wink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-wink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile-wink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-smile.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-surprise.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-surprise.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-surprise.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-surprise.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-tired.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-tired.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-tired.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/face-tired.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fan.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fan.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fan.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fan.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet-drip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet-drip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet-drip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet-drip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/faucet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fax.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fax.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fax.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fax.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather-pointed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather-pointed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather-pointed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather-pointed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/feather.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ferry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ferry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ferry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ferry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-audio.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-audio.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-audio.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-audio.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-question.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-question.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-question.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-question.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-code.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-code.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-code.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-code.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-contract.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-contract.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-contract.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-contract.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-csv.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-csv.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-csv.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-csv.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-excel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-excel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-excel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-excel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-export.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-export.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-export.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-export.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-image.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-image.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-image.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-image.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-import.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-import.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-import.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-import.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-invoice.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-lines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-lines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-lines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-lines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pdf.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pdf.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pdf.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pdf.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-pen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-powerpoint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-powerpoint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-powerpoint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-powerpoint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-prescription.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-prescription.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-prescription.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-prescription.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-shield.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-shield.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-shield.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-shield.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-signature.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-signature.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-signature.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-signature.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-video.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-video.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-video.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-video.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-waveform.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-waveform.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-waveform.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-waveform.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-word.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-word.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-word.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-word.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-zipper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-zipper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-zipper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file-zipper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/file.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill-drip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill-drip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill-drip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill-drip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fill.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/film.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/film.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/film.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/film.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/filter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fingerprint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fingerprint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fingerprint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fingerprint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-burner.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-burner.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-burner.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-burner.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-extinguisher.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-extinguisher.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-extinguisher.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-extinguisher.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-curved.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-curved.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-curved.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-curved.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire-flame-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fire.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish-fins.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish-fins.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish-fins.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish-fins.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/fish.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-checkered.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-checkered.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-checkered.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-checkered.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-usa.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-usa.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-usa.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag-usa.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask-vial.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask-vial.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask-vial.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask-vial.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/flask.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/floppy-disk.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/floppy-disk.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/floppy-disk.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/floppy-disk.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/florin-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/florin-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/florin-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/florin-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-closed.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-closed.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-closed.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-closed.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-tree.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-tree.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-tree.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder-tree.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/folder.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font-awesome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font-awesome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font-awesome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font-awesome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/font.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/football.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/football.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/football.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/football.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-fast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-fast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-fast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-fast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-step.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-step.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-step.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward-step.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/forward.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/franc-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/franc-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/franc-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/franc-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/frog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/frog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/frog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/frog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/futbol.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/futbol.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/futbol.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/futbol.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/g.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/g.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/g.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/g.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gamepad.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gamepad.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gamepad.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gamepad.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gas-pump.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gas-pump.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gas-pump.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gas-pump.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-high.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-high.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-high.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-high.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple-high.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple-high.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple-high.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple-high.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gauge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gavel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gavel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gavel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gavel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gear.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gear.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gear.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gear.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gears.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gears.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gears.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gears.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gem.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gem.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gem.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gem.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/genderless.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/genderless.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/genderless.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/genderless.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ghost.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ghost.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ghost.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ghost.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gift.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gift.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gift.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gift.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gifts.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gifts.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gifts.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gifts.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water-droplet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water-droplet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water-droplet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water-droplet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glass-water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glasses.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glasses.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glasses.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/glasses.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/globe.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/globe.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/globe.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/globe.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/golf-ball-tee.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/golf-ball-tee.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/golf-ball-tee.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/golf-ball-tee.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gopuram.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gopuram.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gopuram.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gopuram.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/graduation-cap.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/graduation-cap.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/graduation-cap.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/graduation-cap.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than-equal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than-equal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than-equal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than-equal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/greater-than.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines-vertical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines-vertical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines-vertical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines-vertical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-lines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-vertical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-vertical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-vertical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip-vertical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/grip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/group-arrows-rotate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/group-arrows-rotate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/group-arrows-rotate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/group-arrows-rotate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guarani-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guarani-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guarani-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guarani-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guitar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guitar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guitar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/guitar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gun.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gun.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gun.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/gun.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/h.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/h.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/h.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/h.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hammer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hammer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hammer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hammer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hamsa.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hamsa.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hamsa.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hamsa.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-back-fist.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-back-fist.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-back-fist.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-back-fist.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-dots.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-dots.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-dots.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-dots.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-fist.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-fist.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-fist.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-fist.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-droplet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-droplet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-droplet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-droplet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-hand.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-hand.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-hand.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-hand.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-heart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-heart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-heart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-heart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-holding.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-lizard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-lizard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-lizard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-lizard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-middle-finger.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-middle-finger.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-middle-finger.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-middle-finger.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-peace.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-peace.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-peace.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-peace.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-point-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-pointer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-pointer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-pointer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-pointer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-scissors.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-scissors.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-scissors.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-scissors.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-sparkles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-sparkles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-sparkles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-sparkles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-spock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-spock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-spock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand-spock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hand.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handcuffs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handcuffs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handcuffs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handcuffs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-asl-interpreting.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-asl-interpreting.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-asl-interpreting.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-asl-interpreting.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bound.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bound.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bound.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bound.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bubbles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bubbles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bubbles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-bubbles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-clapping.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-clapping.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-clapping.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-clapping.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-child.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-child.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-child.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-child.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-circle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-circle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-circle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding-circle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-holding.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-praying.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-praying.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-praying.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands-praying.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hands.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-angle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-angle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-angle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-angle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/handshake.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hanukiah.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hanukiah.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hanukiah.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hanukiah.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hard-drive.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hard-drive.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hard-drive.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hard-drive.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hashtag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hashtag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hashtag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hashtag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy-side.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy-side.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy-side.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy-side.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-cowboy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-wizard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-wizard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-wizard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hat-wizard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-cough.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-mask.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-mask.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-mask.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-mask.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-virus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-virus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-virus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/head-side-virus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heading.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heading.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heading.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heading.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headphones.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headset.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headset.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headset.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/headset.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-bolt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-bolt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-bolt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-bolt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-crack.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-crack.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-crack.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-crack.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-pulse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-pulse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-pulse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart-pulse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/heart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter-symbol.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter-symbol.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter-symbol.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter-symbol.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helicopter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-safety.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-safety.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-safety.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-safety.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-un.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-un.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-un.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/helmet-un.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/highlighter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/highlighter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/highlighter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/highlighter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-avalanche.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-avalanche.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-avalanche.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-avalanche.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-rockslide.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-rockslide.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-rockslide.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hill-rockslide.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hippo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hippo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hippo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hippo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hockey-puck.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hockey-puck.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hockey-puck.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hockey-puck.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/holly-berry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/holly-berry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/holly-berry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/holly-berry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse-head.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse-head.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse-head.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse-head.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/horse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hospital.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hot-tub-person.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hot-tub-person.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hot-tub-person.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hot-tub-person.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotdog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotdog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotdog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotdog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hotel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-end.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-end.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-end.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-end.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-half.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-half.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-half.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-half.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-start.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-start.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-start.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass-start.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hourglass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-crack.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-crack.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-crack.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-crack.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-window.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-window.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-window.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney-window.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-chimney.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-crack.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-crack.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-crack.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-crack.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-fire.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-fire.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-fire.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-fire.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water-circle-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water-circle-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water-circle-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water-circle-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-flood-water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-laptop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-laptop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-laptop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-laptop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical-flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-signal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-signal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-signal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-signal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-tsunami.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-tsunami.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-tsunami.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-tsunami.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house-user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/house.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hryvnia-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hryvnia-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hryvnia-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hryvnia-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hurricane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hurricane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hurricane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/hurricane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i-cursor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i-cursor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i-cursor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i-cursor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/i.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ice-cream.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ice-cream.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ice-cream.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ice-cream.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icicles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icicles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icicles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icicles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icons.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icons.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icons.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/icons.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-badge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-badge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-badge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-badge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card-clip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card-clip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card-clip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card-clip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/id-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/igloo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/igloo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/igloo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/igloo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image-portrait.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image-portrait.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image-portrait.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image-portrait.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/image.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/images.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/images.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/images.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/images.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/inbox.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/inbox.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/inbox.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/inbox.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indent.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indent.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indent.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indent.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indian-rupee-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indian-rupee-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indian-rupee-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/indian-rupee-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/industry.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/industry.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/industry.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/industry.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/infinity.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/infinity.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/infinity.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/infinity.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/info.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/info.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/info.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/info.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/italic.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/italic.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/italic.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/italic.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/j.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/j.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/j.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/j.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar-wheat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar-wheat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar-wheat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar-wheat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jedi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jedi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jedi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jedi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jet-fighter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/joint.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/joint.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/joint.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/joint.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jug-detergent.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jug-detergent.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jug-detergent.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/jug-detergent.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/k.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/k.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/k.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/k.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kaaba.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kaaba.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kaaba.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kaaba.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/key.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/key.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/key.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/key.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/keyboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/keyboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/keyboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/keyboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/khanda.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/khanda.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/khanda.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/khanda.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kip-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kip-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kip-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kip-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kit-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kit-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kit-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kit-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kitchen-set.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kitchen-set.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kitchen-set.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kitchen-set.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kiwi-bird.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kiwi-bird.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kiwi-bird.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/kiwi-bird.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/l.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/l.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/l.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/l.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/land-mine-on.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/land-mine-on.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/land-mine-on.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/land-mine-on.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-dome.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-dome.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-dome.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-dome.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark-flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/landmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/language.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/language.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/language.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/language.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-code.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-code.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-code.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-code.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-file.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-file.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-file.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-file.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/laptop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lari-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lari-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lari-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lari-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/layer-group.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/layer-group.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/layer-group.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/layer-group.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/leaf.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/leaf.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/leaf.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/leaf.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/left-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lemon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lemon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lemon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lemon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than-equal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than-equal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than-equal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than-equal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/less-than.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/life-ring.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/life-ring.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/life-ring.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/life-ring.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lightbulb.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lightbulb.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lightbulb.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lightbulb.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lines-leaning.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lines-leaning.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lines-leaning.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lines-leaning.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/link.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lira-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lira-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lira-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lira-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ol.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ol.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ol.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ol.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ul.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ul.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ul.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list-ul.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/list.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/litecoin-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/litecoin-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/litecoin-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/litecoin-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-arrow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-arrow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-arrow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-arrow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-crosshairs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-crosshairs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-crosshairs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-crosshairs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-dot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-dot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-dot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-dot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/location-pin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/locust.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/locust.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/locust.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/locust.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs-virus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs-virus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs-virus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs-virus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/lungs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/m.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/m.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/m.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/m.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-chart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-chart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-chart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-chart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-location.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-location.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-location.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-location.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/magnifying-glass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/manat-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/manat-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/manat-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/manat-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location-dot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location-dot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location-dot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location-dot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-location.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-pin.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-pin.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-pin.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map-pin.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/map.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/marker.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/marker.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/marker.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/marker.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus-burst.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus-burst.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus-burst.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus-burst.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-and-venus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-double.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-double.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-double.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-double.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars-stroke.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mars.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-citrus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-citrus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-citrus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-citrus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-empty.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-empty.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-empty.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass-empty.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/martini-glass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-face.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-face.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-face.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-face.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-ventilator.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-ventilator.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-ventilator.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask-ventilator.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mask.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/masks-theater.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/masks-theater.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/masks-theater.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/masks-theater.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mattress-pillow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mattress-pillow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mattress-pillow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mattress-pillow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/maximize.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/maximize.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/maximize.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/maximize.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/medal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/medal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/medal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/medal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/memory.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/memory.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/memory.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/memory.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/menorah.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/menorah.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/menorah.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/menorah.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mercury.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mercury.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mercury.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mercury.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/message.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/message.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/message.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/message.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/meteor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/meteor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/meteor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/meteor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microchip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microchip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microchip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microchip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-lines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microphone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microscope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microscope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microscope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/microscope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mill-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mill-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mill-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mill-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minimize.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minimize.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minimize.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minimize.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mitten.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mitten.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mitten.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mitten.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-button.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-button.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-button.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-button.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-retro.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-retro.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-retro.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-retro.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen-button.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen-button.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen-button.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen-button.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile-screen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mobile.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1-wave.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1-wave.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1-wave.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1-wave.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-1.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-transfer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-transfer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-transfer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-transfer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-trend-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-trend-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-trend-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-trend-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wave.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wave.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wave.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wave.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wheat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wheat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wheat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill-wheat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bill.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bills.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bills.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bills.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-bills.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/money-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/monument.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/monument.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/monument.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/monument.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/moon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/moon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/moon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/moon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mortar-pestle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mortar-pestle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mortar-pestle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mortar-pestle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosque.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosque.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosque.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosque.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito-net.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito-net.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito-net.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito-net.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mosquito.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/motorcycle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/motorcycle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/motorcycle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/motorcycle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mound.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mound.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mound.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mound.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-city.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-city.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-city.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-city.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-sun.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-sun.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-sun.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain-sun.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mountain.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-hot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-hot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-hot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-hot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-saucer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-saucer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-saucer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/mug-saucer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/music.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/music.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/music.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/music.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/n.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/n.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/n.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/n.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/naira-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/naira-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/naira-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/naira-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/network-wired.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/network-wired.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/network-wired.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/network-wired.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/neuter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/neuter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/neuter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/neuter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/newspaper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/newspaper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/newspaper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/newspaper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/not-equal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/not-equal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/not-equal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/not-equal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/note-sticky.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/note-sticky.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/note-sticky.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/note-sticky.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/notes-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/notes-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/notes-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/notes-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/o.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/o.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/o.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/o.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-group.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-group.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-group.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-group.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-ungroup.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-ungroup.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-ungroup.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/object-ungroup.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-can.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-can.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-can.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-can.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-well.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-well.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-well.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/oil-well.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/om.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/om.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/om.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/om.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/otter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/otter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/otter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/otter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/outdent.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/outdent.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/outdent.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/outdent.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/p.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/p.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/p.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/p.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pager.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pager.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pager.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pager.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paint-roller.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paint-roller.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paint-roller.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paint-roller.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paintbrush.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paintbrush.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paintbrush.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paintbrush.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/palette.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/palette.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/palette.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/palette.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pallet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pallet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pallet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pallet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/panorama.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/panorama.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/panorama.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/panorama.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paper-plane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paper-plane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paper-plane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paper-plane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paperclip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paperclip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paperclip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paperclip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/parachute-box.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/parachute-box.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/parachute-box.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/parachute-box.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paragraph.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paragraph.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paragraph.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paragraph.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/passport.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/passport.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/passport.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/passport.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paste.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paste.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paste.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paste.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pause.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pause.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pause.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pause.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paw.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paw.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paw.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/paw.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peace.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peace.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peace.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peace.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-clip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-clip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-clip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-clip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-fancy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-fancy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-fancy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-fancy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-nib.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-nib.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-nib.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-nib.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-ruler.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-ruler.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-ruler.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-ruler.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-to-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-to-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-to-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen-to-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pencil.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pencil.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pencil.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pencil.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-arrows.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-arrows.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-arrows.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-arrows.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-carry-box.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-carry-box.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-carry-box.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-carry-box.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-group.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-group.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-group.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-group.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-pulling.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-pulling.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-pulling.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-pulling.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-robbery.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-robbery.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-robbery.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-robbery.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-roof.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-roof.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-roof.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/people-roof.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pepper-hot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pepper-hot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pepper-hot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pepper-hot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/percent.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/percent.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/percent.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/percent.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-down-to-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-down-to-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-down-to-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-down-to-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-up-from-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-up-from-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-up-from-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-arrow-up-from-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-biking.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-biking.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-biking.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-biking.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-booth.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-booth.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-booth.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-booth.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-breastfeeding.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-breastfeeding.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-breastfeeding.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-breastfeeding.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-burst.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-burst.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-burst.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-burst.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-cane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-cane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-cane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-cane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-chalkboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-chalkboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-chalkboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-chalkboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-question.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-question.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-question.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-question.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-digging.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-digging.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-digging.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-digging.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dots-from-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dots-from-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dots-from-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dots-from-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress-burst.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress-burst.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress-burst.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress-burst.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-dress.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-drowning.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-drowning.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-drowning.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-drowning.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling-burst.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling-burst.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling-burst.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling-burst.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-falling.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-half-dress.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-half-dress.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-half-dress.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-half-dress.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-harassing.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-harassing.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-harassing.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-harassing.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-hiking.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-hiking.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-hiking.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-hiking.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-pointing.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-pointing.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-pointing.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-pointing.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-rifle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-rifle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-rifle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-rifle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-to-person.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-to-person.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-to-person.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-military-to-person.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-praying.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-praying.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-praying.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-praying.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-pregnant.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-pregnant.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-pregnant.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-pregnant.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rays.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rays.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rays.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rays.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rifle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rifle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rifle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-rifle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-running.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-running.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-running.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-running.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-shelter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-shelter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-shelter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-shelter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skating.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skating.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skating.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skating.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing-nordic.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing-nordic.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing-nordic.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing-nordic.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-skiing.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-snowboarding.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-snowboarding.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-snowboarding.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-snowboarding.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-swimming.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-swimming.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-swimming.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-swimming.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-through-window.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-through-window.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-through-window.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-through-window.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-loop-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-loop-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-loop-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-loop-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-dashed-line-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-dashed-line-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-dashed-line-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-dashed-line-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-luggage.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-luggage.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-luggage.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-luggage.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-with-cane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-with-cane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-with-cane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking-with-cane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person-walking.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/person.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peseta-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peseta-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peseta-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peseta-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peso-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peso-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peso-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/peso-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-flip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-flip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-flip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-flip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-volume.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-volume.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-volume.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone-volume.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/phone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/photo-film.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/photo-film.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/photo-film.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/photo-film.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/piggy-bank.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/piggy-bank.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/piggy-bank.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/piggy-bank.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pills.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pills.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pills.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pills.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pizza-slice.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pizza-slice.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pizza-slice.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pizza-slice.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/place-of-worship.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/place-of-worship.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/place-of-worship.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/place-of-worship.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-arrival.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-arrival.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-arrival.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-arrival.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-departure.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-departure.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-departure.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-departure.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plant-wilt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plant-wilt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plant-wilt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plant-wilt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plate-wheat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plate-wheat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plate-wheat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plate-wheat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/play.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/play.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/play.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/play.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-bolt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-bolt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-bolt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-bolt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plug.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/podcast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/podcast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/podcast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/podcast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo-storm.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo-storm.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo-storm.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo-storm.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poo.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/poop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/power-off.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/power-off.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/power-off.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/power-off.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription-bottle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/prescription.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/print.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/print.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/print.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/print.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-soap.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-soap.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-soap.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/pump-soap.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/puzzle-piece.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/puzzle-piece.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/puzzle-piece.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/puzzle-piece.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/q.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/q.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/q.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/q.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/qrcode.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/qrcode.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/qrcode.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/qrcode.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/question.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/question.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/question.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/question.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/quote-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/r.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/r.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/r.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/r.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radiation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radiation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radiation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radiation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radio.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radio.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radio.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/radio.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rainbow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rainbow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rainbow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rainbow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ranking-star.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ranking-star.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ranking-star.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ranking-star.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/receipt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/receipt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/receipt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/receipt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/record-vinyl.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/record-vinyl.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/record-vinyl.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/record-vinyl.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-ad.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-ad.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-ad.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-ad.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-list.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-list.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-list.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-list.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rectangle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/recycle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/recycle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/recycle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/recycle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/registered.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/registered.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/registered.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/registered.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/repeat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/repeat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/repeat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/repeat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply-all.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply-all.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply-all.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply-all.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/reply.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/republican.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/republican.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/republican.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/republican.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/restroom.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/restroom.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/restroom.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/restroom.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/retweet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/retweet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/retweet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/retweet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ribbon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ribbon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ribbon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ribbon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-from-bracket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-from-bracket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-from-bracket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-from-bracket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-to-bracket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-to-bracket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-to-bracket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/right-to-bracket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ring.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ring.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ring.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ring.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-barrier.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-barrier.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-barrier.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-barrier.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-bridge.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-bridge.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-bridge.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-bridge.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-spikes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-spikes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-spikes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road-spikes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/road.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/robot.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/robot.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/robot.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/robot.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rocket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rocket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rocket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rocket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rotate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/route.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/route.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/route.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/route.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rss.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rss.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rss.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rss.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruble-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruble-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruble-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruble-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rug.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rug.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rug.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rug.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-combined.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-combined.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-combined.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-combined.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-horizontal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-horizontal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-horizontal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-horizontal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-vertical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-vertical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-vertical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler-vertical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ruler.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupee-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupee-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupee-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupee-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupiah-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupiah-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupiah-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/rupiah-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/s.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/s.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/s.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/s.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-dollar.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-dollar.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-dollar.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-dollar.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sack-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sailboat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sailboat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sailboat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sailboat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite-dish.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite-dish.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite-dish.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite-dish.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/satellite.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-balanced.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-balanced.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-balanced.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-balanced.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced-flip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced-flip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced-flip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced-flip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scale-unbalanced.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-circle-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-flag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-flag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-flag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-flag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/school.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scissors.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scissors.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scissors.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scissors.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver-wrench.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver-wrench.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver-wrench.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver-wrench.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/screwdriver.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll-torah.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll-torah.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll-torah.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll-torah.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/scroll.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sd-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sd-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sd-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sd-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/section.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/section.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/section.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/section.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/seedling.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/seedling.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/seedling.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/seedling.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/server.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/server.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/server.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/server.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shapes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shapes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shapes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shapes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-from-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-from-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-from-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-from-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-nodes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-nodes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-nodes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share-nodes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/share.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sheet-plastic.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sheet-plastic.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sheet-plastic.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sheet-plastic.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shekel-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shekel-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shekel-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shekel-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-cat.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-cat.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-cat.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-cat.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-dog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-dog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-dog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-dog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-halved.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-halved.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-halved.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-halved.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-heart.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-heart.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-heart.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-heart.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-virus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-virus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-virus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield-virus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shield.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ship.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ship.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ship.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ship.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shirt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shirt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shirt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shirt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shoe-prints.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shoe-prints.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shoe-prints.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shoe-prints.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shower.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shower.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shower.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shower.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shrimp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shrimp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shrimp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shrimp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuffle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuffle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuffle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuffle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuttle-space.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuttle-space.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuttle-space.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/shuttle-space.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sign-hanging.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sign-hanging.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sign-hanging.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sign-hanging.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signature.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signature.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signature.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signature.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signs-post.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signs-post.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signs-post.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/signs-post.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sim-card.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sim-card.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sim-card.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sim-card.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sink.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sink.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sink.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sink.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sitemap.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sitemap.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sitemap.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sitemap.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull-crossbones.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull-crossbones.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull-crossbones.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull-crossbones.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/skull.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sleigh.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sleigh.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sleigh.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sleigh.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sliders.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sliders.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sliders.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sliders.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smog.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smog.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smog.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smog.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smoking.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smoking.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smoking.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/smoking.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowflake.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowflake.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowflake.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowflake.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowman.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowman.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowman.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowman.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowplow.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowplow.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowplow.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/snowplow.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/soap.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/soap.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/soap.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/soap.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/socks.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/socks.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/socks.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/socks.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/solar-panel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/solar-panel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/solar-panel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/solar-panel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sort.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spa.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spa.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spa.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spa.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spaghetti-monster-flying.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spaghetti-monster-flying.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spaghetti-monster-flying.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spaghetti-monster-flying.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spell-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spell-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spell-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spell-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spider.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spider.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spider.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spider.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spinner.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spinner.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spinner.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spinner.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/splotch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/splotch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/splotch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/splotch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spoon.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spoon.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spoon.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spoon.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can-sparkles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can-sparkles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can-sparkles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can-sparkles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/spray-can.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-arrow-up-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-arrow-up-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-arrow-up-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-arrow-up-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-caret-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-envelope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-envelope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-envelope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-envelope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-full.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-full.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-full.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-full.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-h.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-h.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-h.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-h.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-nfi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-nfi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-nfi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-nfi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-parking.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-parking.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-parking.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-parking.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-pen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-pen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-pen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-pen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-person-confined.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-person-confined.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-person-confined.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-person-confined.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone-flip.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone-flip.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone-flip.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone-flip.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-phone.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-horizontal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-horizontal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-horizontal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-horizontal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-vertical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-vertical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-vertical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-poll-vertical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-root-variable.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-root-variable.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-root-variable.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-root-variable.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-rss.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-rss.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-rss.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-rss.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-share-nodes.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-share-nodes.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-share-nodes.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-share-nodes.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-up-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-up-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-up-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-up-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-virus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-virus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-virus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-virus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/staff-snake.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/staff-snake.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/staff-snake.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/staff-snake.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stairs.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stairs.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stairs.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stairs.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stamp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stamp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stamp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stamp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stapler.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stapler.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stapler.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stapler.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-and-crescent.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-and-crescent.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-and-crescent.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-and-crescent.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half-stroke.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half-stroke.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half-stroke.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half-stroke.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-half.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-david.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-david.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-david.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-david.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-life.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-life.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-life.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star-of-life.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/star.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sterling-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sterling-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sterling-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sterling-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stethoscope.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stethoscope.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stethoscope.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stethoscope.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stop.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stop.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stop.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stop.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch-20.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch-20.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch-20.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch-20.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stopwatch.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/store.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/street-view.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/street-view.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/street-view.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/street-view.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/strikethrough.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/strikethrough.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/strikethrough.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/strikethrough.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stroopwafel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stroopwafel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stroopwafel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/stroopwafel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/subscript.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/subscript.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/subscript.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/subscript.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-rolling.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-rolling.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-rolling.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase-rolling.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/suitcase.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun-plant-wilt.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun-plant-wilt.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun-plant-wilt.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun-plant-wilt.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/sun.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/superscript.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/superscript.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/superscript.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/superscript.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/swatchbook.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/swatchbook.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/swatchbook.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/swatchbook.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/synagogue.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/synagogue.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/synagogue.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/synagogue.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/syringe.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/syringe.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/syringe.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/syringe.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/t.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/t.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/t.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/t.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells-large.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells-large.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells-large.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells-large.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-cells.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-columns.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-columns.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-columns.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-columns.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-list.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-list.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-list.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-list.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-tennis-paddle-ball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-tennis-paddle-ball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-tennis-paddle-ball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table-tennis-paddle-ball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/table.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-button.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-button.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-button.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-button.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-screen-button.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-screen-button.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-screen-button.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet-screen-button.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablets.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablets.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablets.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tablets.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tachograph-digital.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tachograph-digital.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tachograph-digital.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tachograph-digital.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tags.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tags.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tags.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tags.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tape.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tape.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tape.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tape.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp-droplet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp-droplet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp-droplet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp-droplet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tarp.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/taxi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/taxi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/taxi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/taxi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth-open.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth-open.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth-open.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth-open.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/teeth.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-empty.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-empty.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-empty.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-empty.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-full.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-full.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-full.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-full.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-half.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-half.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-half.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-half.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-high.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-high.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-high.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-high.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-low.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-low.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-low.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-low.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-quarter.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-quarter.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-quarter.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-quarter.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-three-quarters.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-three-quarters.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-three-quarters.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/temperature-three-quarters.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tenge-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tenge-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tenge-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tenge-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-down-to-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-down-to-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-down-to-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-down-to-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-left-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-left-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-left-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-left-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-turn-left.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-turn-left.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-turn-left.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrow-turn-left.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrows-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrows-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrows-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent-arrows-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tent.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tents.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tents.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tents.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tents.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/terminal.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/terminal.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/terminal.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/terminal.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-height.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-height.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-height.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-height.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-width.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-width.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-width.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/text-width.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thermometer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thermometer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thermometer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thermometer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbs-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbtack.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbtack.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbtack.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/thumbtack.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket-simple.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket-simple.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket-simple.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket-simple.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/ticket.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/timeline.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/timeline.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/timeline.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/timeline.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-off.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-off.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-off.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-off.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-on.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-on.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-on.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toggle-on.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-paper.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-portable.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-portable.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-portable.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet-portable.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilets-portable.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilets-portable.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilets-portable.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toilets-portable.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toolbox.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toolbox.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toolbox.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/toolbox.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tooth.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tooth.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tooth.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tooth.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/torii-gate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/torii-gate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/torii-gate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/torii-gate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tornado.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tornado.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tornado.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tornado.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-broadcast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-broadcast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-broadcast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-broadcast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-cell.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-cell.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-cell.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-cell.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-observation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-observation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-observation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tower-observation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tractor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tractor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tractor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tractor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trademark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trademark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trademark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trademark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/traffic-light.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/traffic-light.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/traffic-light.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/traffic-light.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trailer.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trailer.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trailer.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trailer.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-subway.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-subway.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-subway.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-subway.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-tram.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-tram.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-tram.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train-tram.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/train.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/transgender.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/transgender.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/transgender.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/transgender.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can-arrow-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can-arrow-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can-arrow-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can-arrow-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash-can.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree-city.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree-city.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree-city.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree-city.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tree.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/triangle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/triangle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/triangle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/triangle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trophy.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trophy.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trophy.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trophy.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel-bricks.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel-bricks.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel-bricks.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel-bricks.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/trowel.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-arrow-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-arrow-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-arrow-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-arrow-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-droplet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-droplet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-droplet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-droplet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-fast.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-fast.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-fast.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-fast.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field-un.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field-un.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field-un.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field-un.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-field.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-front.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-front.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-front.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-front.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-medical.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-medical.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-medical.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-medical.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-monster.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-monster.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-monster.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-monster.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-moving.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-moving.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-moving.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-moving.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-pickup.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-pickup.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-pickup.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-pickup.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-plane.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-plane.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-plane.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-plane.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-ramp-box.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-ramp-box.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-ramp-box.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck-ramp-box.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/truck.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tty.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tty.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tty.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tty.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turkish-lira-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turkish-lira-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turkish-lira-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turkish-lira-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-up.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-up.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-up.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/turn-up.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tv.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tv.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tv.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/tv.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/u.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/u.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/u.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/u.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella-beach.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella-beach.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella-beach.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella-beach.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/umbrella.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/underline.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/underline.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/underline.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/underline.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/universal-access.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/universal-access.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/universal-access.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/universal-access.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock-keyhole.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock-keyhole.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock-keyhole.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock-keyhole.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/unlock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down-left-right.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down-left-right.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down-left-right.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down-left-right.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-down.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-long.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-long.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-long.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-long.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-and-down-left-from-center.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-and-down-left-from-center.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-and-down-left-from-center.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-and-down-left-from-center.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-from-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-from-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-from-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/up-right-from-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/upload.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/upload.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/upload.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/upload.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-astronaut.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-astronaut.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-astronaut.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-astronaut.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-clock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-clock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-clock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-clock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-doctor.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-doctor.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-doctor.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-doctor.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-gear.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-gear.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-gear.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-gear.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-graduate.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-graduate.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-graduate.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-graduate.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-group.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-group.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-group.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-group.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-injured.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-injured.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-injured.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-injured.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-large.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-lock.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-lock.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-lock.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-lock.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-minus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-minus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-minus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-minus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-ninja.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-ninja.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-ninja.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-ninja.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-nurse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-nurse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-nurse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-nurse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-pen.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-pen.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-pen.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-pen.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-plus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-plus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-plus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-plus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-secret.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-secret.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-secret.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-secret.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-shield.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-shield.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-shield.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-shield.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tag.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tag.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tag.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tag.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tie.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tie.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tie.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-tie.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/user.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-between-lines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-between-lines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-between-lines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-between-lines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-gear.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-gear.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-gear.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-gear.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-line.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-line.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-line.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-line.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rays.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rays.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rays.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rays.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rectangle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rectangle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rectangle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-rectangle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-viewfinder.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-viewfinder.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-viewfinder.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users-viewfinder.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/users.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/utensils.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/utensils.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/utensils.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/utensils.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/v.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/v.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/v.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/v.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/van-shuttle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/van-shuttle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/van-shuttle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/van-shuttle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vault.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vault.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vault.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vault.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vector-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vector-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vector-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vector-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-double.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-double.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-double.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-double.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-mars.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-mars.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-mars.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus-mars.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/venus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest-patches.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest-patches.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest-patches.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest-patches.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vest.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-circle-check.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-circle-check.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-circle-check.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-circle-check.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-virus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-virus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-virus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial-virus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vial.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vials.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vials.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vials.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vials.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/video.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vihara.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vihara.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vihara.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vihara.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-covid.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-slash.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-slash.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-slash.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus-slash.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/virus.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/viruses.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/viruses.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/viruses.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/viruses.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/voicemail.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/voicemail.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/voicemail.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/voicemail.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volcano.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volcano.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volcano.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volcano.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volleyball.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volleyball.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volleyball.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volleyball.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-high.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-high.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-high.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-high.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-low.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-low.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-low.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-low.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-off.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-off.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-off.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-off.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/volume-xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vr-cardboard.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vr-cardboard.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vr-cardboard.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/vr-cardboard.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/w.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/w.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/w.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/w.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/walkie-talkie.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/walkie-talkie.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/walkie-talkie.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/walkie-talkie.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wallet.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wallet.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wallet.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wallet.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic-sparkles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic-sparkles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic-sparkles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic-sparkles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-magic.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-sparkles.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-sparkles.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-sparkles.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wand-sparkles.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/warehouse.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/warehouse.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/warehouse.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/warehouse.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water-ladder.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water-ladder.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water-ladder.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water-ladder.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/water.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wave-square.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wave-square.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wave-square.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wave-square.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-hanging.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-hanging.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-hanging.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-hanging.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-scale.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-scale.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-scale.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/weight-scale.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn-circle-exclamation.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn-circle-exclamation.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn-circle-exclamation.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn-circle-exclamation.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheat-awn.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair-move.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair-move.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair-move.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair-move.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wheelchair.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/whiskey-glass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/whiskey-glass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/whiskey-glass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/whiskey-glass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wifi.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wifi.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wifi.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wifi.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wind.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wind.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wind.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wind.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-maximize.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-maximize.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-maximize.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-maximize.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-minimize.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-minimize.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-minimize.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-minimize.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-restore.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-restore.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-restore.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/window-restore.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-bottle.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-bottle.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-bottle.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-bottle.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass-empty.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass-empty.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass-empty.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass-empty.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wine-glass.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/won-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/won-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/won-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/won-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/worm.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/worm.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/worm.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/worm.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wrench.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wrench.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wrench.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/wrench.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x-ray.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x-ray.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x-ray.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x-ray.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/x.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmark.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmark.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmark.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmark.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmarks-lines.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmarks-lines.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmarks-lines.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/xmarks-lines.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/y.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/y.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/y.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/y.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yen-sign.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yen-sign.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yen-sign.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yen-sign.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yin-yang.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yin-yang.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yin-yang.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/yin-yang.svg diff --git a/crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/z.svg b/crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/z.svg similarity index 100% rename from crates/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/z.svg rename to crates/lib/font-awesome-as-a-crate/fontawesome-free-6.2.0-desktop/svgs/solid/z.svg diff --git a/crates/font-awesome-as-a-crate/released.sh b/crates/lib/font-awesome-as-a-crate/released.sh similarity index 100% rename from crates/font-awesome-as-a-crate/released.sh rename to crates/lib/font-awesome-as-a-crate/released.sh diff --git a/crates/font-awesome-as-a-crate/src/lib.rs b/crates/lib/font-awesome-as-a-crate/src/lib.rs similarity index 100% rename from crates/font-awesome-as-a-crate/src/lib.rs rename to crates/lib/font-awesome-as-a-crate/src/lib.rs diff --git a/crates/metadata/Cargo.toml b/crates/lib/metadata/Cargo.toml similarity index 100% rename from crates/metadata/Cargo.toml rename to crates/lib/metadata/Cargo.toml diff --git a/crates/metadata/build.rs b/crates/lib/metadata/build.rs similarity index 100% rename from crates/metadata/build.rs rename to crates/lib/metadata/build.rs diff --git a/crates/metadata/lib.rs b/crates/lib/metadata/lib.rs similarity index 100% rename from crates/metadata/lib.rs rename to crates/lib/metadata/lib.rs From 72d83a62ee60ccefd9a7d251e53119a004eea989 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 11:51:21 +0100 Subject: [PATCH 26/46] kk --- Cargo.lock | 1 - Cargo.toml | 26 ++++----- crates/bin/docs_rs_builder/Cargo.toml | 43 ++++++++------- crates/bin/docs_rs_watcher/Cargo.toml | 22 ++++---- crates/bin/docs_rs_web/Cargo.toml | 75 +++++++++++++------------- crates/lib/docs_rs_headers/src/etag.rs | 6 +++ crates/lib/docs_rs_logging/src/lib.rs | 1 - 7 files changed, 88 insertions(+), 86 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2fc8b820..4cfdd8b38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1984,7 +1984,6 @@ dependencies = [ "docs_rs_repository_stats", "docs_rs_storage", "docs_rs_utils", - "docs_rs_watcher", "docsrs-metadata", "futures-util", "hostname", diff --git a/Cargo.toml b/Cargo.toml index 0fd6137c0..0297a76e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,19 +58,19 @@ bincode = { workspace = true } chrono = { workspace = true } clap = { workspace = true } derive_more = { workspace = true } -docs_rs_build_queue = { path = "crates/docs_rs_build_queue" } -docs_rs_cargo_metadata = { path = "crates/docs_rs_cargo_metadata" } -docs_rs_database = { path = "crates/docs_rs_database" } -docs_rs_env_vars = { path = "crates/docs_rs_env_vars" } -docs_rs_headers = { path = "crates/docs_rs_headers" } -docs_rs_logging = { path = "crates/docs_rs_logging" } -docs_rs_opentelemetry = { path = "crates/docs_rs_opentelemetry" } -docs_rs_registry_api = { path = "crates/docs_rs_registry_api" } -docs_rs_storage = { path = "crates/docs_rs_storage" } -docs_rs_utils = { path = "crates/docs_rs_utils" } -docs_rs_watcher = { path = "crates/docs_rs_watcher" } -docs_rs_web_utils = { path = "crates/docs_rs_web_utils" } -docsrs-metadata = { path = "crates/metadata" } +docs_rs_build_queue = { path = "crates/lib/docs_rs_build_queue" } +docs_rs_cargo_metadata = { path = "crates/lib/docs_rs_cargo_metadata" } +docs_rs_database = { path = "crates/lib/docs_rs_database" } +docs_rs_env_vars = { path = "crates/lib/docs_rs_env_vars" } +docs_rs_headers = { path = "crates/lib/docs_rs_headers" } +docs_rs_logging = { path = "crates/lib/docs_rs_logging" } +docs_rs_opentelemetry = { path = "crates/lib/docs_rs_opentelemetry" } +docs_rs_registry_api = { path = "crates/lib/docs_rs_registry_api" } +docs_rs_storage = { path = "crates/lib/docs_rs_storage" } +docs_rs_utils = { path = "crates/lib/docs_rs_utils" } +docs_rs_watcher = { path = "crates/bin/docs_rs_watcher" } +docs_rs_web_utils = { path = "crates/lib/docs_rs_web_utils" } +docsrs-metadata = { path = "crates/lib/metadata" } fn-error-context = "0.2.0" futures-util = { workspace = true } getrandom = "0.3.1" diff --git a/crates/bin/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml index 28e872c15..1d45c707f 100644 --- a/crates/bin/docs_rs_builder/Cargo.toml +++ b/crates/bin/docs_rs_builder/Cargo.toml @@ -4,34 +4,33 @@ version = "0.1.0" edition = "2024" [dependencies] -serde = { workspace = true } -docs_rs_database = { path = "../docs_rs_database" } -docs_rs_env_vars = { path = "../docs_rs_env_vars" } -docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } -docs_rs_storage = { path = "../docs_rs_storage" } -docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } -docs_rs_watcher = { path = "../docs_rs_watcher" } -docs_rs_registry_api = { path = "../docs_rs_registry_api" } -docs_rs_build_utils = { path = "../docs_rs_build_utils" } -docs_rs_context = { path = "../docs_rs_context" } -docs_rs_repository_stats = { path = "../docs_rs_repository_stats" } -url = { workspace = true } anyhow = { workspace = true } -serde_json = { workspace = true } -slug = { workspace = true } +docs_rs_build_utils = { path = "../../lib/docs_rs_build_utils" } +docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } +docs_rs_context = { path = "../../lib/docs_rs_context" } +docs_rs_database = { path = "../../lib/docs_rs_database" } +docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } +docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry" } +docs_rs_registry_api = { path = "../../lib/docs_rs_registry_api" } +docs_rs_repository_stats = { path = "../../lib/docs_rs_repository_stats" } +docs_rs_storage = { path = "../../lib/docs_rs_storage" } +docs_rs_utils = { path = "../../lib/docs_rs_utils" } +docsrs-metadata = { path = "../../lib/metadata" } futures-util = { workspace = true } -docs_rs_utils = { path = "../docs_rs_utils" } -docsrs-metadata = { path = "../metadata" } -opentelemetry = { workspace = true } +hostname = "0.4.0" itertools = { workspace = true } +log = "0.4" +opentelemetry = { workspace = true } regex = { workspace = true } rustwide = { version = "0.20.0", features = ["unstable-toolchain-ci", "unstable"] } -tracing = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +slug = { workspace = true } sqlx = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } sysinfo = { version = "0.37.2", default-features = false, features = ["system"] } -log = "0.4" tempfile = { workspace = true } +thiserror = { workspace = true } +tokio = { workspace = true } toml = { workspace = true } -hostname = "0.4.0" +tracing = { workspace = true } +url = { workspace = true } diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index 58ade204e..0cff8516c 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -9,17 +9,17 @@ chrono = { workspace = true } clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} -docs_rs_fastly = { path = "../docs_rs_fastly" } -docs_rs_build_queue = { path = "../docs_rs_build_queue" } -docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } -docs_rs_context = { path = "../docs_rs_context" } -docs_rs_database = { path = "../docs_rs_database" } -docs_rs_env_vars = { path = "../docs_rs_env_vars" } -docs_rs_logging = { path = "../docs_rs_logging" } -docs_rs_repository_stats = { path = "../docs_rs_repository_stats" } -docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } -docs_rs_storage = { path = "../docs_rs_storage" } -docs_rs_utils = { path = "../docs_rs_utils" } +docs_rs_fastly = { path = "../../lib/docs_rs_fastly" } +docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } +docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } +docs_rs_context = { path = "../../lib/docs_rs_context" } +docs_rs_database = { path = "../../lib/docs_rs_database" } +docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } +docs_rs_logging = { path = "../../lib/docs_rs_logging" } +docs_rs_repository_stats = { path = "../../lib/docs_rs_repository_stats" } +docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry" } +docs_rs_storage = { path = "../../lib/docs_rs_storage" } +docs_rs_utils = { path = "../../lib/docs_rs_utils" } futures-util = { workspace = true } itertools = { workspace = true } opentelemetry = { workspace = true } diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index abafe2813..a3e158bdf 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -7,56 +7,55 @@ build = "build.rs" [dependencies] anyhow = { workspace = true } -docs_rs_database = { path = "../docs_rs_database" } -docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } -docs_rs_utils = { path = "../docs_rs_utils" } -docs_rs_build_queue = { path = "../docs_rs_build_queue" } -docs_rs_storage = { path = "../docs_rs_storage" } -docs_rs_env_vars = { path = "../docs_rs_env_vars" } -docs_rs_headers = { path = "../docs_rs_headers" } -docs_rs_cargo_metadata = { path = "../docs_rs_cargo_metadata" } -docs_rs_registry_api = { path = "../docs_rs_registry_api" } -docs_rs_web_utils = { path = "../docs_rs_web_utils" } -semver = { workspace = true } -sentry = { workspace = true } -serde_with = { workspace = true } -derive_more = { workspace = true } -constant_time_eq = "0.4.2" -url = { workspace = true } -axum = { version = "0.8.1", features = ["macros"] } askama = { workspace = true } -chrono = { workspace = true } +async-stream = { workspace = true } +axum = { version = "0.8.1", features = ["macros"] } axum-extra = { version = "0.12.0", features = ["typed-header", "routing", "middleware"] } +base64 = "0.22" +bincode = { workspace = true } +chrono = { workspace = true } comrak = { version = "0.48.0", default-features = false } -serde = { workspace = true } -serde_json = { workspace = true } -tracing = { workspace = true } +constant_time_eq = "0.4.2" +derive_more = { workspace = true } +docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } +docs_rs_build_utils = { path = "../../lib/docs_rs_build_utils" } +docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } +docs_rs_database = { path = "../../lib/docs_rs_database" } +docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } +docs_rs_headers = { path = "../../lib/docs_rs_headers" } +docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry" } +docs_rs_registry_api = { path = "../../lib/docs_rs_registry_api" } +docs_rs_storage = { path = "../../lib/docs_rs_storage" } +docs_rs_utils = { path = "../../lib/docs_rs_utils" } +docs_rs_web_utils = { path = "../../lib/docs_rs_web_utils" } +font-awesome-as-a-crate = { path = "../../lib/font-awesome-as-a-crate" } futures-util = { workspace = true } http = { workspace = true } -syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } -phf = "0.13.1" -base64 = "0.22" -thiserror = { workspace = true } itertools = { workspace = true } -bincode = { workspace = true } -tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } -sqlx = { workspace = true } -async-stream = { workspace = true } lol_html = "2.0.0" +mime = { workspace = true } opentelemetry = { workspace = true } +opentelemetry_sdk = { workspace = true } +phf = "0.13.1" +rayon = { workspace = true } regex = { workspace = true } -tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } -mime = { workspace = true } +semver = { workspace = true } +sentry = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_with = { workspace = true } +sqlx = { workspace = true } +strum = { workspace = true } +syntect = { version = "5.0.0", default-features = false, features = ["parsing", "html", "dump-load", "regex-onig"] } +thiserror = { workspace = true } tokio = { workspace = true } +tokio-util = { version = "0.7.15", default-features = false, features = ["io"] } +toml = { workspace = true } tower = "0.5.1" tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-panic"] } -rayon = { workspace = true } -opentelemetry_sdk = { workspace = true } -strum = { workspace = true } -font-awesome-as-a-crate = { path = "../font-awesome-as-a-crate" } -docs_rs_build_utils = { path = "../docs_rs_build_utils" } -toml = { workspace = true } - +tracing = { workspace = true } +tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } +url = { workspace = true } [build-dependencies] anyhow = { workspace = true } diff --git a/crates/lib/docs_rs_headers/src/etag.rs b/crates/lib/docs_rs_headers/src/etag.rs index cf38e150e..cb045a93b 100644 --- a/crates/lib/docs_rs_headers/src/etag.rs +++ b/crates/lib/docs_rs_headers/src/etag.rs @@ -16,6 +16,12 @@ pub fn compute_etag>(content: T) -> ETag { /// but produces an `ETag` when finalized. pub struct ETagComputer(md5::Context); +impl Default for ETagComputer { + fn default() -> Self { + Self::new() + } +} + impl ETagComputer { pub fn new() -> Self { Self(md5::Context::new()) diff --git a/crates/lib/docs_rs_logging/src/lib.rs b/crates/lib/docs_rs_logging/src/lib.rs index 4eaa8facc..f2607f517 100644 --- a/crates/lib/docs_rs_logging/src/lib.rs +++ b/crates/lib/docs_rs_logging/src/lib.rs @@ -10,7 +10,6 @@ pub struct Guard { sentry_guard: Option, } -#[must_use] pub fn init() -> anyhow::Result { let log_formatter = { let log_format = env::var("DOCSRS_LOG_FORMAT").unwrap_or_default(); From 3e596d4d4bd41c73ce5268594d7d3f4d49207a82 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 12:25:43 +0100 Subject: [PATCH 27/46] kk --- Cargo.lock | 7 +- Cargo.toml | 2 - .../src/docbuilder/rustwide_builder.rs | 2 +- .../crates/additional-targets/Cargo.lock | 0 .../crates/additional-targets/Cargo.toml | 0 .../crates/additional-targets/src/lib.rs | 0 .../tests}/crates/build-std/Cargo.lock | 0 .../tests}/crates/build-std/Cargo.toml | 0 .../tests}/crates/build-std/src/lib.rs | 0 .../crates/incorrect_lockfile_0_1/Cargo.lock | 0 .../crates/incorrect_lockfile_0_1/Cargo.toml | 0 .../crates/incorrect_lockfile_0_1/src/lib.rs | 0 .../crates/incorrect_lockfile_0_1/src/main.rs | 0 .../crates/incorrect_lockfile_0_2/Cargo.lock | 0 .../crates/incorrect_lockfile_0_2/Cargo.toml | 0 .../crates/incorrect_lockfile_0_2/src/lib.rs | 0 .../crates/incorrect_lockfile_0_2/src/main.rs | 0 .../tests}/crates/optional-dep/Cargo.toml | 0 .../tests}/crates/optional-dep/src/lib.rs | 0 .../docs_rs_builder/tests}/regex/body.html | 0 .../docs_rs_builder/tests}/regex/head.html | 0 crates/bin/docs_rs_web/Cargo.toml | 4 + crates/bin/docs_rs_web/src/builds.rs | 2 +- crates/bin/docs_rs_web/src/config.rs | 68 +++++++++++++-- crates/bin/docs_rs_web/src/crate_details.rs | 2 +- crates/bin/docs_rs_web/src/highlight.rs | 3 +- crates/bin/docs_rs_web/src/lib.rs | 49 ++++++----- crates/bin/docs_rs_web/src/page/templates.rs | 2 +- crates/bin/docs_rs_web/src/releases.rs | 4 +- crates/bin/docs_rs_web/src/sitemap.rs | 15 ++-- crates/bin/docs_rs_web/src/utils/html.rs | 39 ++++----- crates/bin/docs_rs_web/src/utils/mod.rs | 1 - .../docs_rs_web/src/utils/rustc_version.rs | 83 ------------------- crates/lib/docs_rs_context/Cargo.toml | 1 + crates/lib/docs_rs_context/src/lib.rs | 26 ++++++ crates/lib/docs_rs_database/src/types/mod.rs | 4 +- crates/lib/docs_rs_registry_api/src/lib.rs | 2 +- 37 files changed, 159 insertions(+), 157 deletions(-) rename {tests => crates/bin/docs_rs_builder/tests}/crates/additional-targets/Cargo.lock (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/additional-targets/Cargo.toml (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/additional-targets/src/lib.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/build-std/Cargo.lock (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/build-std/Cargo.toml (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/build-std/src/lib.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_1/Cargo.lock (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_1/Cargo.toml (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_1/src/lib.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_1/src/main.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_2/Cargo.lock (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_2/Cargo.toml (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_2/src/lib.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/incorrect_lockfile_0_2/src/main.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/optional-dep/Cargo.toml (100%) rename {tests => crates/bin/docs_rs_builder/tests}/crates/optional-dep/src/lib.rs (100%) rename {tests => crates/bin/docs_rs_builder/tests}/regex/body.html (100%) rename {tests => crates/bin/docs_rs_builder/tests}/regex/head.html (100%) delete mode 100644 crates/bin/docs_rs_web/src/utils/rustc_version.rs diff --git a/Cargo.lock b/Cargo.lock index 4cfdd8b38..ff6366261 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1900,7 +1900,6 @@ dependencies = [ "docsrs-metadata", "fn-error-context", "futures-util", - "getrandom 0.3.4", "http 1.4.0", "http-body-util", "indoc", @@ -1908,7 +1907,6 @@ dependencies = [ "kuchikiki", "mime", "mockito", - "num_cpus", "opentelemetry", "opentelemetry-otlp", "opentelemetry-resource-detectors", @@ -2026,6 +2024,7 @@ dependencies = [ "docs_rs_build_queue", "docs_rs_database", "docs_rs_opentelemetry", + "docs_rs_registry_api", "docs_rs_storage", "tokio", ] @@ -2267,6 +2266,7 @@ dependencies = [ "docs_rs_build_queue", "docs_rs_build_utils", "docs_rs_cargo_metadata", + "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", "docs_rs_headers", @@ -2277,12 +2277,14 @@ dependencies = [ "docs_rs_web_utils", "font-awesome-as-a-crate", "futures-util", + "getrandom 0.3.4", "grass", "http 1.4.0", "itertools 0.14.0", "lol_html", "md5", "mime", + "num_cpus", "opentelemetry", "opentelemetry_sdk", "phf 0.13.1", @@ -2294,6 +2296,7 @@ dependencies = [ "serde", "serde_json", "serde_with", + "slug", "sqlx", "strum", "syntect", diff --git a/Cargo.toml b/Cargo.toml index 0297a76e2..86eb04308 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,11 +73,9 @@ docs_rs_web_utils = { path = "crates/lib/docs_rs_web_utils" } docsrs-metadata = { path = "crates/lib/metadata" } fn-error-context = "0.2.0" futures-util = { workspace = true } -getrandom = "0.3.1" http = { workspace = true } itertools = { workspace = true } mime = { workspace = true } -num_cpus = "1.15.0" opentelemetry = { workspace = true } opentelemetry-otlp = { workspace = true } opentelemetry-resource-detectors = { workspace = true } diff --git a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs index ea933ed95..04a8bae93 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -213,7 +213,7 @@ impl RustwideBuilder { runtime: context.runtime().clone(), storage: context.blocking_storage()?, async_storage: context.storage()?, - registry_api: RegistryApi::from_environment()?.into(), + registry_api: context.registry_api()?, repository_stats_updater: RepositoryStatsUpdater::from_environment( context.pool()?.clone(), )? diff --git a/tests/crates/additional-targets/Cargo.lock b/crates/bin/docs_rs_builder/tests/crates/additional-targets/Cargo.lock similarity index 100% rename from tests/crates/additional-targets/Cargo.lock rename to crates/bin/docs_rs_builder/tests/crates/additional-targets/Cargo.lock diff --git a/tests/crates/additional-targets/Cargo.toml b/crates/bin/docs_rs_builder/tests/crates/additional-targets/Cargo.toml similarity index 100% rename from tests/crates/additional-targets/Cargo.toml rename to crates/bin/docs_rs_builder/tests/crates/additional-targets/Cargo.toml diff --git a/tests/crates/additional-targets/src/lib.rs b/crates/bin/docs_rs_builder/tests/crates/additional-targets/src/lib.rs similarity index 100% rename from tests/crates/additional-targets/src/lib.rs rename to crates/bin/docs_rs_builder/tests/crates/additional-targets/src/lib.rs diff --git a/tests/crates/build-std/Cargo.lock b/crates/bin/docs_rs_builder/tests/crates/build-std/Cargo.lock similarity index 100% rename from tests/crates/build-std/Cargo.lock rename to crates/bin/docs_rs_builder/tests/crates/build-std/Cargo.lock diff --git a/tests/crates/build-std/Cargo.toml b/crates/bin/docs_rs_builder/tests/crates/build-std/Cargo.toml similarity index 100% rename from tests/crates/build-std/Cargo.toml rename to crates/bin/docs_rs_builder/tests/crates/build-std/Cargo.toml diff --git a/tests/crates/build-std/src/lib.rs b/crates/bin/docs_rs_builder/tests/crates/build-std/src/lib.rs similarity index 100% rename from tests/crates/build-std/src/lib.rs rename to crates/bin/docs_rs_builder/tests/crates/build-std/src/lib.rs diff --git a/tests/crates/incorrect_lockfile_0_1/Cargo.lock b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/Cargo.lock similarity index 100% rename from tests/crates/incorrect_lockfile_0_1/Cargo.lock rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/Cargo.lock diff --git a/tests/crates/incorrect_lockfile_0_1/Cargo.toml b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/Cargo.toml similarity index 100% rename from tests/crates/incorrect_lockfile_0_1/Cargo.toml rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/Cargo.toml diff --git a/tests/crates/incorrect_lockfile_0_1/src/lib.rs b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/src/lib.rs similarity index 100% rename from tests/crates/incorrect_lockfile_0_1/src/lib.rs rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/src/lib.rs diff --git a/tests/crates/incorrect_lockfile_0_1/src/main.rs b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/src/main.rs similarity index 100% rename from tests/crates/incorrect_lockfile_0_1/src/main.rs rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_1/src/main.rs diff --git a/tests/crates/incorrect_lockfile_0_2/Cargo.lock b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/Cargo.lock similarity index 100% rename from tests/crates/incorrect_lockfile_0_2/Cargo.lock rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/Cargo.lock diff --git a/tests/crates/incorrect_lockfile_0_2/Cargo.toml b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/Cargo.toml similarity index 100% rename from tests/crates/incorrect_lockfile_0_2/Cargo.toml rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/Cargo.toml diff --git a/tests/crates/incorrect_lockfile_0_2/src/lib.rs b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/src/lib.rs similarity index 100% rename from tests/crates/incorrect_lockfile_0_2/src/lib.rs rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/src/lib.rs diff --git a/tests/crates/incorrect_lockfile_0_2/src/main.rs b/crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/src/main.rs similarity index 100% rename from tests/crates/incorrect_lockfile_0_2/src/main.rs rename to crates/bin/docs_rs_builder/tests/crates/incorrect_lockfile_0_2/src/main.rs diff --git a/tests/crates/optional-dep/Cargo.toml b/crates/bin/docs_rs_builder/tests/crates/optional-dep/Cargo.toml similarity index 100% rename from tests/crates/optional-dep/Cargo.toml rename to crates/bin/docs_rs_builder/tests/crates/optional-dep/Cargo.toml diff --git a/tests/crates/optional-dep/src/lib.rs b/crates/bin/docs_rs_builder/tests/crates/optional-dep/src/lib.rs similarity index 100% rename from tests/crates/optional-dep/src/lib.rs rename to crates/bin/docs_rs_builder/tests/crates/optional-dep/src/lib.rs diff --git a/tests/regex/body.html b/crates/bin/docs_rs_builder/tests/regex/body.html similarity index 100% rename from tests/regex/body.html rename to crates/bin/docs_rs_builder/tests/regex/body.html diff --git a/tests/regex/head.html b/crates/bin/docs_rs_builder/tests/regex/head.html similarity index 100% rename from tests/regex/head.html rename to crates/bin/docs_rs_builder/tests/regex/head.html diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index a3e158bdf..b95ba5045 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -20,6 +20,7 @@ derive_more = { workspace = true } docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } docs_rs_build_utils = { path = "../../lib/docs_rs_build_utils" } docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } +docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } docs_rs_headers = { path = "../../lib/docs_rs_headers" } @@ -34,6 +35,7 @@ http = { workspace = true } itertools = { workspace = true } lol_html = "2.0.0" mime = { workspace = true } +num_cpus = "1.15.0" opentelemetry = { workspace = true } opentelemetry_sdk = { workspace = true } phf = "0.13.1" @@ -56,6 +58,8 @@ tower-http = { version = "0.6.0", features = ["fs", "trace", "timeout", "catch-p tracing = { workspace = true } tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } url = { workspace = true } +slug = { workspace = true } +getrandom = "0.3.1" [build-dependencies] anyhow = { workspace = true } diff --git a/crates/bin/docs_rs_web/src/builds.rs b/crates/bin/docs_rs_web/src/builds.rs index 971c85f26..6ebc89f7b 100644 --- a/crates/bin/docs_rs_web/src/builds.rs +++ b/crates/bin/docs_rs_web/src/builds.rs @@ -84,7 +84,7 @@ pub(crate) async fn build_list_handler( Ok(BuildsPage { metadata, builds: get_builds(&mut conn, params.name(), &version).await?, - limits: Limits::for_crate(&config, &mut conn, params.name()).await?, + limits: Limits::for_crate(&config.build_utils_config, &mut conn, params.name()).await?, canonical_url: CanonicalUrl::from_uri( params .clone() diff --git a/crates/bin/docs_rs_web/src/config.rs b/crates/bin/docs_rs_web/src/config.rs index 6de4b3168..1a65cc406 100644 --- a/crates/bin/docs_rs_web/src/config.rs +++ b/crates/bin/docs_rs_web/src/config.rs @@ -1,18 +1,70 @@ -use docs_rs_env_vars::{env, maybe_env}; +use docs_rs_env_vars::{env, maybe_env, require_env}; +use std::{path::PathBuf, time::Duration}; use url::Url; #[derive(Debug)] -pub struct Config {} +pub struct Config { + // Access token for APIs for crates.io (careful: use + // constant_time_eq for comparisons!) + pub(crate) cratesio_token: Option, + // request timeout in seconds + pub(crate) request_timeout: Option, + pub(crate) report_request_timeouts: bool, + // + // Max size of the files served by the docs.rs frontend + pub(crate) max_file_size: usize, + pub(crate) max_file_size_html: usize, + // The most memory that can be used to parse an HTML file + pub(crate) max_parse_memory: usize, + /// amount of threads for CPU intensive rendering + pub(crate) render_threads: usize, + // random crate search generates a number of random IDs to + // efficiently find a random crate with > 100 GH stars. + // The amount depends on the ratio of crates with >100 stars + // to the count of all crates. + // At the time of creating this setting, it is set to + // `500` for a ratio of 7249 over 54k crates. + // For unit-tests the number has to be higher. + pub(crate) random_crate_search_view_size: u32, + + // Content Security Policy + pub(crate) csp_report_only: bool, + + // Cache-Control header, for versioned URLs. + // If both are absent, don't generate the header. If only one is present, + // generate just that directive. Values are in seconds. + pub(crate) cache_control_stale_while_revalidate: Option, + + // Activate full page caching. + // When disabled, we still cache static assets. + // This only affects pages that depend on invalidations to work. + pub(crate) cache_invalidatable_responses: bool, + + pub(crate) storage: docs_rs_storage::Config, + pub(crate) build_utils_config: docs_rs_build_utils::Config, +} impl Config { pub fn from_environment() -> anyhow::Result { + let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; Ok(Self { - // api_host: env( - // "DOCSRS_FASTLY_API_HOST", - // "https://api.fastly.com".parse().unwrap(), - // )?, - // api_token: maybe_env("DOCSRS_FASTLY_API_TOKEN")?, - // service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, + cratesio_token: maybe_env("DOCSRS_CRATESIO_TOKEN")?, + max_file_size: env("DOCSRS_MAX_FILE_SIZE", 50 * 1024 * 1024)?, + max_file_size_html: env("DOCSRS_MAX_FILE_SIZE_HTML", 50 * 1024 * 1024)?, + // LOL HTML only uses as much memory as the size of the start tag! + // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 + max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?, + render_threads: env("DOCSRS_RENDER_THREADS", num_cpus::get())?, + request_timeout: maybe_env::("DOCSRS_REQUEST_TIMEOUT")?.map(Duration::from_secs), + report_request_timeouts: env("DOCSRS_REPORT_REQUEST_TIMEOUTS", false)?, + random_crate_search_view_size: env("DOCSRS_RANDOM_CRATE_SEARCH_VIEW_SIZE", 500)?, + csp_report_only: env("DOCSRS_CSP_REPORT_ONLY", false)?, + cache_control_stale_while_revalidate: maybe_env( + "CACHE_CONTROL_STALE_WHILE_REVALIDATE", + )?, + cache_invalidatable_responses: env("DOCSRS_CACHE_INVALIDATEABLE_RESPONSES", true)?, + storage: docs_rs_storage::Config::from_environment()?, + build_utils_config: docs_rs_build_utils::Config::from_environment()?, }) } } diff --git a/crates/bin/docs_rs_web/src/crate_details.rs b/crates/bin/docs_rs_web/src/crate_details.rs index ed443707a..8e026fc79 100644 --- a/crates/bin/docs_rs_web/src/crate_details.rs +++ b/crates/bin/docs_rs_web/src/crate_details.rs @@ -8,7 +8,6 @@ use crate::{ }, impl_axum_webpage, match_version, page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, - utils::rustc_version::get_correct_docsrs_style_file, }; use anyhow::{Context, Result, anyhow}; use askama::Template; @@ -23,6 +22,7 @@ use docs_rs_database::types::{BuildId, BuildStatus, CrateId, ReleaseId, version: use docs_rs_headers::CanonicalUrl; use docs_rs_registry_api::OwnerKind; use docs_rs_storage::{AsyncStorage, errors::PathNotFoundError}; +use docs_rs_utils::rustc_version::get_correct_docsrs_style_file; use futures_util::stream::TryStreamExt; use serde_json::Value; use std::sync::Arc; diff --git a/crates/bin/docs_rs_web/src/highlight.rs b/crates/bin/docs_rs_web/src/highlight.rs index f6e1fbfdd..c448d3f54 100644 --- a/crates/bin/docs_rs_web/src/highlight.rs +++ b/crates/bin/docs_rs_web/src/highlight.rs @@ -1,10 +1,11 @@ +use anyhow::Result; use std::sync::LazyLock; use syntect::{ html::{ClassStyle, ClassedHTMLGenerator}, parsing::{SyntaxReference, SyntaxSet}, util::LinesWithEndings, }; -use tracing::{debug, error, info}; +use tracing::{debug, error}; const TOTAL_CODE_BYTE_LENGTH_LIMIT: usize = 2 * 1024 * 1024; const PER_LINE_BYTE_LENGTH_LIMIT: usize = 512; diff --git a/crates/bin/docs_rs_web/src/lib.rs b/crates/bin/docs_rs_web/src/lib.rs index 6f26da711..48d81daa2 100644 --- a/crates/bin/docs_rs_web/src/lib.rs +++ b/crates/bin/docs_rs_web/src/lib.rs @@ -6,13 +6,8 @@ use docs_rs_database::types::BuildStatus; pub use docs_rs_utils::{BUILD_VERSION, DEFAULT_MAX_TARGETS}; pub use font_awesome_as_a_crate::icons; -// use crate::{ -// utils::get_correct_docsrs_style_file, -// web::{ -// metrics::WebMetrics, -// page::templates::{RenderBrands, RenderSolid, filters}, -// }, -// }; +use crate::page::templates::{RenderBrands, RenderSolid}; + use anyhow::{Context as _, Result, anyhow, bail}; use askama::Template; use axum_extra::middleware::option_layer; @@ -70,7 +65,8 @@ use std::{ use tower::ServiceBuilder; use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::TraceLayer}; -use crate::metrics::WebMetrics; +use crate::{config::Config, metrics::WebMetrics}; +use docs_rs_utils::rustc_version::get_correct_docsrs_style_file; use self::crate_details::Release; use page::GlobalAlert; @@ -474,12 +470,13 @@ async fn set_sentry_transaction_name_from_axum_route( async fn apply_middleware( router: AxumRouter, + config: Arc, context: &Context, template_data: Option>, ) -> Result { let has_templates = template_data.is_some(); - let web_metrics = Arc::new(WebMetrics::new(&context.meter_provider)); + let web_metrics = Arc::new(WebMetrics::new(&context.meter_provider())); Ok(router.layer( ServiceBuilder::new() @@ -491,20 +488,19 @@ async fn apply_middleware( )) .layer(CatchPanicLayer::new()) .layer(option_layer( - context - .config + config .report_request_timeouts .then_some(middleware::from_fn(log_timeouts_to_sentry)), )) - .layer(option_layer(context.config.request_timeout.map(|to| { + .layer(option_layer(config.request_timeout.map(|to| { TimeoutLayer::with_status_code(StatusCode::REQUEST_TIMEOUT, to) }))) - .layer(Extension(context.pool.clone())) + .layer(Extension(context.pool()?.clone())) // .layer(Extension(context.async_build_queue.clone())) .layer(Extension(web_metrics)) - .layer(Extension(context.config.clone())) - .layer(Extension(context.registry_api.clone())) - .layer(Extension(context.async_storage.clone())) + .layer(Extension(config.clone())) + .layer(Extension(context.registry_api()?.clone())) + .layer(Extension(context.storage()?.clone())) .layer(option_layer(template_data.map(Extension))) .layer(middleware::from_fn(csp::csp_middleware)) .layer(option_layer(has_templates.then_some(middleware::from_fn( @@ -515,15 +511,26 @@ async fn apply_middleware( } pub(crate) async fn build_axum_app( + config: Arc, context: &Context, template_data: Arc, ) -> Result { - apply_middleware(routes::build_axum_routes(), context, Some(template_data)).await + apply_middleware( + routes::build_axum_routes(), + config, + context, + Some(template_data), + ) + .await } #[instrument(skip_all)] -pub fn start_web_server(addr: Option, context: &Context) -> Result<(), Error> { - let template_data = Arc::new(TemplateData::new(context.config.render_threads)?); +pub fn start_web_server( + addr: Option, + config: Arc, + context: &Context, +) -> Result<(), Error> { + let template_data = Arc::new(TemplateData::new(config.render_threads)?); let axum_addr = addr.unwrap_or(DEFAULT_BIND); @@ -533,8 +540,8 @@ pub fn start_web_server(addr: Option, context: &Context) -> Result<( axum_addr.port() ); - context.runtime.block_on(async { - let app = build_axum_app(context, template_data) + context.runtime().block_on(async { + let app = build_axum_app(config, context, template_data) .await? .into_make_service(); let listener = tokio::net::TcpListener::bind(axum_addr) diff --git a/crates/bin/docs_rs_web/src/page/templates.rs b/crates/bin/docs_rs_web/src/page/templates.rs index a44fa0fc5..abaebddab 100644 --- a/crates/bin/docs_rs_web/src/page/templates.rs +++ b/crates/bin/docs_rs_web/src/page/templates.rs @@ -1,5 +1,5 @@ use crate::rustdoc::RustdocPage; -use anyhow::Context; +use anyhow::{Context as _, Result}; use askama::Template; use std::sync::Arc; use tracing::trace; diff --git a/crates/bin/docs_rs_web/src/releases.rs b/crates/bin/docs_rs_web/src/releases.rs index 131e8351c..17cb17bef 100644 --- a/crates/bin/docs_rs_web/src/releases.rs +++ b/crates/bin/docs_rs_web/src/releases.rs @@ -32,7 +32,7 @@ use std::{ str, sync::Arc, }; -use tracing::{trace, warn}; +use tracing::{error, trace, warn}; use url::form_urlencoded; /// Number of release in home page @@ -502,7 +502,7 @@ async fn redirect_to_random_crate( Ok(axum_redirect(params.rustdoc_url())?) } else { - report_error(&anyhow!("found no result in random crate search")); + error!("found no result in random crate search"); Err(AxumNope::NoResults) } } diff --git a/crates/bin/docs_rs_web/src/sitemap.rs b/crates/bin/docs_rs_web/src/sitemap.rs index 2d94a55e2..5ed1cd074 100644 --- a/crates/bin/docs_rs_web/src/sitemap.rs +++ b/crates/bin/docs_rs_web/src/sitemap.rs @@ -98,11 +98,11 @@ pub(crate) async fn sitemap_handler( pin_mut!(result); while let Some(row) = result.next().await { - let row = match row.context("error fetching row from database") { + let row = match row { Ok(row) => row, Err(err) => { - report_error(&err); - yield Err(AxumNope::InternalError(err)); + error!(?err, "error fetching row from database"); + yield Err(AxumNope::InternalError(err.into())); break; } }; @@ -119,9 +119,8 @@ pub(crate) async fn sitemap_handler( .max(Utc.with_ymd_and_hms(2022, 8, 28, 0, 0, 0).unwrap()) .format("%+") .to_string(), - } + }) .render() - .context("error when rendering sitemap item xml")) { Ok(item) => { let bytes = Bytes::from(item); @@ -130,8 +129,8 @@ pub(crate) async fn sitemap_handler( yield Ok(bytes); } Err(err) => { - report_error(&err); - yield Err(AxumNope::InternalError(err)); + error!(?err, "error when rendering sitemap item xml"); + yield Err(AxumNope::InternalError(err.into())); break; } }; @@ -175,7 +174,7 @@ pub(crate) async fn about_builds_handler( ) -> AxumResult { Ok(AboutBuilds { rustc_version: get_config::(&mut conn, ConfigName::RustcVersion).await?, - limits: Limits::new(&config), + limits: Limits::new(&config.build_utils_config), active_tab: "builds", }) } diff --git a/crates/bin/docs_rs_web/src/utils/html.rs b/crates/bin/docs_rs_web/src/utils/html.rs index 0b05808d6..d99741482 100644 --- a/crates/bin/docs_rs_web/src/utils/html.rs +++ b/crates/bin/docs_rs_web/src/utils/html.rs @@ -174,20 +174,17 @@ where let mut reader_stream = ReaderStream::new(&mut reader); while let Some(chunk) = reader_stream.next().await { - let chunk = chunk - .context("error while reading from rustdoc HTML reader") - .map_err(|err| { - report_error(&err); - RustdocRewritingError::Other(err) - })?; + let chunk = chunk.map_err(|err| { + error!(?err, "error while reading from rustdoc HTML reader"); + RustdocRewritingError::Other(err.into()) + })?; - if let Err(err) = input_sender - .send(Some(chunk)) - .await - .context("error when trying to send chunk to html rewriter thread") - { - report_error(&err); - yield Err(RustdocRewritingError::Other(err)); + if let Err(err) = input_sender.send(Some(chunk)).await { + error!( + ?err, + "error when trying to send chunk to html rewriter thread" + ); + yield Err(RustdocRewritingError::Other(err.into())); break; } @@ -196,13 +193,12 @@ where } } // This signals the renderer thread to finalize & exit. - if let Err(err) = input_sender - .send(None) - .await - .context("error when trying to send end signal to html rewriter thread") - { - report_error(&err); - yield Err(RustdocRewritingError::Other(err)); + if let Err(err) = input_sender.send(None).await { + error!( + ?err, + "error when trying to send end signal to html rewriter thread" + ); + yield Err(RustdocRewritingError::Other(err.into())); } while let Some(bytes) = result_receiver.recv().await { yield Ok(bytes); @@ -211,9 +207,8 @@ where join_handle .await .context("task join failed")? - .context("error while rewriting rustdoc HTML") .map_err(|e| { - report_error(&e); + error!(?e, "error while rewriting rustdoc HTML"); // our `render_in_threadpool` and so the async tokio task return an `anyhow::Result`. // In most cases this will be an error from the `HtmlRewriter`, which we'll get as a // `RewritingError` which we extract here again. The other cases remain an diff --git a/crates/bin/docs_rs_web/src/utils/mod.rs b/crates/bin/docs_rs_web/src/utils/mod.rs index 96680ef2d..558c29bf7 100644 --- a/crates/bin/docs_rs_web/src/utils/mod.rs +++ b/crates/bin/docs_rs_web/src/utils/mod.rs @@ -1,2 +1 @@ pub(crate) mod html; -pub(crate) mod rustc_version; diff --git a/crates/bin/docs_rs_web/src/utils/rustc_version.rs b/crates/bin/docs_rs_web/src/utils/rustc_version.rs deleted file mode 100644 index 8a9194dd5..000000000 --- a/crates/bin/docs_rs_web/src/utils/rustc_version.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::error::Result; -use anyhow::{Context, anyhow}; -use chrono::prelude::*; -use regex::Regex; -use std::sync::LazyLock; - -/// Parses rustc commit hash from rustc version string -pub fn parse_rustc_version>(version: S) -> Result { - let version_regex = Regex::new(r" ([\w.-]+) \((\w+) (\d+)-(\d+)-(\d+)\)")?; - let captures = version_regex - .captures(version.as_ref()) - .with_context(|| anyhow!("Failed to parse rustc version '{}'", version.as_ref()))?; - - Ok(format!( - "{}{}{}-{}-{}", - captures.get(3).unwrap().as_str(), - captures.get(4).unwrap().as_str(), - captures.get(5).unwrap().as_str(), - captures.get(1).unwrap().as_str(), - captures.get(2).unwrap().as_str() - )) -} - -pub(crate) fn parse_rustc_date>(version: S) -> Result { - static RE: LazyLock = LazyLock::new(|| Regex::new(r" (\d+)-(\d+)-(\d+)\)$").unwrap()); - - let cap = RE - .captures(version.as_ref()) - .with_context(|| anyhow!("Failed to parse rustc date"))?; - - let year = cap.get(1).unwrap().as_str(); - let month = cap.get(2).unwrap().as_str(); - let day = cap.get(3).unwrap().as_str(); - - NaiveDate::from_ymd_opt( - year.parse::().unwrap(), - month.parse::().unwrap(), - day.parse::().unwrap(), - ) - .ok_or_else(|| anyhow!("date out of range")) -} - -/// Picks the correct "rustdoc.css" static file depending on which rustdoc version was used to -/// generate this version of this crate. -pub fn get_correct_docsrs_style_file(version: &str) -> Result { - let date = parse_rustc_date(version)?; - // This is the date where https://github.com/rust-lang/rust/pull/144476 was merged. - if NaiveDate::from_ymd_opt(2025, 8, 20).unwrap() < date { - Ok("rustdoc-2025-08-20.css".to_owned()) - // This is the date where https://github.com/rust-lang/rust/pull/91356 was merged. - } else if NaiveDate::from_ymd_opt(2021, 12, 5).unwrap() < date { - // If this is the new rustdoc layout, we need the newer docs.rs CSS file. - Ok("rustdoc-2021-12-05.css".to_owned()) - } else { - // By default, we return the old docs.rs CSS file. - Ok("rustdoc.css".to_owned()) - } -} - -#[test] -fn test_parse_rustc_version() { - assert_eq!( - parse_rustc_version("rustc 1.10.0-nightly (57ef01513 2016-05-23)").unwrap(), - "20160523-1.10.0-nightly-57ef01513" - ); - assert_eq!( - parse_rustc_version("docsrs 0.2.0 (ba9ae23 2016-05-26)").unwrap(), - "20160526-0.2.0-ba9ae23" - ); -} - -#[test] -fn test_get_correct_docsrs_style_file() { - assert_eq!( - get_correct_docsrs_style_file("rustc 1.10.0-nightly (57ef01513 2016-05-23)").unwrap(), - "rustdoc.css" - ); - assert_eq!( - get_correct_docsrs_style_file("docsrs 0.2.0 (ba9ae23 2022-05-26)").unwrap(), - "rustdoc-2021-12-05.css" - ); - assert!(get_correct_docsrs_style_file("docsrs 0.2.0").is_err(),); -} diff --git a/crates/lib/docs_rs_context/Cargo.toml b/crates/lib/docs_rs_context/Cargo.toml index cd493c627..f454c9d1a 100644 --- a/crates/lib/docs_rs_context/Cargo.toml +++ b/crates/lib/docs_rs_context/Cargo.toml @@ -8,5 +8,6 @@ anyhow = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } +docs_rs_registry_api = { path = "../docs_rs_registry_api" } tokio = { workspace = true } docs_rs_storage = { path = "../docs_rs_storage" } diff --git a/crates/lib/docs_rs_context/src/lib.rs b/crates/lib/docs_rs_context/src/lib.rs index 295f6c642..3b6a09590 100644 --- a/crates/lib/docs_rs_context/src/lib.rs +++ b/crates/lib/docs_rs_context/src/lib.rs @@ -2,6 +2,7 @@ use anyhow::{Result, anyhow}; use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; use docs_rs_database::Pool; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; +use docs_rs_registry_api::RegistryApi; use docs_rs_storage::{AsyncStorage, Storage}; use std::sync::Arc; use tokio::runtime::Handle; @@ -12,6 +13,7 @@ pub struct Config { build_queue: Option>, database: Option>, storage: Option>, + registry_api: Option>, } pub struct Context { @@ -25,6 +27,8 @@ pub struct Context { storage: Option>, blocking_storage: Option>, + registry_api: Option>, + runtime: Handle, config: Config, } @@ -49,6 +53,7 @@ impl Context { blocking_build_queue: None, storage: None, blocking_storage: None, + registry_api: None, }) } @@ -99,6 +104,19 @@ impl Context { self.storage = Some(storage); Ok(self) } + + pub async fn with_registry_api(mut self) -> Result { + if self.registry_api.is_some() { + return Ok(self); + } + + let config = docs_rs_registry_api::Config::from_environment()?; + let api = RegistryApi::from_config(&config)?; + + self.registry_api = Some(Arc::new(api)); + self.config.registry_api = Some(Arc::new(config)); + Ok(self) + } } // accessors @@ -150,4 +168,12 @@ impl Context { Err(anyhow!("blocking Build queue is not initialized")) } } + + pub fn registry_api(&self) -> Result> { + if let Some(ref registry_api) = self.registry_api { + Ok(registry_api.clone()) + } else { + Err(anyhow!("Registry API is not initialized")) + } + } } diff --git a/crates/lib/docs_rs_database/src/types/mod.rs b/crates/lib/docs_rs_database/src/types/mod.rs index 4f317cff9..a4fcd208e 100644 --- a/crates/lib/docs_rs_database/src/types/mod.rs +++ b/crates/lib/docs_rs_database/src/types/mod.rs @@ -19,8 +19,8 @@ pub struct BuildId(pub i32); #[derive(Debug, Clone, PartialEq, Eq, Serialize, sqlx::Type)] #[sqlx(type_name = "feature")] pub struct Feature { - pub(crate) name: String, - pub(crate) subfeatures: Vec, + pub name: String, + pub subfeatures: Vec, } impl Feature { diff --git a/crates/lib/docs_rs_registry_api/src/lib.rs b/crates/lib/docs_rs_registry_api/src/lib.rs index 3435be4e2..03d4b6824 100644 --- a/crates/lib/docs_rs_registry_api/src/lib.rs +++ b/crates/lib/docs_rs_registry_api/src/lib.rs @@ -1,6 +1,6 @@ mod config; -use crate::config::Config; +pub use crate::config::Config; use anyhow::{Context, Result, anyhow, bail}; use chrono::{DateTime, Utc}; use docs_rs_database::types::version::Version; From 07dcfef77dc7c93849324e07a78789629ae5375c Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 12:27:50 +0100 Subject: [PATCH 28/46] clean up --- Cargo.lock | 1018 ++--------------- Cargo.toml | 122 +- .../docs_rs_storage}/tests/regex/body.html | 0 .../docs_rs_storage}/tests/regex/head.html | 0 4 files changed, 187 insertions(+), 953 deletions(-) rename crates/{bin/docs_rs_builder => lib/docs_rs_storage}/tests/regex/body.html (100%) rename crates/{bin/docs_rs_builder => lib/docs_rs_storage}/tests/regex/head.html (100%) diff --git a/Cargo.lock b/Cargo.lock index ff6366261..6eb98e66d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,7 +8,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bytes", "futures-core", "futures-sink", @@ -29,17 +29,17 @@ dependencies = [ "actix-rt", "actix-service", "actix-utils", - "bitflags 2.10.0", + "bitflags", "bytes", "bytestring", - "derive_more 2.0.1", + "derive_more", "encoding_rs", "foldhash 0.1.5", "futures-core", "http 0.2.12", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "language-tags", "mime", "percent-encoding", @@ -127,13 +127,13 @@ dependencies = [ "bytes", "bytestring", "cfg-if", - "derive_more 2.0.1", + "derive_more", "encoding_rs", "foldhash 0.1.5", "futures-core", "futures-util", "impl-more", - "itoa 1.0.15", + "itoa", "language-tags", "log", "mime", @@ -196,15 +196,6 @@ dependencies = [ "as-slice", ] -[[package]] -name = "alloca" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" -dependencies = [ - "cc", -] - [[package]] name = "allocator-api2" version = "0.2.21" @@ -220,12 +211,6 @@ dependencies = [ "libc", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "anstream" version = "0.6.21" @@ -322,7 +307,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f75363874b771be265f4ffe307ca705ef6f3baa19011c149da8674a87f1b75c4" dependencies = [ "askama_derive", - "itoa 1.0.15", + "itoa", "percent-encoding", "serde", "serde_json", @@ -342,7 +327,7 @@ dependencies = [ "rustc-hash", "serde", "serde_derive", - "syn 2.0.111", + "syn", ] [[package]] @@ -399,7 +384,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -410,7 +395,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -687,29 +672,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "623254723e8dfd535f566ee7b2381645f8981da086b5c4aa26c0c41582bb1d2c" dependencies = [ "aws-smithy-async", - "aws-smithy-protocol-test", "aws-smithy-runtime-api", "aws-smithy-types", - "bytes", "h2 0.3.27", "h2 0.4.12", "http 0.2.12", "http 1.4.0", "http-body 0.4.6", - "http-body 1.0.1", "hyper 0.14.32", "hyper 1.8.1", "hyper-rustls 0.24.2", "hyper-rustls 0.27.7", "hyper-util", - "indexmap 2.12.1", "pin-project-lite", "rustls 0.21.12", "rustls 0.23.35", "rustls-native-certs 0.8.2", "rustls-pki-types", - "serde", - "serde_json", "tokio", "tokio-rustls 0.26.4", "tower", @@ -734,25 +713,6 @@ dependencies = [ "aws-smithy-runtime-api", ] -[[package]] -name = "aws-smithy-protocol-test" -version = "0.63.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa808d23a8edf0da73f6812d06d8c0a48d70f05d2d3696362982aad11ee475b7" -dependencies = [ - "assert-json-diff", - "aws-smithy-runtime-api", - "base64-simd", - "cbor-diag", - "ciborium", - "http 0.2.12", - "pretty_assertions", - "regex-lite", - "roxmltree", - "serde_json", - "thiserror 2.0.17", -] - [[package]] name = "aws-smithy-query" version = "0.60.8" @@ -785,7 +745,6 @@ dependencies = [ "pin-utils", "tokio", "tracing", - "tracing-subscriber", ] [[package]] @@ -820,7 +779,7 @@ dependencies = [ "http-body 0.4.6", "http-body 1.0.1", "http-body-util", - "itoa 1.0.15", + "itoa", "num-integer", "pin-project-lite", "pin-utils", @@ -880,7 +839,7 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-util", - "itoa 1.0.15", + "itoa", "matchit", "memchr", "mime", @@ -948,7 +907,7 @@ checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1038,12 +997,6 @@ dependencies = [ "virtue", ] -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - [[package]] name = "bitflags" version = "2.10.0" @@ -1080,15 +1033,6 @@ dependencies = [ "cfg_aliases", ] -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - [[package]] name = "bstr" version = "1.12.1" @@ -1155,31 +1099,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cbor-diag" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc245b6ecd09b23901a4fbad1ad975701fd5061ceaef6afa93a2d70605a64429" -dependencies = [ - "bs58", - "chrono", - "data-encoding", - "half", - "nom", - "num-bigint", - "num-rational", - "num-traits", - "separator", - "url", - "uuid", -] - [[package]] name = "cc" version = "1.2.48" @@ -1216,33 +1135,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - [[package]] name = "clap" version = "4.5.53" @@ -1274,7 +1166,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1373,12 +1265,6 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.9.4" @@ -1491,41 +1377,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "criterion" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0dfe5e9e71bdcf4e4954f7d14da74d1cdb92a3a07686452d1509652684b1aab" -dependencies = [ - "alloca", - "anes", - "cast", - "ciborium", - "clap", - "criterion-plot", - "itertools 0.13.0", - "num-traits", - "oorandom", - "page_size", - "plotters", - "rayon", - "regex", - "serde", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de36c2bee19fba779808f92bf5d9b0fa5a40095c277aba10c458a12b35d21d6" -dependencies = [ - "cast", - "itertools 0.13.0", -] - [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1569,12 +1420,6 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1607,23 +1452,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "cssparser" -version = "0.27.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" -dependencies = [ - "cssparser-macros", - "dtoa-short", - "itoa 0.4.8", - "matches", - "phf 0.8.0", - "proc-macro2", - "quote", - "smallvec", - "syn 1.0.109", -] - [[package]] name = "cssparser" version = "0.35.0" @@ -1632,7 +1460,7 @@ checksum = "4e901edd733a1472f944a45116df3f846f54d37e67e68640ac8bb69689aca2aa" dependencies = [ "cssparser-macros", "dtoa-short", - "itoa 1.0.15", + "itoa", "phf 0.11.3", "smallvec", ] @@ -1644,7 +1472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" dependencies = [ "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1707,7 +1535,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn", ] [[package]] @@ -1718,7 +1546,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -1735,12 +1563,6 @@ dependencies = [ "parking_lot_core", ] -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - [[package]] name = "debugid" version = "0.8.0" @@ -1790,20 +1612,7 @@ checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.111", + "syn", ] [[package]] @@ -1823,7 +1632,7 @@ checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "unicode-xid", ] @@ -1833,12 +1642,6 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - [[package]] name = "digest" version = "0.10.7" @@ -1857,7 +1660,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ - "bitflags 2.10.0", + "bitflags", "objc2", ] @@ -1869,72 +1672,12 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] name = "docs-rs" version = "0.6.0" -dependencies = [ - "anyhow", - "async-stream", - "aws-smithy-runtime", - "aws-smithy-types", - "bincode 2.0.1", - "chrono", - "clap", - "criterion", - "derive_more 2.0.1", - "docs_rs_build_queue", - "docs_rs_cargo_metadata", - "docs_rs_database", - "docs_rs_env_vars", - "docs_rs_headers", - "docs_rs_logging", - "docs_rs_opentelemetry", - "docs_rs_registry_api", - "docs_rs_storage", - "docs_rs_utils", - "docs_rs_watcher", - "docs_rs_web_utils", - "docsrs-metadata", - "fn-error-context", - "futures-util", - "http 1.4.0", - "http-body-util", - "indoc", - "itertools 0.14.0", - "kuchikiki", - "mime", - "mockito", - "opentelemetry", - "opentelemetry-otlp", - "opentelemetry-resource-detectors", - "opentelemetry_sdk", - "path-slash", - "pretty_assertions", - "rand 0.9.2", - "rayon", - "regex", - "reqwest", - "semver", - "sentry", - "serde", - "serde_json", - "serde_with", - "slug", - "sqlx", - "strum", - "tempfile", - "test-case", - "thiserror 2.0.17", - "tokio", - "tower", - "tracing", - "tracing-log", - "url", - "walkdir", -] [[package]] name = "docs_rs_build_queue" @@ -1985,7 +1728,7 @@ dependencies = [ "docsrs-metadata", "futures-util", "hostname", - "itertools 0.14.0", + "itertools", "log", "opentelemetry", "regex", @@ -2009,7 +1752,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode 2.0.1", - "derive_more 2.0.1", + "derive_more", "docs_rs_database", "semver", "serde", @@ -2035,7 +1778,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode 2.0.1", - "derive_more 2.0.1", + "derive_more", "docs_rs_env_vars", "docs_rs_opentelemetry", "futures-util", @@ -2074,7 +1817,7 @@ dependencies = [ "docs_rs_opentelemetry", "docs_rs_utils", "http 1.4.0", - "itertools 0.14.0", + "itertools", "opentelemetry", "reqwest", "tracing", @@ -2087,13 +1830,13 @@ version = "0.1.0" dependencies = [ "anyhow", "askama", - "derive_more 2.0.1", + "derive_more", "docs_rs_database", "docs_rs_utils", "docs_rs_web_utils", "headers", "http 1.4.0", - "itertools 0.14.0", + "itertools", "md5", "serde", ] @@ -2183,7 +1926,7 @@ dependencies = [ "flate2", "futures-util", "http 1.4.0", - "itertools 0.14.0", + "itertools", "mime", "opentelemetry", "serde", @@ -2233,7 +1976,7 @@ dependencies = [ "docs_rs_storage", "docs_rs_utils", "futures-util", - "itertools 0.14.0", + "itertools", "mockito", "opentelemetry", "rayon", @@ -2262,7 +2005,7 @@ dependencies = [ "chrono", "comrak", "constant_time_eq", - "derive_more 2.0.1", + "derive_more", "docs_rs_build_queue", "docs_rs_build_utils", "docs_rs_cargo_metadata", @@ -2280,7 +2023,7 @@ dependencies = [ "getrandom 0.3.4", "grass", "http 1.4.0", - "itertools 0.14.0", + "itertools", "lol_html", "md5", "mime", @@ -2538,17 +2281,6 @@ dependencies = [ "spin", ] -[[package]] -name = "fn-error-context" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd66269887534af4b0c3e3337404591daa8dc8b9b2b3db71f9523beb4bafb41" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - [[package]] name = "fnv" version = "1.0.7" @@ -2625,16 +2357,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" -[[package]] -name = "futf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" -dependencies = [ - "mac", - "new_debug_unreachable", -] - [[package]] name = "futures" version = "0.3.31" @@ -2702,7 +2424,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -2735,15 +2457,6 @@ dependencies = [ "slab", ] -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -2754,17 +2467,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "getrandom" -version = "0.1.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" -dependencies = [ - "cfg-if", - "libc", - "wasi 0.9.0+wasi-snapshot-preview1", -] - [[package]] name = "getrandom" version = "0.2.16" @@ -2774,7 +2476,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "wasm-bindgen", ] @@ -2802,7 +2504,7 @@ version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ - "bitflags 2.10.0", + "bitflags", "libc", "libgit2-sys", "log", @@ -2924,7 +2626,7 @@ dependencies = [ "bstr", "gix-date", "gix-utils", - "itoa 1.0.15", + "itoa", "thiserror 2.0.17", "winnow", ] @@ -3068,7 +2770,7 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c489abb061c74b0c3ad790e24a606ef968cebab48ec673d6a891ece7d5aef64" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-path", "libc", @@ -3117,7 +2819,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "661245d045aa7c16ba4244daaabd823c562c3e45f1f25b816be2c57ee09f2171" dependencies = [ "bstr", - "itoa 1.0.15", + "itoa", "jiff", "smallvec", "thiserror 2.0.17", @@ -3306,7 +3008,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90181472925b587f6079698f79065ff64786e6d6c14089517a1972bca99fb6e9" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-features 0.42.1", "gix-path", @@ -3318,7 +3020,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b947db8366823e7a750c254f6bb29e27e17f27e457bf336ba79b32423db62cd5" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-features 0.43.1", "gix-path", @@ -3402,7 +3104,7 @@ version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b38e919efd59cb8275d23ad2394b2ab9d002007b27620e145d866d546403b665" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "filetime", "fnv", @@ -3416,7 +3118,7 @@ dependencies = [ "gix-utils", "gix-validate", "hashbrown 0.14.5", - "itoa 1.0.15", + "itoa", "libc", "memmap2", "rustix", @@ -3430,7 +3132,7 @@ version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af39fde3ce4ce11371d9ce826f2936ec347318f2d1972fe98c2e7134e267e25" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "filetime", "fnv", @@ -3444,7 +3146,7 @@ dependencies = [ "gix-utils", "gix-validate", "hashbrown 0.15.5", - "itoa 1.0.15", + "itoa", "libc", "memmap2", "rustix", @@ -3480,7 +3182,7 @@ version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e1ea901acc4d5b44553132a29e8697210cb0e739b2d9752d713072e9391e3c9" dependencies = [ - "bitflags 2.10.0", + "bitflags", "gix-commitgraph 0.28.0", "gix-date", "gix-hash 0.18.0", @@ -3496,7 +3198,7 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d58d4c9118885233be971e0d7a589f5cfb1a8bd6cb6e2ecfb0fc6b1b293c83b" dependencies = [ - "bitflags 2.10.0", + "bitflags", "gix-commitgraph 0.29.0", "gix-date", "gix-hash 0.19.0", @@ -3521,7 +3223,7 @@ dependencies = [ "gix-path", "gix-utils", "gix-validate", - "itoa 1.0.15", + "itoa", "smallvec", "thiserror 2.0.17", "winnow", @@ -3542,7 +3244,7 @@ dependencies = [ "gix-path", "gix-utils", "gix-validate", - "itoa 1.0.15", + "itoa", "smallvec", "thiserror 2.0.17", "winnow", @@ -3674,7 +3376,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce061c50e5f8f7c830cacb3da3e999ae935e283ce8522249f0ce2256d110979d" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-attributes 0.26.1", "gix-config-value", @@ -3689,7 +3391,7 @@ version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daedead611c9bd1f3640dc90a9012b45f790201788af4d659f28d94071da7fba" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-attributes 0.27.0", "gix-config-value", @@ -3850,7 +3552,7 @@ version = "0.34.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78d0b8e5cbd1c329e25383e088cb8f17439414021a643b30afa5146b71e3c65d" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-commitgraph 0.28.0", "gix-date", @@ -3868,7 +3570,7 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f651f2b1742f760bb8161d6743229206e962b73d9c33c41f4e4aefa6586cbd3d" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bstr", "gix-commitgraph 0.29.0", "gix-date", @@ -3916,7 +3618,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0dabbc78c759ecc006b970339394951b2c8e1e38a37b072c105b80b84c308fd" dependencies = [ - "bitflags 2.10.0", + "bitflags", "gix-path", "libc", "windows-sys 0.59.0", @@ -3928,7 +3630,7 @@ version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea9962ed6d9114f7f100efe038752f41283c225bb507a2888903ac593dffa6be" dependencies = [ - "bitflags 2.10.0", + "bitflags", "gix-path", "libc", "windows-sys 0.61.2", @@ -4065,7 +3767,7 @@ version = "0.46.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8648172f85aca3d6e919c06504b7ac26baef54e04c55eb0100fa588c102cc33" dependencies = [ - "bitflags 2.10.0", + "bitflags", "gix-commitgraph 0.28.0", "gix-date", "gix-hash 0.18.0", @@ -4082,7 +3784,7 @@ version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cdc82509d792ba0ad815f86f6b469c7afe10f94362e96c4494525a6601bdd5" dependencies = [ - "bitflags 2.10.0", + "bitflags", "gix-commitgraph 0.29.0", "gix-date", "gix-hash 0.19.0", @@ -4251,17 +3953,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "half" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" -dependencies = [ - "cfg-if", - "crunchy", - "zerocopy", -] - [[package]] name = "hash32" version = "0.3.1" @@ -4411,20 +4102,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "html5ever" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" -dependencies = [ - "log", - "mac", - "markup5ever", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "http" version = "0.2.12" @@ -4433,7 +4110,7 @@ checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -4443,7 +4120,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "itoa 1.0.15", + "itoa", ] [[package]] @@ -4513,7 +4190,7 @@ dependencies = [ "http-body 0.4.6", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "pin-project-lite", "socket2 0.5.10", "tokio", @@ -4537,7 +4214,7 @@ dependencies = [ "http-body 1.0.1", "httparse", "httpdate", - "itoa 1.0.15", + "itoa", "pin-project-lite", "pin-utils", "smallvec", @@ -4803,15 +4480,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "indoc" -version = "2.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" -dependencies = [ - "rustversion", -] - [[package]] name = "ipnet" version = "2.11.0" @@ -4834,15 +4502,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -4852,12 +4511,6 @@ dependencies = [ "either", ] -[[package]] -name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - [[package]] name = "itoa" version = "1.0.15" @@ -4893,7 +4546,7 @@ checksum = "980af8b43c3ad5d8d349ace167ec8170839f753a42d233ba19e08afe1850fa69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -4940,19 +4593,6 @@ dependencies = [ "static_assertions", ] -[[package]] -name = "kuchikiki" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" -dependencies = [ - "cssparser 0.27.2", - "html5ever", - "indexmap 1.9.3", - "matches", - "selectors 0.22.0", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -5015,7 +4655,7 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" dependencies = [ - "bitflags 2.10.0", + "bitflags", "libc", "redox_syscall", ] @@ -5111,15 +4751,15 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a7ce8821eadcb5cb5c64dd0c9876a90f2676424020b41272e36c1dd04d20c59" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cfg-if", - "cssparser 0.35.0", + "cssparser", "encoding_rs", "hashbrown 0.16.1", "memchr", "mime", "precomputed-hash", - "selectors 0.32.0", + "selectors", "thiserror 2.0.17", ] @@ -5132,26 +4772,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "mac" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" - -[[package]] -name = "markup5ever" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" -dependencies = [ - "log", - "phf 0.10.1", - "phf_codegen 0.10.0", - "string_cache", - "string_cache_codegen", - "tendril", -] - [[package]] name = "matchers" version = "0.2.0" @@ -5161,12 +4781,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matches" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" - [[package]] name = "matchit" version = "0.8.4" @@ -5181,7 +4795,7 @@ checksum = "5cf92c10c7e361d6b99666ec1c6f9805b0bea2c3bd8c78dc6fe98ac5bd78db11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -5231,12 +4845,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -5255,7 +4863,7 @@ checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", "log", - "wasi 0.11.1+wasi-snapshot-preview1", + "wasi", "windows-sys 0.61.2", ] @@ -5313,7 +4921,7 @@ version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", @@ -5325,28 +4933,12 @@ version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cfg-if", "cfg_aliases", "libc", ] -[[package]] -name = "nodrop" -version = "0.1.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - [[package]] name = "normpath" version = "1.5.0" @@ -5374,16 +4966,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-bigint-dig" version = "0.8.6" @@ -5426,17 +5008,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -5472,7 +5043,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" dependencies = [ - "bitflags 2.10.0", + "bitflags", "objc2", "objc2-foundation", ] @@ -5493,7 +5064,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.10.0", + "bitflags", "dispatch2", "objc2", ] @@ -5504,7 +5075,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" dependencies = [ - "bitflags 2.10.0", + "bitflags", "dispatch2", "objc2", "objc2-core-foundation", @@ -5537,7 +5108,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ - "bitflags 2.10.0", + "bitflags", "objc2", "objc2-core-foundation", "objc2-core-graphics", @@ -5555,7 +5126,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" dependencies = [ - "bitflags 2.10.0", + "bitflags", "block2", "libc", "objc2", @@ -5578,7 +5149,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" dependencies = [ - "bitflags 2.10.0", + "bitflags", "objc2", "objc2-core-foundation", ] @@ -5589,7 +5160,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" dependencies = [ - "bitflags 2.10.0", + "bitflags", "objc2", "objc2-core-foundation", "objc2-foundation", @@ -5601,7 +5172,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" dependencies = [ - "bitflags 2.10.0", + "bitflags", "block2", "objc2", "objc2-cloud-kit", @@ -5653,7 +5224,7 @@ version = "6.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" dependencies = [ - "bitflags 2.10.0", + "bitflags", "libc", "once_cell", "onig_sys", @@ -5669,19 +5240,13 @@ dependencies = [ "pkg-config", ] -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - [[package]] name = "openssl" version = "0.10.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ - "bitflags 2.10.0", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -5698,7 +5263,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -5845,16 +5410,6 @@ dependencies = [ "sha2", ] -[[package]] -name = "page_size" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "parking" version = "2.2.1" @@ -5884,12 +5439,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "path-slash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -5905,33 +5454,13 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" -[[package]] -name = "phf" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" -dependencies = [ - "phf_macros 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", -] - -[[package]] -name = "phf" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" -dependencies = [ - "phf_shared 0.10.0", -] - [[package]] name = "phf" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_macros 0.11.3", + "phf_macros", "phf_shared 0.11.3", ] @@ -5945,26 +5474,6 @@ dependencies = [ "serde", ] -[[package]] -name = "phf_codegen" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", -] - -[[package]] -name = "phf_codegen" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" -dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", -] - [[package]] name = "phf_codegen" version = "0.11.3" @@ -5985,26 +5494,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_generator" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" -dependencies = [ - "phf_shared 0.8.0", - "rand 0.7.3", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" -dependencies = [ - "phf_shared 0.10.0", - "rand 0.8.5", -] - [[package]] name = "phf_generator" version = "0.11.3" @@ -6025,20 +5514,6 @@ dependencies = [ "phf_shared 0.13.1", ] -[[package]] -name = "phf_macros" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" -dependencies = [ - "phf_generator 0.8.0", - "phf_shared 0.8.0", - "proc-macro-hack", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "phf_macros" version = "0.11.3" @@ -6049,25 +5524,7 @@ dependencies = [ "phf_shared 0.11.3", "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "phf_shared" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" -dependencies = [ - "siphasher 0.3.11", -] - -[[package]] -name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher 0.3.11", + "syn", ] [[package]] @@ -6076,7 +5533,7 @@ version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ - "siphasher 1.0.1", + "siphasher", ] [[package]] @@ -6085,7 +5542,7 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" dependencies = [ - "siphasher 1.0.1", + "siphasher", ] [[package]] @@ -6105,7 +5562,7 @@ checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -6157,34 +5614,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - [[package]] name = "portable-atomic" version = "1.11.1" @@ -6230,22 +5659,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" version = "1.0.103" @@ -6291,10 +5704,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools 0.14.0", + "itertools", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -6312,20 +5725,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" -dependencies = [ - "getrandom 0.1.16", - "libc", - "rand_chacha 0.2.2", - "rand_core 0.5.1", - "rand_hc", - "rand_pcg", -] - [[package]] name = "rand" version = "0.8.5" @@ -6347,16 +5746,6 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "rand_chacha" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" -dependencies = [ - "ppv-lite86", - "rand_core 0.5.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -6377,15 +5766,6 @@ dependencies = [ "rand_core 0.9.3", ] -[[package]] -name = "rand_core" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" -dependencies = [ - "getrandom 0.1.16", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -6404,24 +5784,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_hc" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" -dependencies = [ - "rand_core 0.5.1", -] - -[[package]] -name = "rand_pcg" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" -dependencies = [ - "rand_core 0.5.1", -] - [[package]] name = "rayon" version = "1.11.0" @@ -6448,7 +5810,7 @@ version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.10.0", + "bitflags", ] [[package]] @@ -6468,7 +5830,7 @@ checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -6589,15 +5951,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "roxmltree" -version = "0.14.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "921904a62e410e37e215c40381b7117f830d9d89ba60ab5236170541dd25646b" -dependencies = [ - "xmlparser", -] - [[package]] name = "rsa" version = "0.9.9" @@ -6651,7 +6004,7 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" dependencies = [ - "bitflags 2.10.0", + "bitflags", "errno", "libc", "linux-raw-sys", @@ -6871,7 +6224,7 @@ version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation 0.9.4", "core-foundation-sys", "libc", @@ -6884,7 +6237,7 @@ version = "3.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation 0.10.1", "core-foundation-sys", "libc", @@ -6901,42 +6254,22 @@ dependencies = [ "libc", ] -[[package]] -name = "selectors" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" -dependencies = [ - "bitflags 1.3.2", - "cssparser 0.27.2", - "derive_more 0.99.20", - "fxhash", - "log", - "matches", - "phf 0.8.0", - "phf_codegen 0.8.0", - "precomputed-hash", - "servo_arc 0.1.1", - "smallvec", - "thin-slice", -] - [[package]] name = "selectors" version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09975d3195f34dce9c7b381cb0f00c3c13381d4d3735c0f1a9c894b283b302ab" dependencies = [ - "bitflags 2.10.0", - "cssparser 0.35.0", - "derive_more 2.0.1", + "bitflags", + "cssparser", + "derive_more", "log", "new_debug_unreachable", "phf 0.11.3", "phf_codegen 0.11.3", "precomputed-hash", "rustc-hash", - "servo_arc 0.4.3", + "servo_arc", "smallvec", ] @@ -7074,7 +6407,7 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2046f527fd4b75e0b6ab3bd656c67dce42072f828dc4d03c206d15dca74a93" dependencies = [ - "bitflags 2.10.0", + "bitflags", "sentry-backtrace", "sentry-core", "tracing-core", @@ -7098,12 +6431,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "separator" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f97841a747eef040fcd2e7b3b9a220a7205926e60488e673d9e4926d27772ce5" - [[package]] name = "serde" version = "1.0.228" @@ -7131,7 +6458,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -7140,8 +6467,7 @@ version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ - "indexmap 2.12.1", - "itoa 1.0.15", + "itoa", "memchr", "ryu", "serde", @@ -7154,7 +6480,7 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ - "itoa 1.0.15", + "itoa", "serde", "serde_core", ] @@ -7184,7 +6510,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.15", + "itoa", "ryu", "serde", ] @@ -7217,17 +6543,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "servo_arc" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" -dependencies = [ - "nodrop", - "stable_deref_trait", + "syn", ] [[package]] @@ -7333,12 +6649,6 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - [[package]] name = "siphasher" version = "1.0.1" @@ -7499,7 +6809,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.111", + "syn", ] [[package]] @@ -7522,7 +6832,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.111", + "syn", "tokio", "url", ] @@ -7535,7 +6845,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags", "byteorder", "bytes", "chrono", @@ -7551,7 +6861,7 @@ dependencies = [ "hex", "hkdf", "hmac", - "itoa 1.0.15", + "itoa", "log", "md-5", "memchr", @@ -7578,7 +6888,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" dependencies = [ "atoi", "base64 0.22.1", - "bitflags 2.10.0", + "bitflags", "byteorder", "chrono", "crc", @@ -7591,7 +6901,7 @@ dependencies = [ "hkdf", "hmac", "home", - "itoa 1.0.15", + "itoa", "log", "md-5", "memchr", @@ -7645,31 +6955,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "string_cache" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" -dependencies = [ - "new_debug_unreachable", - "parking_lot", - "phf_shared 0.11.3", - "precomputed-hash", - "serde", -] - -[[package]] -name = "string_cache_codegen" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" -dependencies = [ - "phf_generator 0.11.3", - "phf_shared 0.11.3", - "proc-macro2", - "quote", -] - [[package]] name = "stringprep" version = "0.1.5" @@ -7705,7 +6990,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -7714,17 +6999,6 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.111" @@ -7753,7 +7027,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -7795,7 +7069,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ - "bitflags 2.10.0", + "bitflags", "core-foundation 0.9.4", "system-configuration-sys", ] @@ -7834,17 +7108,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "tendril" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" -dependencies = [ - "futf", - "mac", - "utf-8", -] - [[package]] name = "test-case" version = "3.3.1" @@ -7863,7 +7126,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -7874,16 +7137,10 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "test-case-core", ] -[[package]] -name = "thin-slice" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" - [[package]] name = "thiserror" version = "1.0.69" @@ -7910,7 +7167,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -7921,7 +7178,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -7940,7 +7197,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", - "itoa 1.0.15", + "itoa", "num-conv", "powerfmt", "serde", @@ -7974,16 +7231,6 @@ dependencies = [ "zerovec", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "tinyvec" version = "1.10.0" @@ -8024,7 +7271,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -8223,7 +7470,7 @@ version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" dependencies = [ - "bitflags 2.10.0", + "bitflags", "bytes", "futures-core", "futures-util", @@ -8277,7 +7524,7 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -8336,7 +7583,6 @@ dependencies = [ "serde", "serde_json", "sharded-slab", - "smallvec", "thread_local", "tracing", "tracing-core", @@ -8568,12 +7814,6 @@ dependencies = [ "try-lock", ] -[[package]] -name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" - [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" @@ -8640,7 +7880,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn", "wasm-bindgen-shared", ] @@ -8780,7 +8020,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -8791,7 +8031,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -9149,12 +8389,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - [[package]] name = "yoke" version = "0.8.1" @@ -9174,7 +8408,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "synstructure", ] @@ -9195,7 +8429,7 @@ checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] @@ -9215,7 +8449,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", "synstructure", ] @@ -9255,7 +8489,7 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 86eb04308..6697b020c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,65 +51,65 @@ tracing = "0.1.37" url = { version = "2.1.1", features = ["serde"] } walkdir = "2" -[dependencies] -anyhow = { workspace = true } -async-stream = { workspace = true } -bincode = { workspace = true } -chrono = { workspace = true } -clap = { workspace = true } -derive_more = { workspace = true } -docs_rs_build_queue = { path = "crates/lib/docs_rs_build_queue" } -docs_rs_cargo_metadata = { path = "crates/lib/docs_rs_cargo_metadata" } -docs_rs_database = { path = "crates/lib/docs_rs_database" } -docs_rs_env_vars = { path = "crates/lib/docs_rs_env_vars" } -docs_rs_headers = { path = "crates/lib/docs_rs_headers" } -docs_rs_logging = { path = "crates/lib/docs_rs_logging" } -docs_rs_opentelemetry = { path = "crates/lib/docs_rs_opentelemetry" } -docs_rs_registry_api = { path = "crates/lib/docs_rs_registry_api" } -docs_rs_storage = { path = "crates/lib/docs_rs_storage" } -docs_rs_utils = { path = "crates/lib/docs_rs_utils" } -docs_rs_watcher = { path = "crates/bin/docs_rs_watcher" } -docs_rs_web_utils = { path = "crates/lib/docs_rs_web_utils" } -docsrs-metadata = { path = "crates/lib/metadata" } -fn-error-context = "0.2.0" -futures-util = { workspace = true } -http = { workspace = true } -itertools = { workspace = true } -mime = { workspace = true } -opentelemetry = { workspace = true } -opentelemetry-otlp = { workspace = true } -opentelemetry-resource-detectors = { workspace = true } -opentelemetry_sdk = { workspace = true } -path-slash = "0.2.0" -rayon = { workspace = true } -regex = { workspace = true } -reqwest = { workspace = true } -semver = { workspace = true } -sentry = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -serde_with = { workspace = true } -slug = { workspace = true } -sqlx = { workspace = true } -strum = { workspace = true } -tempfile = { workspace = true } -thiserror = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } -tracing-log = "0.2.0" -url = { workspace = true } -walkdir = { workspace = true } +# [dependencies] +# anyhow = { workspace = true } +# async-stream = { workspace = true } +# bincode = { workspace = true } +# chrono = { workspace = true } +# clap = { workspace = true } +# derive_more = { workspace = true } +# docs_rs_build_queue = { path = "crates/lib/docs_rs_build_queue" } +# docs_rs_cargo_metadata = { path = "crates/lib/docs_rs_cargo_metadata" } +# docs_rs_database = { path = "crates/lib/docs_rs_database" } +# docs_rs_env_vars = { path = "crates/lib/docs_rs_env_vars" } +# docs_rs_headers = { path = "crates/lib/docs_rs_headers" } +# docs_rs_logging = { path = "crates/lib/docs_rs_logging" } +# docs_rs_opentelemetry = { path = "crates/lib/docs_rs_opentelemetry" } +# docs_rs_registry_api = { path = "crates/lib/docs_rs_registry_api" } +# docs_rs_storage = { path = "crates/lib/docs_rs_storage" } +# docs_rs_utils = { path = "crates/lib/docs_rs_utils" } +# docs_rs_watcher = { path = "crates/bin/docs_rs_watcher" } +# docs_rs_web_utils = { path = "crates/lib/docs_rs_web_utils" } +# docsrs-metadata = { path = "crates/lib/metadata" } +# fn-error-context = "0.2.0" +# futures-util = { workspace = true } +# http = { workspace = true } +# itertools = { workspace = true } +# mime = { workspace = true } +# opentelemetry = { workspace = true } +# opentelemetry-otlp = { workspace = true } +# opentelemetry-resource-detectors = { workspace = true } +# opentelemetry_sdk = { workspace = true } +# path-slash = "0.2.0" +# rayon = { workspace = true } +# regex = { workspace = true } +# reqwest = { workspace = true } +# semver = { workspace = true } +# sentry = { workspace = true } +# serde = { workspace = true } +# serde_json = { workspace = true } +# serde_with = { workspace = true } +# slug = { workspace = true } +# sqlx = { workspace = true } +# strum = { workspace = true } +# tempfile = { workspace = true } +# thiserror = { workspace = true } +# tokio = { workspace = true } +# tracing = { workspace = true } +# tracing-log = "0.2.0" +# url = { workspace = true } +# walkdir = { workspace = true } -[dev-dependencies] -aws-smithy-runtime = {version = "1.0.1", features = ["client", "test-util"]} -aws-smithy-types = "1.0.1" -criterion = "0.8.0" -http-body-util = "0.1.0" -indoc = "2.0.0" -kuchikiki = "0.8" -mockito = { workspace = true } -opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio", "testing"] } -pretty_assertions = "1.4.0" -rand = "0.9" -test-case = { workspace = true } -tower = { version = "0.5.1", features = ["util"] } +# [dev-dependencies] +# aws-smithy-runtime = {version = "1.0.1", features = ["client", "test-util"]} +# aws-smithy-types = "1.0.1" +# criterion = "0.8.0" +# http-body-util = "0.1.0" +# indoc = "2.0.0" +# kuchikiki = "0.8" +# mockito = { workspace = true } +# opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio", "testing"] } +# pretty_assertions = "1.4.0" +# rand = "0.9" +# test-case = { workspace = true } +# tower = { version = "0.5.1", features = ["util"] } diff --git a/crates/bin/docs_rs_builder/tests/regex/body.html b/crates/lib/docs_rs_storage/tests/regex/body.html similarity index 100% rename from crates/bin/docs_rs_builder/tests/regex/body.html rename to crates/lib/docs_rs_storage/tests/regex/body.html diff --git a/crates/bin/docs_rs_builder/tests/regex/head.html b/crates/lib/docs_rs_storage/tests/regex/head.html similarity index 100% rename from crates/bin/docs_rs_builder/tests/regex/head.html rename to crates/lib/docs_rs_storage/tests/regex/head.html From 8f61ad749156aea99eb171b0203257bcb00fcd97 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 12:29:19 +0100 Subject: [PATCH 29/46] save --- Cargo.toml | 63 ------------------------------------------------------ src/lib.rs | 1 + 2 files changed, 1 insertion(+), 63 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6697b020c..fc7562b74 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,66 +50,3 @@ toml = "0.9.2" tracing = "0.1.37" url = { version = "2.1.1", features = ["serde"] } walkdir = "2" - -# [dependencies] -# anyhow = { workspace = true } -# async-stream = { workspace = true } -# bincode = { workspace = true } -# chrono = { workspace = true } -# clap = { workspace = true } -# derive_more = { workspace = true } -# docs_rs_build_queue = { path = "crates/lib/docs_rs_build_queue" } -# docs_rs_cargo_metadata = { path = "crates/lib/docs_rs_cargo_metadata" } -# docs_rs_database = { path = "crates/lib/docs_rs_database" } -# docs_rs_env_vars = { path = "crates/lib/docs_rs_env_vars" } -# docs_rs_headers = { path = "crates/lib/docs_rs_headers" } -# docs_rs_logging = { path = "crates/lib/docs_rs_logging" } -# docs_rs_opentelemetry = { path = "crates/lib/docs_rs_opentelemetry" } -# docs_rs_registry_api = { path = "crates/lib/docs_rs_registry_api" } -# docs_rs_storage = { path = "crates/lib/docs_rs_storage" } -# docs_rs_utils = { path = "crates/lib/docs_rs_utils" } -# docs_rs_watcher = { path = "crates/bin/docs_rs_watcher" } -# docs_rs_web_utils = { path = "crates/lib/docs_rs_web_utils" } -# docsrs-metadata = { path = "crates/lib/metadata" } -# fn-error-context = "0.2.0" -# futures-util = { workspace = true } -# http = { workspace = true } -# itertools = { workspace = true } -# mime = { workspace = true } -# opentelemetry = { workspace = true } -# opentelemetry-otlp = { workspace = true } -# opentelemetry-resource-detectors = { workspace = true } -# opentelemetry_sdk = { workspace = true } -# path-slash = "0.2.0" -# rayon = { workspace = true } -# regex = { workspace = true } -# reqwest = { workspace = true } -# semver = { workspace = true } -# sentry = { workspace = true } -# serde = { workspace = true } -# serde_json = { workspace = true } -# serde_with = { workspace = true } -# slug = { workspace = true } -# sqlx = { workspace = true } -# strum = { workspace = true } -# tempfile = { workspace = true } -# thiserror = { workspace = true } -# tokio = { workspace = true } -# tracing = { workspace = true } -# tracing-log = "0.2.0" -# url = { workspace = true } -# walkdir = { workspace = true } - -# [dev-dependencies] -# aws-smithy-runtime = {version = "1.0.1", features = ["client", "test-util"]} -# aws-smithy-types = "1.0.1" -# criterion = "0.8.0" -# http-body-util = "0.1.0" -# indoc = "2.0.0" -# kuchikiki = "0.8" -# mockito = { workspace = true } -# opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio", "testing"] } -# pretty_assertions = "1.4.0" -# rand = "0.9" -# test-case = { workspace = true } -# tower = { version = "0.5.1", features = ["util"] } diff --git a/src/lib.rs b/src/lib.rs index e69de29bb..8b1378917 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -0,0 +1 @@ + From e0e65f435f5940546b72248ebff1f963e3ebae90 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 12:42:42 +0100 Subject: [PATCH 30/46] save --- Cargo.lock | 191 +- crates/bin/docs_rs_builder/Cargo.toml | 1 - .../src/docbuilder/rustwide_builder.rs | 1756 ++++++++--------- crates/bin/docs_rs_builder/src/metrics.rs | 2 +- .../src/utils/queue_builder.rs | 6 +- crates/bin/docs_rs_watcher/Cargo.toml | 7 - crates/bin/docs_rs_watcher/src/build_queue.rs | 10 +- .../docs_rs_watcher/src/consistency/mod.rs | 5 +- crates/bin/docs_rs_watcher/src/db/delete.rs | 2 +- crates/bin/docs_rs_watcher/src/main.rs | 2 +- crates/bin/docs_rs_web/Cargo.toml | 7 +- crates/bin/docs_rs_web/src/build_details.rs | 4 +- crates/bin/docs_rs_web/src/config.rs | 1 - crates/bin/docs_rs_web/src/lib.rs | 2 +- crates/bin/docs_rs_web/src/releases.rs | 2 +- crates/bin/docs_rs_web/src/rustdoc.rs | 2 - crates/bin/docs_rs_web/src/sitemap.rs | 1 - crates/lib/docs_rs_build_utils/Cargo.toml | 7 +- crates/lib/docs_rs_build_utils/src/limits.rs | 5 +- .../lib/docs_rs_build_utils/src/overrides.rs | 108 +- crates/lib/docs_rs_cargo_metadata/src/lib.rs | 4 +- crates/lib/docs_rs_database/Cargo.toml | 1 - crates/lib/docs_rs_database/src/types/mod.rs | 32 +- crates/lib/docs_rs_headers/Cargo.toml | 1 - .../lib/docs_rs_headers/src/surrogate_key.rs | 148 +- crates/lib/docs_rs_registry_api/Cargo.toml | 1 - crates/lib/docs_rs_storage/Cargo.toml | 2 + .../docs_rs_storage/benches/compression.rs | 2 +- crates/lib/docs_rs_storage/src/lib.rs | 2 +- .../docs_rs_storage/src/utils/sized_buffer.rs | 58 +- 30 files changed, 1251 insertions(+), 1121 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6eb98e66d..9b78251b5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,6 +196,15 @@ dependencies = [ "as-slice", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "allocator-api2" version = "0.2.21" @@ -211,6 +220,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" version = "0.6.21" @@ -1099,6 +1114,12 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.48" @@ -1135,6 +1156,33 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.53" @@ -1377,6 +1425,41 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" +dependencies = [ + "alloca", + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools 0.13.0", + "num-traits", + "oorandom", + "page_size", + "plotters", + "rayon", + "regex", + "serde", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" +dependencies = [ + "cast", + "itertools 0.13.0", +] + [[package]] name = "crossbeam-channel" version = "0.5.15" @@ -1420,6 +1503,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.4.9" @@ -1701,7 +1790,6 @@ name = "docs_rs_build_utils" version = "0.1.0" dependencies = [ "anyhow", - "docs_rs_database", "docs_rs_env_vars", "docs_rs_utils", "futures-util", @@ -1728,7 +1816,7 @@ dependencies = [ "docsrs-metadata", "futures-util", "hostname", - "itertools", + "itertools 0.14.0", "log", "opentelemetry", "regex", @@ -1743,7 +1831,6 @@ dependencies = [ "tokio", "toml 0.9.8", "tracing", - "url", ] [[package]] @@ -1782,7 +1869,6 @@ dependencies = [ "docs_rs_env_vars", "docs_rs_opentelemetry", "futures-util", - "hex", "mime", "mime_guess", "opentelemetry", @@ -1817,7 +1903,7 @@ dependencies = [ "docs_rs_opentelemetry", "docs_rs_utils", "http 1.4.0", - "itertools", + "itertools 0.14.0", "opentelemetry", "reqwest", "tracing", @@ -1832,11 +1918,10 @@ dependencies = [ "askama", "derive_more", "docs_rs_database", - "docs_rs_utils", "docs_rs_web_utils", "headers", "http 1.4.0", - "itertools", + "itertools 0.14.0", "md5", "serde", ] @@ -1871,7 +1956,6 @@ name = "docs_rs_registry_api" version = "0.1.0" dependencies = [ "anyhow", - "async-trait", "bincode 2.0.1", "chrono", "docs_rs_database", @@ -1917,6 +2001,7 @@ dependencies = [ "aws-smithy-types-convert", "bzip2", "chrono", + "criterion", "dashmap", "docs_rs_database", "docs_rs_env_vars", @@ -1926,7 +2011,7 @@ dependencies = [ "flate2", "futures-util", "http 1.4.0", - "itertools", + "itertools 0.14.0", "mime", "opentelemetry", "serde", @@ -1960,12 +2045,10 @@ name = "docs_rs_watcher" version = "0.1.0" dependencies = [ "anyhow", - "chrono", "clap", "crates-index", "crates-index-diff", "docs_rs_build_queue", - "docs_rs_cargo_metadata", "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", @@ -1976,16 +2059,11 @@ dependencies = [ "docs_rs_storage", "docs_rs_utils", "futures-util", - "itertools", + "itertools 0.14.0", "mockito", "opentelemetry", "rayon", - "regex", - "reqwest", - "serde", - "serde_json", "sqlx", - "thiserror 2.0.17", "tokio", "tracing", "url", @@ -2023,13 +2101,12 @@ dependencies = [ "getrandom 0.3.4", "grass", "http 1.4.0", - "itertools", + "itertools 0.14.0", "lol_html", "md5", "mime", "num_cpus", "opentelemetry", - "opentelemetry_sdk", "phf 0.13.1", "phf_codegen 0.13.1", "rayon", @@ -3953,6 +4030,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hash32" version = "0.3.1" @@ -4502,6 +4590,15 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itertools" version = "0.14.0" @@ -5240,6 +5337,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + [[package]] name = "openssl" version = "0.10.75" @@ -5410,6 +5513,16 @@ dependencies = [ "sha2", ] +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "parking" version = "2.2.1" @@ -5614,6 +5727,34 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" +[[package]] +name = "plotters" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" + +[[package]] +name = "plotters-svg" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" +dependencies = [ + "plotters-backend", +] + [[package]] name = "portable-atomic" version = "1.11.1" @@ -5704,7 +5845,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" dependencies = [ "anyhow", - "itertools", + "itertools 0.14.0", "proc-macro2", "quote", "syn", @@ -7231,6 +7372,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.10.0" diff --git a/crates/bin/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml index 1d45c707f..c4a9169ad 100644 --- a/crates/bin/docs_rs_builder/Cargo.toml +++ b/crates/bin/docs_rs_builder/Cargo.toml @@ -33,4 +33,3 @@ thiserror = { workspace = true } tokio = { workspace = true } toml = { workspace = true } tracing = { workspace = true } -url = { workspace = true } diff --git a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs index 04a8bae93..2829b4543 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -1452,881 +1452,881 @@ impl Default for BuildPackageSummary { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::db::types::Feature; - use crate::registry_api::ReleaseData; - use crate::storage::{CompressionAlgorithm, compression}; - use crate::test::{AxumRouterTestExt, TestEnvironment}; - use pretty_assertions::assert_eq; - use std::{io, iter}; - use test_case::test_case; - - fn get_features( - env: &TestEnvironment, - name: &str, - version: &Version, - ) -> Result>, sqlx::Error> { - env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - sqlx::query_scalar!( - r#"SELECT - releases.features "features?: Vec" - FROM releases - INNER JOIN crates ON crates.id = releases.crate_id - WHERE crates.name = $1 AND releases.version = $2"#, - name, - version as _, - ) - .fetch_one(&mut *conn) - .await - }) - } - - fn remove_cache_files(env: &TestEnvironment, crate_: &str, version: &Version) -> Result<()> { - let paths = [ - format!("cache/index.crates.io-6f17d22bba15001f/{crate_}-{version}.crate"), - format!("src/index.crates.io-6f17d22bba15001f/{crate_}-{version}"), - format!( - "index/index.crates.io-6f17d22bba15001f/.cache/{}/{}/{crate_}", - &crate_[0..2], - &crate_[2..4] - ), - ]; - - for path in paths { - let full_path = env - .config() - .rustwide_workspace - .join("cargo-home/registry") - .join(path); - if full_path.exists() { - info!("deleting {}", full_path.display()); - if full_path.is_file() { - std::fs::remove_file(full_path)?; - } else { - std::fs::remove_dir_all(full_path)?; - } - } - } - - Ok(()) - } - - #[test] - #[ignore] - fn test_build_crate() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let crate_ = DUMMY_CRATE_NAME; - let crate_path = crate_.replace('-', "_"); - let version = DUMMY_CRATE_VERSION; - let default_target = "x86_64-unknown-linux-gnu"; - - let storage = env.storage(); - let old_rustdoc_file = format!("rustdoc/{crate_}/{version}/some_doc_file"); - let old_source_file = format!("sources/{crate_}/{version}/some_source_file"); - storage.store_one(&old_rustdoc_file, Vec::new())?; - storage.store_one(&old_source_file, Vec::new())?; - - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - // check release record in the db (default and other targets) - let row = env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - r#"SELECT - r.rustdoc_status, - r.default_target, - r.doc_targets, - r.archive_storage, - r.source_size as "source_size!", - cov.total_items, - b.id as build_id, - b.build_status::TEXT as build_status, - b.docsrs_version, - b.rustc_version, - b.documentation_size - FROM - crates as c - INNER JOIN releases AS r ON c.id = r.crate_id - INNER JOIN builds as b ON r.id = b.rid - LEFT OUTER JOIN doc_coverage AS cov ON r.id = cov.release_id - WHERE - c.name = $1 AND - r.version = $2"#, - crate_, - version as _, - ) - .fetch_one(&mut *conn) - .await - })?; - - assert_eq!(row.rustdoc_status, Some(true)); - assert_eq!(row.default_target, Some(default_target.into())); - assert!(row.total_items.is_some()); - assert!(row.archive_storage); - assert!(!row.docsrs_version.unwrap().is_empty()); - assert!(!row.rustc_version.unwrap().is_empty()); - assert_eq!(row.build_status.unwrap(), "success"); - assert!(row.source_size > 0); - assert!(row.documentation_size.unwrap() > 0); - - let mut targets: Vec = row - .doc_targets - .unwrap() - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_owned()) - .collect(); - targets.sort(); - - let runtime = env.runtime(); - let web = runtime.block_on(env.web_app()); - - // old rustdoc & source files are gone - assert!(!storage.exists(&old_rustdoc_file)?); - assert!(!storage.exists(&old_source_file)?); - - // doc archive exists - let doc_archive = rustdoc_archive_path(crate_, &version); - assert!(storage.exists(&doc_archive)?, "{}", doc_archive); - - // source archive exists - let source_archive = source_archive_path(crate_, &version); - assert!(storage.exists(&source_archive)?, "{}", source_archive); - - // default target was built and is accessible - assert!(storage.exists_in_archive( - &doc_archive, - None, - &format!("{crate_path}/index.html"), - )?); - runtime.block_on(web.assert_success(&format!("/{crate_}/{version}/{crate_path}/")))?; - - // source is also packaged - assert!(storage.exists_in_archive(&source_archive, None, "src/lib.rs",)?); - runtime.block_on( - web.assert_success(&format!("/crate/{crate_}/{version}/source/src/lib.rs")), - )?; - assert!(!storage.exists_in_archive( - &doc_archive, - None, - &format!("{default_target}/{crate_path}/index.html"), - )?); - - let default_target_url = format!("/{crate_}/{version}/{default_target}/{crate_path}/"); - runtime.block_on(web.assert_redirect( - &default_target_url, - &format!("/{crate_}/{version}/{crate_path}/"), - ))?; - - // Non-dist toolchains only have a single target, and of course - // if include_default_targets is false we won't have this full list - // of targets. - if builder.toolchain.as_dist().is_some() && env.config().include_default_targets { - assert_eq!( - targets, - vec![ - "aarch64-apple-darwin", - "aarch64-unknown-linux-gnu", - "i686-pc-windows-msvc", - "x86_64-pc-windows-msvc", - "x86_64-unknown-linux-gnu", - ] - ); - - // other targets too - for target in DEFAULT_TARGETS { - for alg in RUSTDOC_JSON_COMPRESSION_ALGORITHMS { - // check if rustdoc json files exist for all targets - let path = rustdoc_json_path( - crate_, - &version, - target, - RustdocJsonFormatVersion::Latest, - Some(*alg), - ); - assert!(storage.exists(&path)?); - - let ext = compression::file_extension_for(*alg); - - let json_prefix = format!("rustdoc-json/{crate_}/{version}/{target}/"); - let mut json_files: Vec<_> = storage - .list_prefix(&json_prefix) - .filter_map(|res| res.ok()) - .map(|f| f.strip_prefix(&json_prefix).unwrap().to_owned()) - .collect(); - json_files.retain(|f| f.ends_with(&format!(".json.{ext}"))); - json_files.sort(); - dbg!(&json_files); - assert!(json_files[0].starts_with(&format!("empty-library_1.0.0_{target}_"))); - assert!(json_files[0].ends_with(&format!(".json.{ext}"))); - assert_eq!( - json_files[1], - format!("empty-library_1.0.0_{target}_latest.json.{ext}") - ); - } - - if target == &default_target { - continue; - } - let target_docs_present = storage.exists_in_archive( - &doc_archive, - None, - &format!("{target}/{crate_path}/index.html"), - )?; - - let target_url = format!("/{crate_}/{version}/{target}/{crate_path}/index.html"); - - assert!(target_docs_present); - runtime.block_on(web.assert_success(&target_url))?; - - assert!( - storage - .exists(&format!("build-logs/{}/{target}.txt", row.build_id)) - .unwrap() - ); - } - } - Ok(()) - } - - #[test] - #[ignore] - fn test_collect_metrics() -> Result<()> { - let metrics_dir = tempfile::tempdir().unwrap().keep(); - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .compiler_metrics_collection_path(Some(metrics_dir.clone())) - .include_default_targets(false) - .build()?, - )?; - - let crate_ = DUMMY_CRATE_NAME; - let version = DUMMY_CRATE_VERSION; - - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_package(crate_, &version, PackageKind::CratesIo, true)? - .successful - ); - - let metric_files: Vec<_> = fs::read_dir(&metrics_dir)? - .filter_map(|di| di.ok()) - .map(|di| di.path()) - .collect(); - - assert_eq!(metric_files.len(), 1); - - let _: serde_json::Value = serde_json::from_slice(&fs::read(&metric_files[0])?)?; - - Ok(()) - } - - #[test] - #[ignore] - fn test_build_binary_crate() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - // some binary crate - let crate_ = "heater"; - let version = Version::new(0, 2, 3); - - let storage = env.storage(); - let old_rustdoc_file = format!("rustdoc/{crate_}/{version}/some_doc_file"); - let old_source_file = format!("sources/{crate_}/{version}/some_source_file"); - storage.store_one(&old_rustdoc_file, Vec::new())?; - storage.store_one(&old_source_file, Vec::new())?; - - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - !builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - // check release record in the db (default and other targets) - let row = env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - "SELECT - r.rustdoc_status, - r.is_library - FROM - crates as c - INNER JOIN releases AS r ON c.id = r.crate_id - LEFT OUTER JOIN doc_coverage AS cov ON r.id = cov.release_id - WHERE - c.name = $1 AND - r.version = $2", - crate_, - version as _ - ) - .fetch_one(&mut *conn) - .await - })?; - - assert_eq!(row.rustdoc_status, Some(false)); - assert_eq!(row.is_library, Some(false)); - - // doc archive exists - let doc_archive = rustdoc_archive_path(crate_, &version); - assert!(!storage.exists(&doc_archive)?); - - // source archive exists - let source_archive = source_archive_path(crate_, &version); - assert!(storage.exists(&source_archive)?); - - // old rustdoc & source files still exist - assert!(storage.exists(&old_rustdoc_file)?); - assert!(storage.exists(&old_source_file)?); - - Ok(()) - } - - #[test] - #[ignore] - fn test_failed_build_with_existing_successful_release() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - // rand 0.8.5 fails to build with recent nightly versions - // https://github.com/rust-lang/docs.rs/issues/26750 - let crate_ = "rand"; - let version = Version::new(0, 8, 5); - - // create a successful release & build in the database - let release_id = env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - let crate_id = initialize_crate(&mut conn, crate_).await?; - let release_id = initialize_release(&mut conn, crate_id, &version).await?; - let build_id = initialize_build(&mut conn, release_id).await?; - finish_build( - &mut conn, - build_id, - "some-version", - "other-version", - BuildStatus::Success, - None, - None, - ) - .await?; - finish_release( - &mut conn, - crate_id, - release_id, - &MetadataPackage { - name: crate_.into(), - version: version.clone(), - id: "".into(), - license: None, - repository: None, - homepage: None, - description: None, - documentation: None, - dependencies: vec![], - targets: vec![], - readme: None, - keywords: vec![], - features: HashMap::new(), - }, - Path::new("/unknown/"), - "x86_64-unknown-linux-gnu", - serde_json::Value::Array(vec![]), - vec![ - "i686-pc-windows-msvc".into(), - "aarch64-unknown-linux-gnu".into(), - "aarch64-apple-darwin".into(), - "x86_64-pc-windows-msvc".into(), - "x86_64-unknown-linux-gnu".into(), - ], - &ReleaseData::default(), - true, - false, - iter::once(CompressionAlgorithm::Bzip2), - None, - true, - 42, - ) - .await?; - - Ok::<_, anyhow::Error>(release_id) - })?; - - fn check_rustdoc_status(env: &TestEnvironment, rid: ReleaseId) -> Result<()> { - assert_eq!( - env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - sqlx::query_scalar!("SELECT rustdoc_status FROM releases WHERE id = $1", rid.0) - .fetch_one(&mut *conn) - .await - })?, - Some(true) - ); - Ok(()) - } - - check_rustdoc_status(&env, release_id)?; - - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - // not successful build - !builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - check_rustdoc_status(&env, release_id)?; - Ok(()) - } - - #[test_case("scsys-macros", Version::new(0, 2, 6))] - #[test_case("scsys-derive", Version::new(0, 2, 6))] - #[test_case("thiserror-impl", Version::new(1, 0, 26))] - #[ignore] - fn test_proc_macro(crate_: &str, version: Version) -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - let storage = env.storage(); - - // doc archive exists - let doc_archive = rustdoc_archive_path(crate_, &version); - assert!(storage.exists(&doc_archive)?); - - // source archive exists - let source_archive = source_archive_path(crate_, &version); - assert!(storage.exists(&source_archive)?); - - Ok(()) - } - - #[test] - #[ignore] - fn test_cross_compile_non_host_default() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let crate_ = "windows-win"; - let version = Version::new(2, 4, 1); - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - if builder.toolchain.as_ci().is_some() { - return Ok(()); - } - assert!( - builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - let storage = env.storage(); - - // doc archive exists - let doc_archive = rustdoc_archive_path(crate_, &version); - assert!(storage.exists(&doc_archive)?, "{}", doc_archive); - - // source archive exists - let source_archive = source_archive_path(crate_, &version); - assert!(storage.exists(&source_archive)?, "{}", source_archive); - - let target = "x86_64-unknown-linux-gnu"; - let crate_path = crate_.replace('-', "_"); - let target_docs_present = storage.exists_in_archive( - &doc_archive, - None, - &format!("{target}/{crate_path}/index.html"), - )?; - assert!(target_docs_present); - - env.runtime().block_on(async { - let web = env.web_app().await; - let target_url = format!("/{crate_}/{version}/{target}/{crate_path}/index.html"); - - web.assert_success(&target_url).await - })?; - - Ok(()) - } - - #[test] - #[ignore] - fn test_locked_fails_unlocked_needs_new_deps() -> Result<()> { - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .include_default_targets(false) - .build()?, - )?; - - // if the corrected dependency of the crate was already downloaded we need to remove it - remove_cache_files(&env, "rand_core", &Version::new(0, 5, 1))?; - - // Specific setup required: - // * crate has a binary so that it is published with a lockfile - // * crate has a library so that it is documented by docs.rs - // * crate has an optional dependency - // * metadata enables the optional dependency for docs.rs - // * `cargo doc` fails with the version of the dependency in the lockfile - // * there is a newer version of the dependency available that correctly builds - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_local_package(Path::new("tests/crates/incorrect_lockfile_0_1"))? - .successful - ); - - Ok(()) - } - - #[test] - #[ignore] - fn test_locked_fails_unlocked_needs_new_unknown_deps() -> Result<()> { - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .include_default_targets(false) - .build()?, - )?; - - // if the corrected dependency of the crate was already downloaded we need to remove it - remove_cache_files(&env, "value-bag-sval2", &Version::new(1, 4, 1))?; - - // Similar to above, this crate fails to build with the published - // lockfile, but generating a new working lockfile requires - // introducing a completely new dependency (not just version) which - // would not have had its details pulled down from the sparse-index. - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_local_package(Path::new("tests/crates/incorrect_lockfile_0_2"))? - .successful - ); - - Ok(()) - } - - #[test] - #[ignore] - fn test_rustflags_are_passed_to_build_script() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let crate_ = "proc-macro2"; - let version = Version::new(1, 0, 95); - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - Ok(()) - } - - #[test] - #[ignore] - fn test_sources_are_added_even_for_build_failures_before_build() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - // https://github.com/rust-lang/docs.rs/issues/2523 - // package with invalid cargo metadata. - // Will succeed in the crate fetch step, so sources are - // added. Will fail when we try to build. - let crate_ = "simconnect-sys"; - let version = Version::new(0, 23, 1); - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - - // `Result` is `Ok`, but the build-result is `false` - assert!( - !builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - // source archive exists - let source_archive = source_archive_path(crate_, &version); - assert!( - env.storage().exists(&source_archive)?, - "archive doesnt exist: {source_archive}" - ); - - Ok(()) - } - - #[test] - #[ignore] - fn test_build_failures_before_build() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - // https://github.com/rust-lang/docs.rs/issues/2491 - // package without Cargo.toml, so fails directly in the fetch stage. - let crate_ = "emheap"; - let version = Version::new(0, 1, 0); - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - - // `Result` is `Ok`, but the build-result is `false` - let summary = builder.build_package(crate_, &version, PackageKind::CratesIo, false)?; - - assert!(!summary.successful); - assert!(summary.should_reattempt); - - let row = env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - r#"SELECT - rustc_version, - docsrs_version, - build_status as "build_status: BuildStatus", - errors - FROM - crates as c - INNER JOIN releases as r on c.id = r.crate_id - INNER JOIN builds as b on b.rid = r.id - WHERE c.name = $1 and r.version = $2"#, - crate_, - version as _, - ) - .fetch_one(&mut *conn) - .await - })?; - - assert!(row.rustc_version.is_none()); - assert!(row.docsrs_version.is_none()); - assert_eq!(row.build_status, BuildStatus::Failure); - assert!(row.errors.unwrap().contains("missing Cargo.toml")); - - Ok(()) - } - - #[test] - #[ignore] - fn test_implicit_features_for_optional_dependencies() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let crate_ = "serde"; - let version = Version::new(1, 0, 152); - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_package(crate_, &version, PackageKind::CratesIo, false)? - .successful - ); - - assert!( - get_features(&env, crate_, &version)? - .unwrap() - .iter() - .any(|f| f.name == "serde_derive") - ); - - Ok(()) - } - - #[test] - #[ignore] - fn test_no_implicit_features_for_optional_dependencies_with_dep_syntax() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let mut builder = RustwideBuilder::init(&env.context).unwrap(); - builder.update_toolchain()?; - assert!( - builder - .build_local_package(Path::new("tests/crates/optional-dep"))? - .successful - ); - - assert_eq!( - get_features(&env, "optional-dep", &Version::new(0, 0, 1))? - .unwrap() - .iter() - .map(|f| f.name.to_owned()) - .sorted() - .collect_vec(), - // "regex" feature is not in the list, - // because we don't have implicit features for optional dependencies - // with `dep` syntax any more. - vec!["alloc", "default", "optional_regex", "std"] - ); - - Ok(()) - } - - #[test] - #[ignore] - fn test_build_std() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let mut builder = RustwideBuilder::init(&env.context)?; - builder.update_toolchain()?; - assert!( - builder - .build_local_package(Path::new("tests/crates/build-std"))? - .successful - ); - Ok(()) - } - - #[test] - #[ignore] - fn test_workspace_reinitialize_at_once() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let mut builder = RustwideBuilder::init(&env.context)?; - builder.update_toolchain()?; - builder.reinitialize_workspace_if_interval_passed(&env.context)?; - assert!( - builder - .build_local_package(Path::new("tests/crates/build-std"))? - .successful - ); - Ok(()) - } - - #[test] - #[ignore] - fn test_workspace_reinitialize_after_interval() -> Result<()> { - let env = TestEnvironment::with_config_and_runtime( - TestEnvironment::base_config() - .build_workspace_reinitialization_interval(Duration::from_secs(1)) - .build()?, - )?; - - use std::thread::sleep; - use std::time::Duration; - - let mut builder = RustwideBuilder::init(&env.context)?; - builder.update_toolchain()?; - assert!( - builder - .build_local_package(Path::new("tests/crates/build-std"))? - .successful - ); - sleep(Duration::from_secs(1)); - builder.reinitialize_workspace_if_interval_passed(&env.context)?; - assert!( - builder - .build_local_package(Path::new("tests/crates/build-std"))? - .successful - ); - Ok(()) - } - - #[test] - #[ignore] - fn test_new_builder_detects_existing_rustc() -> Result<()> { - let env = TestEnvironment::new_with_runtime()?; - - let mut builder = RustwideBuilder::init(&env.context)?; - builder.update_toolchain()?; - let old_version = builder.rustc_version()?; - drop(builder); - - // new builder should detect the existing rustc version from the previous builder - // (simulating running `update-toolchain` and `build crate` in separate invocations) - let mut builder = RustwideBuilder::init(&env.context)?; - assert!( - builder - .build_package( - DUMMY_CRATE_NAME, - &DUMMY_CRATE_VERSION, - PackageKind::CratesIo, - false - )? - .successful - ); - assert_eq!(old_version, builder.rustc_version()?); - - Ok(()) - } - - #[test] - fn test_read_format_version_from_rustdoc_json() -> Result<()> { - let buf = serde_json::to_vec(&serde_json::json!({ - "something": "else", - "format_version": 42 - }))?; - - assert_eq!( - read_format_version_from_rustdoc_json(&mut io::Cursor::new(buf))?, - RustdocJsonFormatVersion::Version(42) - ); - - Ok(()) - } - - #[test] - #[ignore] - fn test_additional_targets() -> Result<()> { - fn assert_contains(targets: &[String], target: &str) { - assert!( - targets.iter().any(|t| t == target), - "Not found target {target:?} in {targets:?}" - ); - } - - let env = TestEnvironment::new_with_runtime()?; - - let mut builder = RustwideBuilder::init(&env.context)?; - builder.update_toolchain()?; - - assert!( - builder - .build_local_package(Path::new("tests/crates/additional-targets"))? - .successful - ); - - let row = env.runtime().block_on(async { - let mut conn = env.async_db().async_conn().await; - sqlx::query!( - r#"SELECT - r.doc_targets - FROM - crates as c - INNER JOIN releases AS r ON c.id = r.crate_id - WHERE - c.name = $1 AND - r.version = $2"#, - "additional-targets", - "0.1.0", - ) - .fetch_one(&mut *conn) - .await - })?; - - let targets: Vec = row - .doc_targets - .unwrap() - .as_array() - .unwrap() - .iter() - .map(|v| v.as_str().unwrap().to_owned()) - .collect(); - - assert_contains(&targets, "x86_64-apple-darwin"); - // Part of the default targets. - assert_contains(&targets, "aarch64-apple-darwin"); - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::db::types::Feature; +// use crate::registry_api::ReleaseData; +// use crate::storage::{CompressionAlgorithm, compression}; +// use crate::test::{AxumRouterTestExt, TestEnvironment}; +// use pretty_assertions::assert_eq; +// use std::{io, iter}; +// use test_case::test_case; + +// fn get_features( +// env: &TestEnvironment, +// name: &str, +// version: &Version, +// ) -> Result>, sqlx::Error> { +// env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query_scalar!( +// r#"SELECT +// releases.features "features?: Vec" +// FROM releases +// INNER JOIN crates ON crates.id = releases.crate_id +// WHERE crates.name = $1 AND releases.version = $2"#, +// name, +// version as _, +// ) +// .fetch_one(&mut *conn) +// .await +// }) +// } + +// fn remove_cache_files(env: &TestEnvironment, crate_: &str, version: &Version) -> Result<()> { +// let paths = [ +// format!("cache/index.crates.io-6f17d22bba15001f/{crate_}-{version}.crate"), +// format!("src/index.crates.io-6f17d22bba15001f/{crate_}-{version}"), +// format!( +// "index/index.crates.io-6f17d22bba15001f/.cache/{}/{}/{crate_}", +// &crate_[0..2], +// &crate_[2..4] +// ), +// ]; + +// for path in paths { +// let full_path = env +// .config() +// .rustwide_workspace +// .join("cargo-home/registry") +// .join(path); +// if full_path.exists() { +// info!("deleting {}", full_path.display()); +// if full_path.is_file() { +// std::fs::remove_file(full_path)?; +// } else { +// std::fs::remove_dir_all(full_path)?; +// } +// } +// } + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_build_crate() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let crate_ = DUMMY_CRATE_NAME; +// let crate_path = crate_.replace('-', "_"); +// let version = DUMMY_CRATE_VERSION; +// let default_target = "x86_64-unknown-linux-gnu"; + +// let storage = env.storage(); +// let old_rustdoc_file = format!("rustdoc/{crate_}/{version}/some_doc_file"); +// let old_source_file = format!("sources/{crate_}/{version}/some_source_file"); +// storage.store_one(&old_rustdoc_file, Vec::new())?; +// storage.store_one(&old_source_file, Vec::new())?; + +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// // check release record in the db (default and other targets) +// let row = env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// r#"SELECT +// r.rustdoc_status, +// r.default_target, +// r.doc_targets, +// r.archive_storage, +// r.source_size as "source_size!", +// cov.total_items, +// b.id as build_id, +// b.build_status::TEXT as build_status, +// b.docsrs_version, +// b.rustc_version, +// b.documentation_size +// FROM +// crates as c +// INNER JOIN releases AS r ON c.id = r.crate_id +// INNER JOIN builds as b ON r.id = b.rid +// LEFT OUTER JOIN doc_coverage AS cov ON r.id = cov.release_id +// WHERE +// c.name = $1 AND +// r.version = $2"#, +// crate_, +// version as _, +// ) +// .fetch_one(&mut *conn) +// .await +// })?; + +// assert_eq!(row.rustdoc_status, Some(true)); +// assert_eq!(row.default_target, Some(default_target.into())); +// assert!(row.total_items.is_some()); +// assert!(row.archive_storage); +// assert!(!row.docsrs_version.unwrap().is_empty()); +// assert!(!row.rustc_version.unwrap().is_empty()); +// assert_eq!(row.build_status.unwrap(), "success"); +// assert!(row.source_size > 0); +// assert!(row.documentation_size.unwrap() > 0); + +// let mut targets: Vec = row +// .doc_targets +// .unwrap() +// .as_array() +// .unwrap() +// .iter() +// .map(|v| v.as_str().unwrap().to_owned()) +// .collect(); +// targets.sort(); + +// let runtime = env.runtime(); +// let web = runtime.block_on(env.web_app()); + +// // old rustdoc & source files are gone +// assert!(!storage.exists(&old_rustdoc_file)?); +// assert!(!storage.exists(&old_source_file)?); + +// // doc archive exists +// let doc_archive = rustdoc_archive_path(crate_, &version); +// assert!(storage.exists(&doc_archive)?, "{}", doc_archive); + +// // source archive exists +// let source_archive = source_archive_path(crate_, &version); +// assert!(storage.exists(&source_archive)?, "{}", source_archive); + +// // default target was built and is accessible +// assert!(storage.exists_in_archive( +// &doc_archive, +// None, +// &format!("{crate_path}/index.html"), +// )?); +// runtime.block_on(web.assert_success(&format!("/{crate_}/{version}/{crate_path}/")))?; + +// // source is also packaged +// assert!(storage.exists_in_archive(&source_archive, None, "src/lib.rs",)?); +// runtime.block_on( +// web.assert_success(&format!("/crate/{crate_}/{version}/source/src/lib.rs")), +// )?; +// assert!(!storage.exists_in_archive( +// &doc_archive, +// None, +// &format!("{default_target}/{crate_path}/index.html"), +// )?); + +// let default_target_url = format!("/{crate_}/{version}/{default_target}/{crate_path}/"); +// runtime.block_on(web.assert_redirect( +// &default_target_url, +// &format!("/{crate_}/{version}/{crate_path}/"), +// ))?; + +// // Non-dist toolchains only have a single target, and of course +// // if include_default_targets is false we won't have this full list +// // of targets. +// if builder.toolchain.as_dist().is_some() && env.config().include_default_targets { +// assert_eq!( +// targets, +// vec![ +// "aarch64-apple-darwin", +// "aarch64-unknown-linux-gnu", +// "i686-pc-windows-msvc", +// "x86_64-pc-windows-msvc", +// "x86_64-unknown-linux-gnu", +// ] +// ); + +// // other targets too +// for target in DEFAULT_TARGETS { +// for alg in RUSTDOC_JSON_COMPRESSION_ALGORITHMS { +// // check if rustdoc json files exist for all targets +// let path = rustdoc_json_path( +// crate_, +// &version, +// target, +// RustdocJsonFormatVersion::Latest, +// Some(*alg), +// ); +// assert!(storage.exists(&path)?); + +// let ext = compression::file_extension_for(*alg); + +// let json_prefix = format!("rustdoc-json/{crate_}/{version}/{target}/"); +// let mut json_files: Vec<_> = storage +// .list_prefix(&json_prefix) +// .filter_map(|res| res.ok()) +// .map(|f| f.strip_prefix(&json_prefix).unwrap().to_owned()) +// .collect(); +// json_files.retain(|f| f.ends_with(&format!(".json.{ext}"))); +// json_files.sort(); +// dbg!(&json_files); +// assert!(json_files[0].starts_with(&format!("empty-library_1.0.0_{target}_"))); +// assert!(json_files[0].ends_with(&format!(".json.{ext}"))); +// assert_eq!( +// json_files[1], +// format!("empty-library_1.0.0_{target}_latest.json.{ext}") +// ); +// } + +// if target == &default_target { +// continue; +// } +// let target_docs_present = storage.exists_in_archive( +// &doc_archive, +// None, +// &format!("{target}/{crate_path}/index.html"), +// )?; + +// let target_url = format!("/{crate_}/{version}/{target}/{crate_path}/index.html"); + +// assert!(target_docs_present); +// runtime.block_on(web.assert_success(&target_url))?; + +// assert!( +// storage +// .exists(&format!("build-logs/{}/{target}.txt", row.build_id)) +// .unwrap() +// ); +// } +// } +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_collect_metrics() -> Result<()> { +// let metrics_dir = tempfile::tempdir().unwrap().keep(); +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .compiler_metrics_collection_path(Some(metrics_dir.clone())) +// .include_default_targets(false) +// .build()?, +// )?; + +// let crate_ = DUMMY_CRATE_NAME; +// let version = DUMMY_CRATE_VERSION; + +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_package(crate_, &version, PackageKind::CratesIo, true)? +// .successful +// ); + +// let metric_files: Vec<_> = fs::read_dir(&metrics_dir)? +// .filter_map(|di| di.ok()) +// .map(|di| di.path()) +// .collect(); + +// assert_eq!(metric_files.len(), 1); + +// let _: serde_json::Value = serde_json::from_slice(&fs::read(&metric_files[0])?)?; + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_build_binary_crate() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// // some binary crate +// let crate_ = "heater"; +// let version = Version::new(0, 2, 3); + +// let storage = env.storage(); +// let old_rustdoc_file = format!("rustdoc/{crate_}/{version}/some_doc_file"); +// let old_source_file = format!("sources/{crate_}/{version}/some_source_file"); +// storage.store_one(&old_rustdoc_file, Vec::new())?; +// storage.store_one(&old_source_file, Vec::new())?; + +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// !builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// // check release record in the db (default and other targets) +// let row = env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// "SELECT +// r.rustdoc_status, +// r.is_library +// FROM +// crates as c +// INNER JOIN releases AS r ON c.id = r.crate_id +// LEFT OUTER JOIN doc_coverage AS cov ON r.id = cov.release_id +// WHERE +// c.name = $1 AND +// r.version = $2", +// crate_, +// version as _ +// ) +// .fetch_one(&mut *conn) +// .await +// })?; + +// assert_eq!(row.rustdoc_status, Some(false)); +// assert_eq!(row.is_library, Some(false)); + +// // doc archive exists +// let doc_archive = rustdoc_archive_path(crate_, &version); +// assert!(!storage.exists(&doc_archive)?); + +// // source archive exists +// let source_archive = source_archive_path(crate_, &version); +// assert!(storage.exists(&source_archive)?); + +// // old rustdoc & source files still exist +// assert!(storage.exists(&old_rustdoc_file)?); +// assert!(storage.exists(&old_source_file)?); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_failed_build_with_existing_successful_release() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// // rand 0.8.5 fails to build with recent nightly versions +// // https://github.com/rust-lang/docs.rs/issues/26750 +// let crate_ = "rand"; +// let version = Version::new(0, 8, 5); + +// // create a successful release & build in the database +// let release_id = env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// let crate_id = initialize_crate(&mut conn, crate_).await?; +// let release_id = initialize_release(&mut conn, crate_id, &version).await?; +// let build_id = initialize_build(&mut conn, release_id).await?; +// finish_build( +// &mut conn, +// build_id, +// "some-version", +// "other-version", +// BuildStatus::Success, +// None, +// None, +// ) +// .await?; +// finish_release( +// &mut conn, +// crate_id, +// release_id, +// &MetadataPackage { +// name: crate_.into(), +// version: version.clone(), +// id: "".into(), +// license: None, +// repository: None, +// homepage: None, +// description: None, +// documentation: None, +// dependencies: vec![], +// targets: vec![], +// readme: None, +// keywords: vec![], +// features: HashMap::new(), +// }, +// Path::new("/unknown/"), +// "x86_64-unknown-linux-gnu", +// serde_json::Value::Array(vec![]), +// vec![ +// "i686-pc-windows-msvc".into(), +// "aarch64-unknown-linux-gnu".into(), +// "aarch64-apple-darwin".into(), +// "x86_64-pc-windows-msvc".into(), +// "x86_64-unknown-linux-gnu".into(), +// ], +// &ReleaseData::default(), +// true, +// false, +// iter::once(CompressionAlgorithm::Bzip2), +// None, +// true, +// 42, +// ) +// .await?; + +// Ok::<_, anyhow::Error>(release_id) +// })?; + +// fn check_rustdoc_status(env: &TestEnvironment, rid: ReleaseId) -> Result<()> { +// assert_eq!( +// env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query_scalar!("SELECT rustdoc_status FROM releases WHERE id = $1", rid.0) +// .fetch_one(&mut *conn) +// .await +// })?, +// Some(true) +// ); +// Ok(()) +// } + +// check_rustdoc_status(&env, release_id)?; + +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// // not successful build +// !builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// check_rustdoc_status(&env, release_id)?; +// Ok(()) +// } + +// #[test_case("scsys-macros", Version::new(0, 2, 6))] +// #[test_case("scsys-derive", Version::new(0, 2, 6))] +// #[test_case("thiserror-impl", Version::new(1, 0, 26))] +// #[ignore] +// fn test_proc_macro(crate_: &str, version: Version) -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// let storage = env.storage(); + +// // doc archive exists +// let doc_archive = rustdoc_archive_path(crate_, &version); +// assert!(storage.exists(&doc_archive)?); + +// // source archive exists +// let source_archive = source_archive_path(crate_, &version); +// assert!(storage.exists(&source_archive)?); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_cross_compile_non_host_default() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let crate_ = "windows-win"; +// let version = Version::new(2, 4, 1); +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// if builder.toolchain.as_ci().is_some() { +// return Ok(()); +// } +// assert!( +// builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// let storage = env.storage(); + +// // doc archive exists +// let doc_archive = rustdoc_archive_path(crate_, &version); +// assert!(storage.exists(&doc_archive)?, "{}", doc_archive); + +// // source archive exists +// let source_archive = source_archive_path(crate_, &version); +// assert!(storage.exists(&source_archive)?, "{}", source_archive); + +// let target = "x86_64-unknown-linux-gnu"; +// let crate_path = crate_.replace('-', "_"); +// let target_docs_present = storage.exists_in_archive( +// &doc_archive, +// None, +// &format!("{target}/{crate_path}/index.html"), +// )?; +// assert!(target_docs_present); + +// env.runtime().block_on(async { +// let web = env.web_app().await; +// let target_url = format!("/{crate_}/{version}/{target}/{crate_path}/index.html"); + +// web.assert_success(&target_url).await +// })?; + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_locked_fails_unlocked_needs_new_deps() -> Result<()> { +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .include_default_targets(false) +// .build()?, +// )?; + +// // if the corrected dependency of the crate was already downloaded we need to remove it +// remove_cache_files(&env, "rand_core", &Version::new(0, 5, 1))?; + +// // Specific setup required: +// // * crate has a binary so that it is published with a lockfile +// // * crate has a library so that it is documented by docs.rs +// // * crate has an optional dependency +// // * metadata enables the optional dependency for docs.rs +// // * `cargo doc` fails with the version of the dependency in the lockfile +// // * there is a newer version of the dependency available that correctly builds +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/incorrect_lockfile_0_1"))? +// .successful +// ); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_locked_fails_unlocked_needs_new_unknown_deps() -> Result<()> { +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .include_default_targets(false) +// .build()?, +// )?; + +// // if the corrected dependency of the crate was already downloaded we need to remove it +// remove_cache_files(&env, "value-bag-sval2", &Version::new(1, 4, 1))?; + +// // Similar to above, this crate fails to build with the published +// // lockfile, but generating a new working lockfile requires +// // introducing a completely new dependency (not just version) which +// // would not have had its details pulled down from the sparse-index. +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/incorrect_lockfile_0_2"))? +// .successful +// ); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_rustflags_are_passed_to_build_script() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let crate_ = "proc-macro2"; +// let version = Version::new(1, 0, 95); +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_sources_are_added_even_for_build_failures_before_build() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// // https://github.com/rust-lang/docs.rs/issues/2523 +// // package with invalid cargo metadata. +// // Will succeed in the crate fetch step, so sources are +// // added. Will fail when we try to build. +// let crate_ = "simconnect-sys"; +// let version = Version::new(0, 23, 1); +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; + +// // `Result` is `Ok`, but the build-result is `false` +// assert!( +// !builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// // source archive exists +// let source_archive = source_archive_path(crate_, &version); +// assert!( +// env.storage().exists(&source_archive)?, +// "archive doesnt exist: {source_archive}" +// ); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_build_failures_before_build() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// // https://github.com/rust-lang/docs.rs/issues/2491 +// // package without Cargo.toml, so fails directly in the fetch stage. +// let crate_ = "emheap"; +// let version = Version::new(0, 1, 0); +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; + +// // `Result` is `Ok`, but the build-result is `false` +// let summary = builder.build_package(crate_, &version, PackageKind::CratesIo, false)?; + +// assert!(!summary.successful); +// assert!(summary.should_reattempt); + +// let row = env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// r#"SELECT +// rustc_version, +// docsrs_version, +// build_status as "build_status: BuildStatus", +// errors +// FROM +// crates as c +// INNER JOIN releases as r on c.id = r.crate_id +// INNER JOIN builds as b on b.rid = r.id +// WHERE c.name = $1 and r.version = $2"#, +// crate_, +// version as _, +// ) +// .fetch_one(&mut *conn) +// .await +// })?; + +// assert!(row.rustc_version.is_none()); +// assert!(row.docsrs_version.is_none()); +// assert_eq!(row.build_status, BuildStatus::Failure); +// assert!(row.errors.unwrap().contains("missing Cargo.toml")); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_implicit_features_for_optional_dependencies() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let crate_ = "serde"; +// let version = Version::new(1, 0, 152); +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_package(crate_, &version, PackageKind::CratesIo, false)? +// .successful +// ); + +// assert!( +// get_features(&env, crate_, &version)? +// .unwrap() +// .iter() +// .any(|f| f.name == "serde_derive") +// ); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_no_implicit_features_for_optional_dependencies_with_dep_syntax() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let mut builder = RustwideBuilder::init(&env.context).unwrap(); +// builder.update_toolchain()?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/optional-dep"))? +// .successful +// ); + +// assert_eq!( +// get_features(&env, "optional-dep", &Version::new(0, 0, 1))? +// .unwrap() +// .iter() +// .map(|f| f.name.to_owned()) +// .sorted() +// .collect_vec(), +// // "regex" feature is not in the list, +// // because we don't have implicit features for optional dependencies +// // with `dep` syntax any more. +// vec!["alloc", "default", "optional_regex", "std"] +// ); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_build_std() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let mut builder = RustwideBuilder::init(&env.context)?; +// builder.update_toolchain()?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/build-std"))? +// .successful +// ); +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_workspace_reinitialize_at_once() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let mut builder = RustwideBuilder::init(&env.context)?; +// builder.update_toolchain()?; +// builder.reinitialize_workspace_if_interval_passed(&env.context)?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/build-std"))? +// .successful +// ); +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_workspace_reinitialize_after_interval() -> Result<()> { +// let env = TestEnvironment::with_config_and_runtime( +// TestEnvironment::base_config() +// .build_workspace_reinitialization_interval(Duration::from_secs(1)) +// .build()?, +// )?; + +// use std::thread::sleep; +// use std::time::Duration; + +// let mut builder = RustwideBuilder::init(&env.context)?; +// builder.update_toolchain()?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/build-std"))? +// .successful +// ); +// sleep(Duration::from_secs(1)); +// builder.reinitialize_workspace_if_interval_passed(&env.context)?; +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/build-std"))? +// .successful +// ); +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_new_builder_detects_existing_rustc() -> Result<()> { +// let env = TestEnvironment::new_with_runtime()?; + +// let mut builder = RustwideBuilder::init(&env.context)?; +// builder.update_toolchain()?; +// let old_version = builder.rustc_version()?; +// drop(builder); + +// // new builder should detect the existing rustc version from the previous builder +// // (simulating running `update-toolchain` and `build crate` in separate invocations) +// let mut builder = RustwideBuilder::init(&env.context)?; +// assert!( +// builder +// .build_package( +// DUMMY_CRATE_NAME, +// &DUMMY_CRATE_VERSION, +// PackageKind::CratesIo, +// false +// )? +// .successful +// ); +// assert_eq!(old_version, builder.rustc_version()?); + +// Ok(()) +// } + +// #[test] +// fn test_read_format_version_from_rustdoc_json() -> Result<()> { +// let buf = serde_json::to_vec(&serde_json::json!({ +// "something": "else", +// "format_version": 42 +// }))?; + +// assert_eq!( +// read_format_version_from_rustdoc_json(&mut io::Cursor::new(buf))?, +// RustdocJsonFormatVersion::Version(42) +// ); + +// Ok(()) +// } + +// #[test] +// #[ignore] +// fn test_additional_targets() -> Result<()> { +// fn assert_contains(targets: &[String], target: &str) { +// assert!( +// targets.iter().any(|t| t == target), +// "Not found target {target:?} in {targets:?}" +// ); +// } + +// let env = TestEnvironment::new_with_runtime()?; + +// let mut builder = RustwideBuilder::init(&env.context)?; +// builder.update_toolchain()?; + +// assert!( +// builder +// .build_local_package(Path::new("tests/crates/additional-targets"))? +// .successful +// ); + +// let row = env.runtime().block_on(async { +// let mut conn = env.async_db().async_conn().await; +// sqlx::query!( +// r#"SELECT +// r.doc_targets +// FROM +// crates as c +// INNER JOIN releases AS r ON c.id = r.crate_id +// WHERE +// c.name = $1 AND +// r.version = $2"#, +// "additional-targets", +// "0.1.0", +// ) +// .fetch_one(&mut *conn) +// .await +// })?; + +// let targets: Vec = row +// .doc_targets +// .unwrap() +// .as_array() +// .unwrap() +// .iter() +// .map(|v| v.as_str().unwrap().to_owned()) +// .collect(); + +// assert_contains(&targets, "x86_64-apple-darwin"); +// // Part of the default targets. +// assert_contains(&targets, "aarch64-apple-darwin"); + +// Ok(()) +// } +// } diff --git a/crates/bin/docs_rs_builder/src/metrics.rs b/crates/bin/docs_rs_builder/src/metrics.rs index 356f059ba..2f27dac6c 100644 --- a/crates/bin/docs_rs_builder/src/metrics.rs +++ b/crates/bin/docs_rs_builder/src/metrics.rs @@ -1,4 +1,4 @@ -//// buckets for documentation size, in MiB +/// buckets for documentation size, in MiB /// Base for some estimates: /// * `itertools` docs is an 8.2 MB archive with 144 MB of docs /// * the biggest doc archive know of (`stm32ral`) is an 1.8 GiB archive, diff --git a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs index 4afb1c70b..dbe211b26 100644 --- a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs +++ b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs @@ -9,11 +9,7 @@ use std::{fs, io, thread}; use tracing::{error, warn}; /// the main build-server loop -pub fn queue_builder( - config: &Config, - context: &Context, - mut builder: RustwideBuilder, -) -> Result<()> { +pub fn queue_builder(config: &Config, context: &Context, builder: RustwideBuilder) -> Result<()> { loop { let temp_dir = &config.temp_dir; if temp_dir.exists() diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index 0cff8516c..916e1cb7d 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -5,13 +5,11 @@ edition = "2024" [dependencies] anyhow = { workspace = true } -chrono = { workspace = true } clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} docs_rs_fastly = { path = "../../lib/docs_rs_fastly" } docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } -docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } @@ -24,12 +22,7 @@ futures-util = { workspace = true } itertools = { workspace = true } opentelemetry = { workspace = true } rayon = { workspace = true } -regex = { workspace = true } -reqwest = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } sqlx = { workspace = true } -thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } url = { workspace = true } diff --git a/crates/bin/docs_rs_watcher/src/build_queue.rs b/crates/bin/docs_rs_watcher/src/build_queue.rs index a432407c9..63ff3e4d3 100644 --- a/crates/bin/docs_rs_watcher/src/build_queue.rs +++ b/crates/bin/docs_rs_watcher/src/build_queue.rs @@ -61,7 +61,7 @@ pub async fn get_new_crates( for change in &changes { if let Some((ref krate, ..)) = change.crate_deleted() { - match delete_crate(&mut *conn, &storage, krate).await { + match delete_crate(&mut *conn, storage, krate).await { Ok(_) => info!( "crate {} was deleted from the index and the database", krate @@ -74,7 +74,7 @@ pub async fn get_new_crates( let krate: KrateName = krate.parse().unwrap(); - cdn.queue_crate_invalidation(&krate).await; + cdn.queue_crate_invalidation(&krate).await?; build_queue.remove_crate_from_queue(&krate).await?; continue; } @@ -85,7 +85,7 @@ pub async fn get_new_crates( .parse() .context("couldn't parse release version as semver")?; - match delete_version(&mut *conn, &storage, &release.name, &version).await { + match delete_version(&mut *conn, storage, &release.name, &version).await { Ok(_) => info!( "release {}-{} was deleted from the index and the database", release.name, release.version @@ -96,7 +96,7 @@ pub async fn get_new_crates( } let krate: KrateName = release.name.parse().unwrap(); - cdn.queue_crate_invalidation(&krate).await; + cdn.queue_crate_invalidation(&krate).await?; build_queue .remove_version_from_queue(&release.name, &version) .await?; @@ -150,7 +150,7 @@ pub async fn get_new_crates( } let krate: KrateName = release.name.parse().unwrap(); - cdn.queue_crate_invalidation(&krate).await; + cdn.queue_crate_invalidation(&krate).await?; } } diff --git a/crates/bin/docs_rs_watcher/src/consistency/mod.rs b/crates/bin/docs_rs_watcher/src/consistency/mod.rs index 5ac4156ab..7cbe65de7 100644 --- a/crates/bin/docs_rs_watcher/src/consistency/mod.rs +++ b/crates/bin/docs_rs_watcher/src/consistency/mod.rs @@ -1,9 +1,6 @@ // use crate::build_queue::PRIORITY_CONSISTENCY_CHECK; // use crate::{Context, db::delete}; -use anyhow::{Context as _, Result}; -use docs_rs_build_queue::PRIORITY_CONSISTENCY_CHECK; -use itertools::Itertools; -use tracing::{info, warn}; +use anyhow::Result; mod data; mod db; diff --git a/crates/bin/docs_rs_watcher/src/db/delete.rs b/crates/bin/docs_rs_watcher/src/db/delete.rs index 7e346b00a..7ed99c8d3 100644 --- a/crates/bin/docs_rs_watcher/src/db/delete.rs +++ b/crates/bin/docs_rs_watcher/src/db/delete.rs @@ -1,4 +1,4 @@ -use anyhow::{Context as _, Result}; +use anyhow::Result; use docs_rs_database::types::{CrateId, version::Version}; use docs_rs_storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}; use sqlx::Connection; diff --git a/crates/bin/docs_rs_watcher/src/main.rs b/crates/bin/docs_rs_watcher/src/main.rs index ae695f35a..e077fb60c 100644 --- a/crates/bin/docs_rs_watcher/src/main.rs +++ b/crates/bin/docs_rs_watcher/src/main.rs @@ -112,7 +112,7 @@ pub fn start_background_service_metric_collector( build_queue: Arc, meter_provider: &AnyMeterProvider, ) -> Result<()> { - let service_metrics = Arc::new(OtelServiceMetrics::new(&meter_provider)); + let service_metrics = Arc::new(OtelServiceMetrics::new(meter_provider)); start_async_cron( "background service metric collector", diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index b95ba5045..fffed0696 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -37,7 +37,6 @@ lol_html = "2.0.0" mime = { workspace = true } num_cpus = "1.15.0" opentelemetry = { workspace = true } -opentelemetry_sdk = { workspace = true } phf = "0.13.1" rayon = { workspace = true } regex = { workspace = true } @@ -61,6 +60,7 @@ url = { workspace = true } slug = { workspace = true } getrandom = "0.3.1" + [build-dependencies] anyhow = { workspace = true } syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } @@ -70,5 +70,8 @@ md5 = "0.8.0" phf_codegen = "0.13" [package.metadata.cargo-machete] -ignored = ["phf"] +ignored = [ + "slug",# used in HTML templates, where machete doesn't look inside + "phf", # used in code that's generated by the build-script +] diff --git a/crates/bin/docs_rs_web/src/build_details.rs b/crates/bin/docs_rs_web/src/build_details.rs index 866b74d75..c1b5d4f08 100644 --- a/crates/bin/docs_rs_web/src/build_details.rs +++ b/crates/bin/docs_rs_web/src/build_details.rs @@ -12,10 +12,8 @@ use anyhow::Context as _; use askama::Template; use axum::{extract::Extension, response::IntoResponse}; use chrono::{DateTime, Utc}; -use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_MANUAL_FROM_CRATES_IO}; -use docs_rs_database::types::{BuildId, BuildStatus, version::Version}; +use docs_rs_database::types::{BuildId, BuildStatus}; use docs_rs_storage::AsyncStorage; -use docs_rs_utils::{BUILD_VERSION, DEFAULT_MAX_TARGETS}; use futures_util::TryStreamExt; use serde::Deserialize; use std::sync::Arc; diff --git a/crates/bin/docs_rs_web/src/config.rs b/crates/bin/docs_rs_web/src/config.rs index 1a65cc406..5fd22eba6 100644 --- a/crates/bin/docs_rs_web/src/config.rs +++ b/crates/bin/docs_rs_web/src/config.rs @@ -1,6 +1,5 @@ use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{path::PathBuf, time::Duration}; -use url::Url; #[derive(Debug)] pub struct Config { diff --git a/crates/bin/docs_rs_web/src/lib.rs b/crates/bin/docs_rs_web/src/lib.rs index 48d81daa2..40026f5ea 100644 --- a/crates/bin/docs_rs_web/src/lib.rs +++ b/crates/bin/docs_rs_web/src/lib.rs @@ -476,7 +476,7 @@ async fn apply_middleware( ) -> Result { let has_templates = template_data.is_some(); - let web_metrics = Arc::new(WebMetrics::new(&context.meter_provider())); + let web_metrics = Arc::new(WebMetrics::new(context.meter_provider())); Ok(router.layer( ServiceBuilder::new() diff --git a/crates/bin/docs_rs_web/src/releases.rs b/crates/bin/docs_rs_web/src/releases.rs index 17cb17bef..ec7b844fe 100644 --- a/crates/bin/docs_rs_web/src/releases.rs +++ b/crates/bin/docs_rs_web/src/releases.rs @@ -11,7 +11,7 @@ use crate::{ page::templates::{RenderBrands, RenderRegular, RenderSolid, filters}, rustdoc::OfficialCrateDescription, }; -use anyhow::{Context as _, Result, anyhow}; +use anyhow::{Context as _, Result}; use askama::Template; use axum::{ extract::{Extension, Query}, diff --git a/crates/bin/docs_rs_web/src/rustdoc.rs b/crates/bin/docs_rs_web/src/rustdoc.rs index 584134741..7aa3869b0 100644 --- a/crates/bin/docs_rs_web/src/rustdoc.rs +++ b/crates/bin/docs_rs_web/src/rustdoc.rs @@ -32,9 +32,7 @@ use axum_extra::{ headers::{ContentType, ETag, Header as _, HeaderMapExt as _}, typed_header::TypedHeader, }; -use docs_rs_build_queue::{AsyncBuildQueue, PRIORITY_CONTINUOUS, QueuedCrate}; use docs_rs_cargo_metadata::Dependency; -use docs_rs_database::types::version::Version; use docs_rs_headers::etag::ETagComputer; use docs_rs_headers::{IfNoneMatch, X_ROBOTS_TAG}; use docs_rs_registry_api::OwnerKind; diff --git a/crates/bin/docs_rs_web/src/sitemap.rs b/crates/bin/docs_rs_web/src/sitemap.rs index 5ed1cd074..c4129bb5b 100644 --- a/crates/bin/docs_rs_web/src/sitemap.rs +++ b/crates/bin/docs_rs_web/src/sitemap.rs @@ -7,7 +7,6 @@ use crate::{ impl_axum_webpage, page::templates::{RenderBrands, RenderSolid, filters}, }; -use anyhow::Context as _; use askama::Template; use async_stream::stream; use axum::{ diff --git a/crates/lib/docs_rs_build_utils/Cargo.toml b/crates/lib/docs_rs_build_utils/Cargo.toml index e8f1e0b9d..c0b3533f3 100644 --- a/crates/lib/docs_rs_build_utils/Cargo.toml +++ b/crates/lib/docs_rs_build_utils/Cargo.toml @@ -4,12 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] -docs_rs_database = { path = "../docs_rs_database" } -serde = { workspace = true} -docs_rs_utils = { path = "../docs_rs_utils" } -sqlx = { workspace = true } anyhow = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_utils = { path = "../docs_rs_utils" } futures-util = { workspace = true } +serde = { workspace = true} +sqlx = { workspace = true } tracing = { workspace = true } diff --git a/crates/lib/docs_rs_build_utils/src/limits.rs b/crates/lib/docs_rs_build_utils/src/limits.rs index 48d6a48cb..3920b8ac7 100644 --- a/crates/lib/docs_rs_build_utils/src/limits.rs +++ b/crates/lib/docs_rs_build_utils/src/limits.rs @@ -1,7 +1,4 @@ -use crate::{ - config::Config, - overrides::{self, Overrides}, -}; +use crate::{config::Config, overrides::Overrides}; use anyhow::Result; use serde::Serialize; use std::time::Duration; diff --git a/crates/lib/docs_rs_build_utils/src/overrides.rs b/crates/lib/docs_rs_build_utils/src/overrides.rs index af2614e86..e6f7ce9bb 100644 --- a/crates/lib/docs_rs_build_utils/src/overrides.rs +++ b/crates/lib/docs_rs_build_utils/src/overrides.rs @@ -84,57 +84,57 @@ impl Overrides { } } -#[cfg(test)] -mod test { - use crate::{db::Overrides, test::*}; - use std::time::Duration; - - #[test] - fn retrieve_overrides() { - async_wrapper(|env| async move { - let db = env.async_db(); - let mut conn = db.async_conn().await; - - let krate = "hexponent"; - - // no overrides - let actual = Overrides::for_crate(&mut conn, krate).await?; - assert_eq!(actual, None); - - // add partial overrides - let expected = Overrides { - targets: Some(1), - ..Overrides::default() - }; - Overrides::save(&mut conn, krate, expected).await?; - let actual = Overrides::for_crate(&mut conn, krate).await?; - assert_eq!(actual, Some(expected)); - - // overwrite with full overrides - let expected = Overrides { - memory: Some(100_000), - targets: Some(1), - timeout: Some(Duration::from_secs(300)), - }; - Overrides::save(&mut conn, krate, expected).await?; - let actual = Overrides::for_crate(&mut conn, krate).await?; - assert_eq!(actual, Some(expected)); - - // overwrite with partial overrides - let expected = Overrides { - memory: Some(1), - ..Overrides::default() - }; - Overrides::save(&mut conn, krate, expected).await?; - let actual = Overrides::for_crate(&mut conn, krate).await?; - assert_eq!(actual, Some(expected)); - - // remove overrides - Overrides::remove(&mut conn, krate).await?; - let actual = Overrides::for_crate(&mut conn, krate).await?; - assert_eq!(actual, None); - - Ok(()) - }) - } -} +// #[cfg(test)] +// mod test { +// use crate::{db::Overrides, test::*}; +// use std::time::Duration; + +// #[test] +// fn retrieve_overrides() { +// async_wrapper(|env| async move { +// let db = env.async_db(); +// let mut conn = db.async_conn().await; + +// let krate = "hexponent"; + +// // no overrides +// let actual = Overrides::for_crate(&mut conn, krate).await?; +// assert_eq!(actual, None); + +// // add partial overrides +// let expected = Overrides { +// targets: Some(1), +// ..Overrides::default() +// }; +// Overrides::save(&mut conn, krate, expected).await?; +// let actual = Overrides::for_crate(&mut conn, krate).await?; +// assert_eq!(actual, Some(expected)); + +// // overwrite with full overrides +// let expected = Overrides { +// memory: Some(100_000), +// targets: Some(1), +// timeout: Some(Duration::from_secs(300)), +// }; +// Overrides::save(&mut conn, krate, expected).await?; +// let actual = Overrides::for_crate(&mut conn, krate).await?; +// assert_eq!(actual, Some(expected)); + +// // overwrite with partial overrides +// let expected = Overrides { +// memory: Some(1), +// ..Overrides::default() +// }; +// Overrides::save(&mut conn, krate, expected).await?; +// let actual = Overrides::for_crate(&mut conn, krate).await?; +// assert_eq!(actual, Some(expected)); + +// // remove overrides +// Overrides::remove(&mut conn, krate).await?; +// let actual = Overrides::for_crate(&mut conn, krate).await?; +// assert_eq!(actual, None); + +// Ok(()) +// }) +// } +// } diff --git a/crates/lib/docs_rs_cargo_metadata/src/lib.rs b/crates/lib/docs_rs_cargo_metadata/src/lib.rs index c2388e04e..ff027b78f 100644 --- a/crates/lib/docs_rs_cargo_metadata/src/lib.rs +++ b/crates/lib/docs_rs_cargo_metadata/src/lib.rs @@ -5,6 +5,8 @@ use docs_rs_database::types::version::Version; use semver::VersionReq; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +#[cfg(test)] +use std::path::Path; pub struct CargoMetadata { root: Package, @@ -20,7 +22,7 @@ impl CargoMetadata { let status = res.status; if !status.success() { let stderr = std::str::from_utf8(&res.stderr).unwrap_or(""); - bail!("error returned by `cargo metadata`: {status}\n{stderr}") + anyhow::bail!("error returned by `cargo metadata`: {status}\n{stderr}") } Self::load_from_metadata(std::str::from_utf8(&res.stdout)?) } diff --git a/crates/lib/docs_rs_database/Cargo.toml b/crates/lib/docs_rs_database/Cargo.toml index 80c4a2d9c..1f9e33d94 100644 --- a/crates/lib/docs_rs_database/Cargo.toml +++ b/crates/lib/docs_rs_database/Cargo.toml @@ -11,7 +11,6 @@ derive_more = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } futures-util = { workspace = true } -hex = "0.4.3" mime = { workspace = true } mime_guess = "2" opentelemetry = { workspace = true } diff --git a/crates/lib/docs_rs_database/src/types/mod.rs b/crates/lib/docs_rs_database/src/types/mod.rs index a4fcd208e..8a1e6e262 100644 --- a/crates/lib/docs_rs_database/src/types/mod.rs +++ b/crates/lib/docs_rs_database/src/types/mod.rs @@ -58,20 +58,20 @@ impl PartialEq<&str> for BuildStatus { } } -#[cfg(test)] -mod tests { - use super::*; - use test_case::test_case; +// #[cfg(test)] +// mod tests { +// use super::*; +// use test_case::test_case; - #[test_case(BuildStatus::Success, "success")] - #[test_case(BuildStatus::Failure, "failure")] - #[test_case(BuildStatus::InProgress, "in_progress")] - fn test_build_status_serialization(status: BuildStatus, expected: &str) { - let serialized = serde_json::to_string(&status).unwrap(); - assert_eq!(serialized, format!("\"{expected}\"")); - assert_eq!( - serde_json::from_str::(&serialized).unwrap(), - status - ); - } -} +// #[test_case(BuildStatus::Success, "success")] +// #[test_case(BuildStatus::Failure, "failure")] +// #[test_case(BuildStatus::InProgress, "in_progress")] +// fn test_build_status_serialization(status: BuildStatus, expected: &str) { +// let serialized = serde_json::to_string(&status).unwrap(); +// assert_eq!(serialized, format!("\"{expected}\"")); +// assert_eq!( +// serde_json::from_str::(&serialized).unwrap(), +// status +// ); +// } +// } diff --git a/crates/lib/docs_rs_headers/Cargo.toml b/crates/lib/docs_rs_headers/Cargo.toml index 385f27641..0072408f7 100644 --- a/crates/lib/docs_rs_headers/Cargo.toml +++ b/crates/lib/docs_rs_headers/Cargo.toml @@ -12,6 +12,5 @@ serde = { workspace = true } anyhow = { workspace = true } itertools = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } -docs_rs_utils = { path = "../docs_rs_utils" } docs_rs_web_utils = { path = "../docs_rs_web_utils" } askama = { workspace = true } diff --git a/crates/lib/docs_rs_headers/src/surrogate_key.rs b/crates/lib/docs_rs_headers/src/surrogate_key.rs index 3f606ab62..943f53a28 100644 --- a/crates/lib/docs_rs_headers/src/surrogate_key.rs +++ b/crates/lib/docs_rs_headers/src/surrogate_key.rs @@ -175,77 +175,77 @@ impl FromStr for SurrogateKeys { } } -#[cfg(test)] -mod tests { - use super::*; - use crate::test::headers::test_typed_encode; - use std::ops::RangeInclusive; - use test_case::test_case; - - #[test] - fn test_parse_surrogate_key_too_long() { - let input = "X".repeat(1025); - assert!(SurrogateKey::from_str(&input).is_err()); - } - - #[test_case(""; "empty")] - #[test_case(" "; "space")] - #[test_case("\n"; "newline")] - fn test_parse_surrogate_key_err(input: &str) { - assert!(SurrogateKey::from_str(input).is_err()); - } - - #[test_case("some-key")] - #[test_case("1234")] - #[test_case("crate-some-crate")] - #[test_case("release-some-crate-1.2.3")] - fn test_parse_surrogate_key_ok(input: &str) { - assert_eq!(SurrogateKey::from_str(input).unwrap(), input); - } - - #[test] - fn test_encode() -> anyhow::Result<()> { - let k1 = SurrogateKey::from_str("key-2").unwrap(); - let k2 = SurrogateKey::from_str("key-1").unwrap(); - // this key is duplicate, should be removed - let k3 = SurrogateKey::from_str("key-2").unwrap(); - - assert_eq!(k1, k3); - assert_ne!(k1, k2); - assert_ne!(k3, k2); - - assert_eq!( - test_typed_encode(SurrogateKeys::from_iter_until_full([k1, k2, k3])), - "key-2 key-1" - ); - - Ok(()) - } - - #[test_case('0'..='9'; "numbers")] - #[test_case('a'..='z'; "lower case")] - #[test_case('A'..='Z'; "upper case")] - fn test_from_krate_name(range: RangeInclusive) { - // ensure that the valid character range for crate names also fits - // into surrogate keys, and header values. - for ch in range { - let name = format!("k{}", ch); - let krate_name: KrateName = name.parse().unwrap(); - let surrogate_key: SurrogateKey = krate_name.into(); - assert_eq!(surrogate_key, format!("crate-{name}")); - } - } - - #[test] - fn test_try_from_iter_checks_full_length() -> anyhow::Result<()> { - let mut it = (0..10_000).map(|n| SurrogateKey::from_str(&format!("key-{n}")).unwrap()); - - let first_key = SurrogateKeys::from_iter_until_full(&mut it); - assert_eq!(first_key.encoded_len(), 16377); // < the max length of 16384 - - // elements remaining in the iterator - assert_eq!(it.count(), 8056); - - Ok(()) - } -} +// #[cfg(test)] +// mod tests { +// use super::*; +// use crate::test::headers::test_typed_encode; +// use std::ops::RangeInclusive; +// use test_case::test_case; + +// #[test] +// fn test_parse_surrogate_key_too_long() { +// let input = "X".repeat(1025); +// assert!(SurrogateKey::from_str(&input).is_err()); +// } + +// #[test_case(""; "empty")] +// #[test_case(" "; "space")] +// #[test_case("\n"; "newline")] +// fn test_parse_surrogate_key_err(input: &str) { +// assert!(SurrogateKey::from_str(input).is_err()); +// } + +// #[test_case("some-key")] +// #[test_case("1234")] +// #[test_case("crate-some-crate")] +// #[test_case("release-some-crate-1.2.3")] +// fn test_parse_surrogate_key_ok(input: &str) { +// assert_eq!(SurrogateKey::from_str(input).unwrap(), input); +// } + +// #[test] +// fn test_encode() -> anyhow::Result<()> { +// let k1 = SurrogateKey::from_str("key-2").unwrap(); +// let k2 = SurrogateKey::from_str("key-1").unwrap(); +// // this key is duplicate, should be removed +// let k3 = SurrogateKey::from_str("key-2").unwrap(); + +// assert_eq!(k1, k3); +// assert_ne!(k1, k2); +// assert_ne!(k3, k2); + +// assert_eq!( +// test_typed_encode(SurrogateKeys::from_iter_until_full([k1, k2, k3])), +// "key-2 key-1" +// ); + +// Ok(()) +// } + +// #[test_case('0'..='9'; "numbers")] +// #[test_case('a'..='z'; "lower case")] +// #[test_case('A'..='Z'; "upper case")] +// fn test_from_krate_name(range: RangeInclusive) { +// // ensure that the valid character range for crate names also fits +// // into surrogate keys, and header values. +// for ch in range { +// let name = format!("k{}", ch); +// let krate_name: KrateName = name.parse().unwrap(); +// let surrogate_key: SurrogateKey = krate_name.into(); +// assert_eq!(surrogate_key, format!("crate-{name}")); +// } +// } + +// #[test] +// fn test_try_from_iter_checks_full_length() -> anyhow::Result<()> { +// let mut it = (0..10_000).map(|n| SurrogateKey::from_str(&format!("key-{n}")).unwrap()); + +// let first_key = SurrogateKeys::from_iter_until_full(&mut it); +// assert_eq!(first_key.encoded_len(), 16377); // < the max length of 16384 + +// // elements remaining in the iterator +// assert_eq!(it.count(), 8056); + +// Ok(()) +// } +// } diff --git a/crates/lib/docs_rs_registry_api/Cargo.toml b/crates/lib/docs_rs_registry_api/Cargo.toml index 5162915b3..1276939da 100644 --- a/crates/lib/docs_rs_registry_api/Cargo.toml +++ b/crates/lib/docs_rs_registry_api/Cargo.toml @@ -5,7 +5,6 @@ edition = "2024" [dependencies] anyhow = { workspace = true } -async-trait = "0.1.83" bincode = { workspace = true } chrono = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } diff --git a/crates/lib/docs_rs_storage/Cargo.toml b/crates/lib/docs_rs_storage/Cargo.toml index 1694e4919..bc95f66ab 100644 --- a/crates/lib/docs_rs_storage/Cargo.toml +++ b/crates/lib/docs_rs_storage/Cargo.toml @@ -37,8 +37,10 @@ zstd = "0.13.0" serde_json = { workspace = true } [dev-dependencies] +criterion = "0.8.0" test-case = { workspace = true } + [[bench]] name = "compression" harness = false diff --git a/crates/lib/docs_rs_storage/benches/compression.rs b/crates/lib/docs_rs_storage/benches/compression.rs index 2514d6806..d8b2c9a15 100644 --- a/crates/lib/docs_rs_storage/benches/compression.rs +++ b/crates/lib/docs_rs_storage/benches/compression.rs @@ -1,5 +1,5 @@ use criterion::{Criterion, Throughput, criterion_group, criterion_main}; -use docs_rs::storage::{CompressionAlgorithm, compress, decompress}; +use docs_rs_storage::{CompressionAlgorithm, compress, decompress}; use std::hint::black_box; pub fn regex_capture_matches(c: &mut Criterion) { diff --git a/crates/lib/docs_rs_storage/src/lib.rs b/crates/lib/docs_rs_storage/src/lib.rs index d24589cfa..257d1cad3 100644 --- a/crates/lib/docs_rs_storage/src/lib.rs +++ b/crates/lib/docs_rs_storage/src/lib.rs @@ -924,7 +924,7 @@ impl AsyncStorage { } // remove existing local archive index files. - let local_index_folder = self.config.local_archive_cache_path.join(&prefix); + let local_index_folder = self.config.local_archive_cache_path.join(prefix); if local_index_folder.exists() { // FIXME: make this work when prefix is not a folder? tokio::fs::remove_dir_all(&local_index_folder) diff --git a/crates/lib/docs_rs_storage/src/utils/sized_buffer.rs b/crates/lib/docs_rs_storage/src/utils/sized_buffer.rs index 3e7c9e968..51c696d82 100644 --- a/crates/lib/docs_rs_storage/src/utils/sized_buffer.rs +++ b/crates/lib/docs_rs_storage/src/utils/sized_buffer.rs @@ -76,32 +76,32 @@ impl AsyncWrite for SizedBuffer { } } -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_sized_buffer() { - let mut buffer = SizedBuffer::new(1024); - - // Add two chunks of 500 bytes - assert_eq!(500, buffer.write(&[0; 500]).unwrap()); - assert_eq!(500, buffer.write(&[0; 500]).unwrap()); - - // Ensure adding a third chunk fails - let error = buffer.write(&[0; 500]).unwrap_err(); - assert!( - error - .get_ref() - .unwrap() - .is::() - ); - - // Ensure all the third chunk was discarded - assert_eq!(1000, buffer.inner.len()); - - // Ensure it's possible to reach the limit - assert_eq!(24, buffer.write(&[0; 24]).unwrap()); - assert_eq!(1024, buffer.inner.len()); - } -} +// #[cfg(test)] +// mod tests { +// use super::*; + +// #[test] +// fn test_sized_buffer() { +// let mut buffer = SizedBuffer::new(1024); + +// // Add two chunks of 500 bytes +// assert_eq!(500, buffer.write(&[0; 500]).unwrap()); +// assert_eq!(500, buffer.write(&[0; 500]).unwrap()); + +// // Ensure adding a third chunk fails +// let error = buffer.write(&[0; 500]).unwrap_err(); +// assert!( +// error +// .get_ref() +// .unwrap() +// .is::() +// ); + +// // Ensure all the third chunk was discarded +// assert_eq!(1000, buffer.inner.len()); + +// // Ensure it's possible to reach the limit +// assert_eq!(24, buffer.write(&[0; 24]).unwrap()); +// assert_eq!(1024, buffer.inner.len()); +// } +// } From f61106715c820ad84242428c83c8c4c07aea7a33 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 12:55:26 +0100 Subject: [PATCH 31/46] tryt --- Cargo.lock | 2 ++ crates/bin/docs_rs_web/Cargo.toml | 2 ++ crates/bin/docs_rs_web/src/lib.rs | 27 +++++++++---------- crates/bin/docs_rs_web/src/main.rs | 42 ++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 crates/bin/docs_rs_web/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 9b78251b5..b8df2dfcf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2081,6 +2081,7 @@ dependencies = [ "base64 0.22.1", "bincode 2.0.1", "chrono", + "clap", "comrak", "constant_time_eq", "derive_more", @@ -2091,6 +2092,7 @@ dependencies = [ "docs_rs_database", "docs_rs_env_vars", "docs_rs_headers", + "docs_rs_logging", "docs_rs_opentelemetry", "docs_rs_registry_api", "docs_rs_storage", diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index fffed0696..59abb23de 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -26,6 +26,7 @@ docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } docs_rs_headers = { path = "../../lib/docs_rs_headers" } docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry" } docs_rs_registry_api = { path = "../../lib/docs_rs_registry_api" } +docs_rs_logging = { path = "../../lib/docs_rs_logging" } docs_rs_storage = { path = "../../lib/docs_rs_storage" } docs_rs_utils = { path = "../../lib/docs_rs_utils" } docs_rs_web_utils = { path = "../../lib/docs_rs_web_utils" } @@ -59,6 +60,7 @@ tracing-futures= { version = "0.2.5", features = ["std-future", "futures-03"] } url = { workspace = true } slug = { workspace = true } getrandom = "0.3.1" +clap = { workspace = true } [build-dependencies] diff --git a/crates/bin/docs_rs_web/src/lib.rs b/crates/bin/docs_rs_web/src/lib.rs index 40026f5ea..86eb3086b 100644 --- a/crates/bin/docs_rs_web/src/lib.rs +++ b/crates/bin/docs_rs_web/src/lib.rs @@ -2,6 +2,7 @@ pub mod page; +pub use config::Config; use docs_rs_database::types::BuildStatus; pub use docs_rs_utils::{BUILD_VERSION, DEFAULT_MAX_TARGETS}; pub use font_awesome_as_a_crate::icons; @@ -65,7 +66,7 @@ use std::{ use tower::ServiceBuilder; use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::TraceLayer}; -use crate::{config::Config, metrics::WebMetrics}; +use crate::metrics::WebMetrics; use docs_rs_utils::rustc_version::get_correct_docsrs_style_file; use self::crate_details::Release; @@ -524,8 +525,9 @@ pub(crate) async fn build_axum_app( .await } +/// runs the webserver, blocks forever, until we get a shutdown signal for graceful shutdown. #[instrument(skip_all)] -pub fn start_web_server( +pub async fn run_web_server( addr: Option, config: Arc, context: &Context, @@ -540,19 +542,16 @@ pub fn start_web_server( axum_addr.port() ); - context.runtime().block_on(async { - let app = build_axum_app(config, context, template_data) - .await? - .into_make_service(); - let listener = tokio::net::TcpListener::bind(axum_addr) - .await - .context("error binding socket for web server")?; + let app = build_axum_app(config, context, template_data) + .await? + .into_make_service(); + let listener = tokio::net::TcpListener::bind(axum_addr) + .await + .context("error binding socket for web server")?; - axum::serve(listener, app) - .with_graceful_shutdown(shutdown_signal()) - .await?; - Ok::<(), Error>(()) - })?; + axum::serve(listener, app) + .with_graceful_shutdown(shutdown_signal()) + .await?; Ok(()) } diff --git a/crates/bin/docs_rs_web/src/main.rs b/crates/bin/docs_rs_web/src/main.rs new file mode 100644 index 000000000..0c7a594ec --- /dev/null +++ b/crates/bin/docs_rs_web/src/main.rs @@ -0,0 +1,42 @@ +use anyhow::{Context as _, Result}; +use clap::Parser; +use docs_rs_build_queue::AsyncBuildQueue; +use docs_rs_database::Pool; +use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_utils::start_async_cron; +use std::{net::SocketAddr, sync::Arc, time::Duration}; +use tracing::{info, trace}; + +#[derive(Parser)] +#[command( + about = env!("CARGO_PKG_DESCRIPTION"), + version = docs_rs_utils::BUILD_VERSION, + rename_all = "kebab-case", +)] +struct Cli { + #[arg(name = "SOCKET_ADDR", default_value = "0.0.0.0:3000")] + socket_addr: SocketAddr, +} + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + let _guard = docs_rs_logging::init().context("error initializing logging")?; + + let args = Cli::try_parse().context("error parsing command line args")?; + + let context = docs_rs_context::Context::new()? + .with_pool() + .await? + .with_build_queue() + .await? + .with_storage() + .await? + .with_registry_api() + .await?; + + let config = Arc::new(crate::Config::from_environment()?); + + start_web_server(args.socket_addr, config, &context).await?; + + Ok(()) +} From 8d570030b055a0f384c308131b60698c64a614cd Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 13:15:42 +0100 Subject: [PATCH 32/46] WI --- Cargo.lock | 1 + crates/bin/docs_rs_web/src/crate_details.rs | 98 ++----------------- crates/bin/docs_rs_web/src/lib.rs | 21 ++-- crates/bin/docs_rs_web/src/main.rs | 14 +-- crates/lib/docs_rs_database/Cargo.toml | 1 + .../lib/docs_rs_database/src/crate_details.rs | 97 ++++++++++++++++++ crates/lib/docs_rs_database/src/lib.rs | 1 + 7 files changed, 122 insertions(+), 111 deletions(-) create mode 100644 crates/lib/docs_rs_database/src/crate_details.rs diff --git a/Cargo.lock b/Cargo.lock index b8df2dfcf..5c11fbbc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1865,6 +1865,7 @@ version = "0.1.0" dependencies = [ "anyhow", "bincode 2.0.1", + "chrono", "derive_more", "docs_rs_env_vars", "docs_rs_opentelemetry", diff --git a/crates/bin/docs_rs_web/src/crate_details.rs b/crates/bin/docs_rs_web/src/crate_details.rs index 8e026fc79..38b85a811 100644 --- a/crates/bin/docs_rs_web/src/crate_details.rs +++ b/crates/bin/docs_rs_web/src/crate_details.rs @@ -17,8 +17,14 @@ use axum::{ }; use chrono::{DateTime, Utc}; use docs_rs_cargo_metadata::{Dependency, db::ReleaseDependencyList}; -use docs_rs_database::types::krate_name::KrateName; -use docs_rs_database::types::{BuildId, BuildStatus, CrateId, ReleaseId, version::Version}; +use docs_rs_database::{ + crate_details::latest_release, + types::{BuildId, BuildStatus, CrateId, ReleaseId, version::Version}, +}; +use docs_rs_database::{ + crate_details::{Release, parse_doc_targets}, + types::krate_name::KrateName, +}; use docs_rs_headers::CanonicalUrl; use docs_rs_registry_api::OwnerKind; use docs_rs_storage::{AsyncStorage, errors::PathNotFoundError}; @@ -76,33 +82,6 @@ struct RepositoryMetadata { name: Option, } -#[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct Release { - pub id: ReleaseId, - pub version: Version, - #[allow(clippy::doc_overindented_list_items)] - /// Aggregated build status of the release. - /// * no builds -> build In progress - /// * any build is successful -> Success - /// -> even with failed or in-progress builds we have docs to show - /// * any build is failed -> Failure - /// -> we can only have Failure or InProgress here, so the Failure is the - /// important part on this aggregation level. - /// * the rest is all builds are in-progress -> InProgress - /// -> if we have any builds, and the previous conditions don't match, we end - /// up here, but we still check. - /// - /// calculated in a database view : `release_build_status` - pub build_status: BuildStatus, - pub yanked: Option, - pub is_library: Option, - pub rustdoc_status: Option, - pub target_name: Option, - pub default_target: Option, - pub doc_targets: Option>, - pub release_time: Option>, -} - impl CrateDetails { #[tracing::instrument(skip(conn))] pub(crate) async fn from_matched_release( @@ -210,7 +189,7 @@ impl CrateDetails { rustdoc_status: krate.rustdoc_status, target_name: krate.target_name.clone(), default_target: krate.default_target, - doc_targets: krate.doc_targets.map(MetaData::parse_doc_targets), + doc_targets: krate.doc_targets.map(parse_doc_targets), yanked: krate.yanked, rustdoc_css_file: krate .rustc_version @@ -368,65 +347,6 @@ impl CrateDetails { } } -pub(crate) fn latest_release(releases: &[Release]) -> Option<&Release> { - if let Some(release) = releases.iter().find(|release| { - release.version.pre.is_empty() - && release.yanked == Some(false) - && release.build_status != BuildStatus::InProgress - }) { - Some(release) - } else { - releases - .iter() - .find(|release| release.build_status != BuildStatus::InProgress) - } -} - -/// Return all releases for a crate, sorted in descending order by semver -pub(crate) async fn releases_for_crate( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, -) -> Result, anyhow::Error> { - let mut releases: Vec = sqlx::query!( - r#"SELECT - releases.id as "id: ReleaseId", - releases.version as "version: Version", - release_build_status.build_status as "build_status!: BuildStatus", - releases.yanked, - releases.is_library, - releases.rustdoc_status, - releases.release_time, - releases.target_name, - releases.default_target, - releases.doc_targets - FROM releases - INNER JOIN release_build_status ON releases.id = release_build_status.rid - WHERE - releases.crate_id = $1"#, - crate_id.0, - ) - .fetch(&mut *conn) - .try_filter_map(|row| async move { - Ok(Some(Release { - id: row.id, - version: row.version, - build_status: row.build_status, - yanked: row.yanked, - is_library: row.is_library, - rustdoc_status: row.rustdoc_status, - target_name: row.target_name, - default_target: row.default_target, - doc_targets: row.doc_targets.map(MetaData::parse_doc_targets), - release_time: row.release_time, - })) - }) - .try_collect() - .await?; - - releases.sort_by(|a, b| b.version.cmp(&a.version)); - Ok(releases) -} - #[derive(Debug, Clone, Template)] #[template(path = "crate/details.html")] struct CrateDetailsPage { diff --git a/crates/bin/docs_rs_web/src/lib.rs b/crates/bin/docs_rs_web/src/lib.rs index 86eb3086b..eabb658b6 100644 --- a/crates/bin/docs_rs_web/src/lib.rs +++ b/crates/bin/docs_rs_web/src/lib.rs @@ -3,7 +3,10 @@ pub mod page; pub use config::Config; -use docs_rs_database::types::BuildStatus; +use docs_rs_database::{ + crate_details::{Release, parse_doc_targets, releases_for_crate}, + types::BuildStatus, +}; pub use docs_rs_utils::{BUILD_VERSION, DEFAULT_MAX_TARGETS}; pub use font_awesome_as_a_crate::icons; @@ -15,7 +18,6 @@ use axum_extra::middleware::option_layer; use docs_rs_context::Context; use docs_rs_database::types::{CrateId, krate_name::KrateName, version::Version}; use serde::Serialize; -use serde_json::Value; use tracing::{info, instrument}; mod build_details; @@ -69,7 +71,6 @@ use tower_http::{catch_panic::CatchPanicLayer, timeout::TimeoutLayer, trace::Tra use crate::metrics::WebMetrics; use docs_rs_utils::rustc_version::get_correct_docsrs_style_file; -use self::crate_details::Release; use page::GlobalAlert; // Warning message shown in the navigation bar of every page. Set to `None` to hide it. @@ -210,10 +211,10 @@ pub(crate) struct MatchedRelease { pub req_version: ReqVersion, /// the matched release - pub release: crate_details::Release, + pub release: Release, /// all releases since we have them anyways and so we can pass them to CrateDetails - pub(crate) all_releases: Vec, + pub(crate) all_releases: Vec, } impl MatchedRelease { @@ -370,7 +371,7 @@ async fn match_version( // first load and parse all versions of this crate, // `releases_for_crate` is already sorted, newest version first. - let releases = crate_details::releases_for_crate(conn, crate_id) + let releases = releases_for_crate(conn, crate_id) .await .context("error fetching releases for crate")?; @@ -716,7 +717,7 @@ impl MetaData { target_name: row.target_name, rustdoc_status: row.rustdoc_status, default_target: row.default_target, - doc_targets: row.doc_targets.map(MetaData::parse_doc_targets), + doc_targets: row.doc_targets.map(parse_doc_targets), yanked: row.yanked, rustdoc_css_file: row .rustc_version @@ -725,12 +726,6 @@ impl MetaData { .transpose()?, }) } - - fn parse_doc_targets(targets: Value) -> Vec { - let mut targets: Vec = serde_json::from_value(targets).unwrap_or_default(); - targets.sort_unstable(); - targets - } } #[derive(Template)] diff --git a/crates/bin/docs_rs_web/src/main.rs b/crates/bin/docs_rs_web/src/main.rs index 0c7a594ec..9387b6710 100644 --- a/crates/bin/docs_rs_web/src/main.rs +++ b/crates/bin/docs_rs_web/src/main.rs @@ -1,11 +1,7 @@ -use anyhow::{Context as _, Result}; +use anyhow::Context as _; use clap::Parser; -use docs_rs_build_queue::AsyncBuildQueue; -use docs_rs_database::Pool; -use docs_rs_opentelemetry::AnyMeterProvider; -use docs_rs_utils::start_async_cron; -use std::{net::SocketAddr, sync::Arc, time::Duration}; -use tracing::{info, trace}; +use docs_rs_web::{Config, run_web_server}; +use std::{net::SocketAddr, sync::Arc}; #[derive(Parser)] #[command( @@ -34,9 +30,9 @@ async fn main() -> anyhow::Result<()> { .with_registry_api() .await?; - let config = Arc::new(crate::Config::from_environment()?); + let config = Arc::new(Config::from_environment()?); - start_web_server(args.socket_addr, config, &context).await?; + run_web_server(args.socket_addr, config, &context).await?; Ok(()) } diff --git a/crates/lib/docs_rs_database/Cargo.toml b/crates/lib/docs_rs_database/Cargo.toml index 1f9e33d94..4e7c10834 100644 --- a/crates/lib/docs_rs_database/Cargo.toml +++ b/crates/lib/docs_rs_database/Cargo.toml @@ -23,6 +23,7 @@ strum = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } +chrono = { workspace = true } [features] testing = [] diff --git a/crates/lib/docs_rs_database/src/crate_details.rs b/crates/lib/docs_rs_database/src/crate_details.rs new file mode 100644 index 000000000..726bf658e --- /dev/null +++ b/crates/lib/docs_rs_database/src/crate_details.rs @@ -0,0 +1,97 @@ +use chrono::{DateTime, Utc}; +use futures_util::TryStreamExt as _; +use serde_json::Value; + +use crate::types::{BuildStatus, ReleaseId, version::Version}; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Release { + pub id: ReleaseId, + pub version: Version, + #[allow(clippy::doc_overindented_list_items)] + /// Aggregated build status of the release. + /// * no builds -> build In progress + /// * any build is successful -> Success + /// -> even with failed or in-progress builds we have docs to show + /// * any build is failed -> Failure + /// -> we can only have Failure or InProgress here, so the Failure is the + /// important part on this aggregation level. + /// * the rest is all builds are in-progress -> InProgress + /// -> if we have any builds, and the previous conditions don't match, we end + /// up here, but we still check. + /// + /// calculated in a database view : `release_build_status` + pub build_status: BuildStatus, + pub yanked: Option, + pub is_library: Option, + pub rustdoc_status: Option, + pub target_name: Option, + pub default_target: Option, + pub doc_targets: Option>, + pub release_time: Option>, +} + +pub fn parse_doc_targets(targets: Value) -> Vec { + let mut targets: Vec = serde_json::from_value(targets).unwrap_or_default(); + targets.sort_unstable(); + targets +} + +/// Return all releases for a crate, sorted in descending order by semver +pub async fn releases_for_crate( + conn: &mut sqlx::PgConnection, + crate_id: crate::types::CrateId, +) -> Result, anyhow::Error> { + let mut releases: Vec = sqlx::query!( + r#"SELECT + releases.id as "id: ReleaseId", + releases.version as "version: Version", + release_build_status.build_status as "build_status!: BuildStatus", + releases.yanked, + releases.is_library, + releases.rustdoc_status, + releases.release_time, + releases.target_name, + releases.default_target, + releases.doc_targets + FROM releases + INNER JOIN release_build_status ON releases.id = release_build_status.rid + WHERE + releases.crate_id = $1"#, + crate_id.0, + ) + .fetch(&mut *conn) + .try_filter_map(|row| async move { + Ok(Some(Release { + id: row.id, + version: row.version, + build_status: row.build_status, + yanked: row.yanked, + is_library: row.is_library, + rustdoc_status: row.rustdoc_status, + target_name: row.target_name, + default_target: row.default_target, + doc_targets: row.doc_targets.map(parse_doc_targets), + release_time: row.release_time, + })) + }) + .try_collect() + .await?; + + releases.sort_by(|a, b| b.version.cmp(&a.version)); + Ok(releases) +} + +pub fn latest_release(releases: &[Release]) -> Option<&Release> { + if let Some(release) = releases.iter().find(|release| { + release.version.pre.is_empty() + && release.yanked == Some(false) + && release.build_status != BuildStatus::InProgress + }) { + Some(release) + } else { + releases + .iter() + .find(|release| release.build_status != BuildStatus::InProgress) + } +} diff --git a/crates/lib/docs_rs_database/src/lib.rs b/crates/lib/docs_rs_database/src/lib.rs index 4ffe6f76b..e5626ee3e 100644 --- a/crates/lib/docs_rs_database/src/lib.rs +++ b/crates/lib/docs_rs_database/src/lib.rs @@ -1,4 +1,5 @@ mod config; +pub mod crate_details; pub(crate) mod migrate; pub mod mimes; pub mod service_config; From 024150abded85365c2f60e8431ea9a9d7c2f8ce4 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 13:17:04 +0100 Subject: [PATCH 33/46] fix --- .../bin/docs_rs_builder/src/db/add_package.rs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/bin/docs_rs_builder/src/db/add_package.rs b/crates/bin/docs_rs_builder/src/db/add_package.rs index f133cba33..61286f228 100644 --- a/crates/bin/docs_rs_builder/src/db/add_package.rs +++ b/crates/bin/docs_rs_builder/src/db/add_package.rs @@ -2,8 +2,9 @@ use crate::docbuilder::rustwide_builder::DocCoverage; use anyhow::{Context, Result, anyhow}; use docs_rs_cargo_metadata::Package as MetadataPackage; use docs_rs_cargo_metadata::db::ReleaseDependencyList; -use docs_rs_database::types::{ - BuildId, BuildStatus, CrateId, Feature, ReleaseId, version::Version, +use docs_rs_database::{ + crate_details::{latest_release, releases_for_crate}, + types::{BuildId, BuildStatus, CrateId, Feature, ReleaseId, version::Version}, }; use docs_rs_registry_api::{CrateData, CrateOwner, ReleaseData}; use docs_rs_storage::CompressionAlgorithm; @@ -133,17 +134,17 @@ pub async fn update_latest_version_id( crate_id: CrateId, ) -> Result<()> { todo!(); - // let releases = releases_for_crate(conn, crate_id).await?; - - // sqlx::query!( - // "UPDATE crates - // SET latest_version_id = $2 - // WHERE id = $1", - // crate_id.0, - // latest_release(&releases).map(|release| release.id.0), - // ) - // .execute(&mut *conn) - // .await?; + let releases = releases_for_crate(conn, crate_id).await?; + + sqlx::query!( + "UPDATE crates + SET latest_version_id = $2 + WHERE id = $1", + crate_id.0, + latest_release(&releases).map(|release| release.id.0), + ) + .execute(&mut *conn) + .await?; Ok(()) } From abcd36a1e840c7c713d809cdb93ec2ddf31670d9 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 13:21:01 +0100 Subject: [PATCH 34/46] fix --- crates/bin/docs_rs_web/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bin/docs_rs_web/src/main.rs b/crates/bin/docs_rs_web/src/main.rs index 9387b6710..82427a0c4 100644 --- a/crates/bin/docs_rs_web/src/main.rs +++ b/crates/bin/docs_rs_web/src/main.rs @@ -32,7 +32,7 @@ async fn main() -> anyhow::Result<()> { let config = Arc::new(Config::from_environment()?); - run_web_server(args.socket_addr, config, &context).await?; + run_web_server(Some(args.socket_addr), config, &context).await?; Ok(()) } From 9b2a029c7c92cab4787028f130b46022fc474f11 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 13:43:09 +0100 Subject: [PATCH 35/46] watcher / web binaries --- Cargo.lock | 1 + crates/bin/docs_rs_watcher/src/lib.rs | 23 +++++---- crates/lib/docs_rs_context/Cargo.toml | 1 + crates/lib/docs_rs_context/src/lib.rs | 26 +++++++++++ crates/lib/docs_rs_fastly/src/lib.rs | 67 ++++++++++++++------------- 5 files changed, 76 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c11fbbc1..04165711c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1853,6 +1853,7 @@ dependencies = [ "anyhow", "docs_rs_build_queue", "docs_rs_database", + "docs_rs_fastly", "docs_rs_opentelemetry", "docs_rs_registry_api", "docs_rs_storage", diff --git a/crates/bin/docs_rs_watcher/src/lib.rs b/crates/bin/docs_rs_watcher/src/lib.rs index 5ccac0250..f460f9a5b 100644 --- a/crates/bin/docs_rs_watcher/src/lib.rs +++ b/crates/bin/docs_rs_watcher/src/lib.rs @@ -9,11 +9,13 @@ pub mod service_metrics; mod utils; pub use config::Config; +use docs_rs_context::Context; +use crate::build_queue::get_new_crates; use anyhow::Error; use docs_rs_build_queue::AsyncBuildQueue; use std::time::Instant; -use tracing::debug; +use tracing::{debug, error}; /// Run the registry watcher /// NOTE: this should only be run once, otherwise crates would be added @@ -21,6 +23,7 @@ use tracing::debug; pub async fn watch_registry( build_queue: &AsyncBuildQueue, config: &config::Config, + context: &Context, ) -> Result<(), Error> { let mut last_gc = Instant::now(); @@ -30,15 +33,15 @@ pub async fn watch_registry( } else { debug!("Checking new crates"); let index = index::Index::from_config(config).await?; - // TODO: - // match build_queue - // .get_new_crates(&index) - // .await - // .context("Failed to get new crates") - // { - // Ok(n) => debug!("{} crates added to queue", n), - // Err(e) => report_error(&e), - // } + + let mut conn = context.pool()?.get_async().await?; + let storage = context.storage()?; + + match get_new_crates(&mut conn, &index, &build_queue, &storage, &*context.cdn()?).await + { + Ok(n) => debug!("{} crates added to queue", n), + Err(e) => error!(?e, "Failed to get new crates"), + } if last_gc.elapsed().as_secs() >= config.registry_gc_interval { index.run_git_gc().await; diff --git a/crates/lib/docs_rs_context/Cargo.toml b/crates/lib/docs_rs_context/Cargo.toml index f454c9d1a..8958886bc 100644 --- a/crates/lib/docs_rs_context/Cargo.toml +++ b/crates/lib/docs_rs_context/Cargo.toml @@ -9,5 +9,6 @@ docs_rs_database = { path = "../docs_rs_database" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_registry_api = { path = "../docs_rs_registry_api" } +docs_rs_fastly = { path = "../docs_rs_fastly" } tokio = { workspace = true } docs_rs_storage = { path = "../docs_rs_storage" } diff --git a/crates/lib/docs_rs_context/src/lib.rs b/crates/lib/docs_rs_context/src/lib.rs index 3b6a09590..1aef54c8c 100644 --- a/crates/lib/docs_rs_context/src/lib.rs +++ b/crates/lib/docs_rs_context/src/lib.rs @@ -1,6 +1,7 @@ use anyhow::{Result, anyhow}; use docs_rs_build_queue::{AsyncBuildQueue, BuildQueue}; use docs_rs_database::Pool; +use docs_rs_fastly::Cdn; use docs_rs_opentelemetry::{AnyMeterProvider, get_meter_provider}; use docs_rs_registry_api::RegistryApi; use docs_rs_storage::{AsyncStorage, Storage}; @@ -14,6 +15,7 @@ pub struct Config { database: Option>, storage: Option>, registry_api: Option>, + cdn: Option>, } pub struct Context { @@ -29,6 +31,8 @@ pub struct Context { registry_api: Option>, + cdn: Option>, + runtime: Handle, config: Config, } @@ -54,6 +58,7 @@ impl Context { storage: None, blocking_storage: None, registry_api: None, + cdn: None, }) } @@ -117,6 +122,19 @@ impl Context { self.config.registry_api = Some(Arc::new(config)); Ok(self) } + + pub async fn with_cdn(mut self) -> Result { + if self.cdn.is_some() { + return Ok(self); + } + + let config = Arc::new(docs_rs_fastly::Config::from_environment()?); + let cdn = Cdn::from_config(config.clone(), &self.meter_provider)?; + + self.cdn = Some(Arc::new(cdn)); + self.config.cdn = Some(config); + Ok(self) + } } // accessors @@ -176,4 +194,12 @@ impl Context { Err(anyhow!("Registry API is not initialized")) } } + + pub fn cdn(&self) -> Result> { + if let Some(ref cdn) = self.cdn { + Ok(cdn.clone()) + } else { + Err(anyhow!("CDN is not initialized")) + } + } } diff --git a/crates/lib/docs_rs_fastly/src/lib.rs b/crates/lib/docs_rs_fastly/src/lib.rs index 8d15dc22f..e77e81db2 100644 --- a/crates/lib/docs_rs_fastly/src/lib.rs +++ b/crates/lib/docs_rs_fastly/src/lib.rs @@ -1,10 +1,15 @@ mod config; mod metrics; -use anyhow::{Result, anyhow, bail}; +use std::sync::Arc; + +pub use config::Config; + +use anyhow::{Result, bail}; use chrono::{DateTime, TimeZone as _, Utc}; use docs_rs_database::types::krate_name::KrateName; use docs_rs_headers::{SURROGATE_KEY, SurrogateKey, SurrogateKeys}; +use docs_rs_opentelemetry::AnyMeterProvider; use docs_rs_utils::APP_USER_AGENT; use http::{ HeaderMap, HeaderName, HeaderValue, @@ -12,7 +17,6 @@ use http::{ }; use itertools::Itertools as _; use opentelemetry::KeyValue; -use std::sync::OnceLock; use tracing::error; const FASTLY_KEY: HeaderName = HeaderName::from_static("fastly-key"); @@ -22,24 +26,6 @@ const FASTLY_RATELIMIT_REMAINING: HeaderName = HeaderName::from_static("fastly-ratelimit-remaining"); const FASTLY_RATELIMIT_RESET: HeaderName = HeaderName::from_static("fastly-ratelimit-reset"); -static CLIENT: OnceLock> = OnceLock::new(); - -fn fastly_client(api_token: impl AsRef) -> anyhow::Result<&'static reqwest::Client> { - CLIENT - .get_or_init(|| -> Result<_> { - let mut headers = HeaderMap::new(); - headers.insert(USER_AGENT, HeaderValue::from_static(APP_USER_AGENT)); - headers.insert(ACCEPT, HeaderValue::from_static("application/json")); - headers.insert(FASTLY_KEY, HeaderValue::from_str(api_token.as_ref())?); - - Ok(reqwest::Client::builder() - .default_headers(headers) - .build()?) - }) - .as_ref() - .map_err(|err| anyhow!("reqwest Client init failed: {}", err)) -} - fn fetch_rate_limit_state(headers: &HeaderMap) -> (Option, Option>) { // https://www.fastly.com/documentation/reference/api/#rate-limiting ( @@ -56,11 +42,34 @@ fn fetch_rate_limit_state(headers: &HeaderMap) -> (Option, Option, metrics: metrics::CdnMetrics, } impl Cdn { + pub fn from_config( + config: Arc, + meter_provider: &AnyMeterProvider, + ) -> Result { + let Some(ref api_token) = config.api_token else { + bail!("Fastly API token not configured"); + }; + + let mut headers = HeaderMap::new(); + headers.insert(USER_AGENT, HeaderValue::from_static(APP_USER_AGENT)); + headers.insert(ACCEPT, HeaderValue::from_static("application/json")); + headers.insert(FASTLY_KEY, HeaderValue::from_str(api_token)?); + + Ok(Self { + client: reqwest::Client::builder() + .default_headers(headers) + .build()?, + config, + metrics: metrics::CdnMetrics::new(&meter_provider), + }) + } + /// Purge the given surrogate keys from all configured fastly services. /// /// Accepts any number of surrogate keys, and splits them into appropriately sized @@ -69,12 +78,6 @@ impl Cdn { where I: IntoIterator, { - let Some(api_token) = &self.config.api_token else { - bail!("Fastly API token not configured"); - }; - - let client = fastly_client(api_token)?; - let record_rate_limit_metrics = |limit_remaining: Option, limit_reset: Option>| { if let Some(limit_remaining) = limit_remaining { @@ -118,7 +121,8 @@ impl Cdn { // https://www.fastly.com/documentation/reference/api/purging/ // TODO: investigate how they could help & test // soft purge. But later, after the initial migration. - match client + match self + .client .post( self.config .api_host @@ -177,10 +181,9 @@ impl Cdn { } pub async fn queue_crate_invalidation(&self, krate_name: &KrateName) -> Result<()> { - if self.config.api_token.is_some() - && let Err(err) = self - .purge_surrogate_keys(std::iter::once(SurrogateKey::from(krate_name.clone()))) - .await + if let Err(err) = self + .purge_surrogate_keys(std::iter::once(SurrogateKey::from(krate_name.clone()))) + .await { // TODO: for now just consume & report the error, I want to see how often that happens. // We can then decide if we need more protection mechanisms (like retries or queuing). From a8df0d6d072980026d124e7a2557d52655cb9120 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 13:52:17 +0100 Subject: [PATCH 36/46] context --- crates/bin/docs_rs_watcher/src/config.rs | 5 ----- crates/bin/docs_rs_watcher/src/index.rs | 12 +++++++++++- crates/bin/docs_rs_watcher/src/main.rs | 6 +++++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/crates/bin/docs_rs_watcher/src/config.rs b/crates/bin/docs_rs_watcher/src/config.rs index fdf8603e4..7c2d0b44a 100644 --- a/crates/bin/docs_rs_watcher/src/config.rs +++ b/crates/bin/docs_rs_watcher/src/config.rs @@ -6,7 +6,6 @@ use url::Url; pub struct Config { pub registry_index_path: PathBuf, pub registry_url: Option, - pub registry_api_host: Url, /// How long to wait between registry checks pub delay_between_registry_fetches: Duration, @@ -26,10 +25,6 @@ impl Config { Ok(Self { registry_index_path: env("REGISTRY_INDEX_PATH", prefix.join("crates.io-index"))?, registry_url: maybe_env("REGISTRY_URL")?, - registry_api_host: env( - "DOCSRS_REGISTRY_API_HOST", - "https://crates.io".parse().unwrap(), - )?, delay_between_registry_fetches: Duration::from_secs(env::( "DOCSRS_DELAY_BETWEEN_REGISTRY_FETCHES", 60, diff --git a/crates/bin/docs_rs_watcher/src/index.rs b/crates/bin/docs_rs_watcher/src/index.rs index 2d17f224b..1f650d849 100644 --- a/crates/bin/docs_rs_watcher/src/index.rs +++ b/crates/bin/docs_rs_watcher/src/index.rs @@ -5,7 +5,7 @@ use std::{ path::{Path, PathBuf}, sync::{Arc, Mutex, atomic::AtomicBool}, }; -use tokio::process::Command; +use tokio::{fs, process::Command}; use tracing::error; const THREAD_NAME: &str = "crates-index-diff"; @@ -31,6 +31,16 @@ impl Index { repository_url: Option>, ) -> Result { let path = path.as_ref().to_path_buf(); + + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).await.with_context(|| { + format!( + "failed to create parent directories for registry index path: {:#?}", + parent + ) + })?; + } + let repository_url = repository_url.map(|url| url.as_ref().to_owned()); let clone_options = repository_url diff --git a/crates/bin/docs_rs_watcher/src/main.rs b/crates/bin/docs_rs_watcher/src/main.rs index e077fb60c..4cab002b2 100644 --- a/crates/bin/docs_rs_watcher/src/main.rs +++ b/crates/bin/docs_rs_watcher/src/main.rs @@ -36,6 +36,10 @@ async fn main() -> anyhow::Result<()> { let context = docs_rs_context::Context::new()? .with_pool() .await? + .with_storage() + .await? + .with_cdn() + .await? .with_build_queue() .await?; @@ -55,7 +59,7 @@ async fn main() -> anyhow::Result<()> { // metrics from the registry watcher, which should only run once, and all the time. start_background_service_metric_collector(build_queue.clone(), context.meter_provider())?; - watch_registry(&build_queue, &config).await?; + watch_registry(&build_queue, &config, &context).await?; Ok(()) } From a1515fcf807badae84b75e8bafec842bb63deb9c Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 14:07:52 +0100 Subject: [PATCH 37/46] builder executable --- Cargo.lock | 2 + crates/bin/docs_rs_builder/Cargo.toml | 3 ++ .../bin/docs_rs_builder/src/docbuilder/mod.rs | 2 +- .../src/docbuilder/rustwide_builder.rs | 2 +- crates/bin/docs_rs_builder/src/lib.rs | 7 +-- crates/bin/docs_rs_builder/src/main.rs | 45 +++++++++++++++++++ crates/bin/docs_rs_builder/src/utils/mod.rs | 2 +- 7 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 crates/bin/docs_rs_builder/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 04165711c..35949cdb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1808,6 +1808,7 @@ dependencies = [ "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_logging", "docs_rs_opentelemetry", "docs_rs_registry_api", "docs_rs_repository_stats", @@ -1831,6 +1832,7 @@ dependencies = [ "tokio", "toml 0.9.8", "tracing", + "tracing-log", ] [[package]] diff --git a/crates/bin/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml index c4a9169ad..0d0c58da5 100644 --- a/crates/bin/docs_rs_builder/Cargo.toml +++ b/crates/bin/docs_rs_builder/Cargo.toml @@ -10,6 +10,7 @@ docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } +docs_rs_logging = { path = "../../lib/docs_rs_logging" } docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry" } docs_rs_registry_api = { path = "../../lib/docs_rs_registry_api" } docs_rs_repository_stats = { path = "../../lib/docs_rs_repository_stats" } @@ -33,3 +34,5 @@ thiserror = { workspace = true } tokio = { workspace = true } toml = { workspace = true } tracing = { workspace = true } +tracing-log = "0.2.0" + diff --git a/crates/bin/docs_rs_builder/src/docbuilder/mod.rs b/crates/bin/docs_rs_builder/src/docbuilder/mod.rs index 7e5e0ff7a..311cc90ab 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/mod.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/mod.rs @@ -1 +1 @@ -pub(crate) mod rustwide_builder; +pub mod rustwide_builder; diff --git a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs index 2829b4543..a260261ed 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -199,7 +199,7 @@ pub struct RustwideBuilder { } impl RustwideBuilder { - pub fn init(config: Config, context: &Context) -> Result { + pub fn init(config: Arc, context: &Context) -> Result { let toolchain = context.runtime().block_on(async { let mut conn = context.pool()?.get_async().await?; get_configured_toolchain(&mut conn).await diff --git a/crates/bin/docs_rs_builder/src/lib.rs b/crates/bin/docs_rs_builder/src/lib.rs index dab6ef783..16021cd08 100644 --- a/crates/bin/docs_rs_builder/src/lib.rs +++ b/crates/bin/docs_rs_builder/src/lib.rs @@ -1,6 +1,7 @@ mod config; +mod db; +pub mod docbuilder; mod metrics; +pub mod utils; -mod db; -mod docbuilder; -mod utils; +pub use config::Config; diff --git a/crates/bin/docs_rs_builder/src/main.rs b/crates/bin/docs_rs_builder/src/main.rs new file mode 100644 index 000000000..a27b9bbd7 --- /dev/null +++ b/crates/bin/docs_rs_builder/src/main.rs @@ -0,0 +1,45 @@ +use anyhow::Context as _; +use docs_rs_builder::{ + Config, docbuilder::rustwide_builder::RustwideBuilder, utils::queue_builder::queue_builder, +}; +use std::sync::Arc; +use tokio::runtime; +use tracing_log::LogTracer; + +fn main() -> anyhow::Result<()> { + let _guard = docs_rs_logging::init().context("error initializing logging")?; + + // set the global log::logger for backwards compatibility + // through rustwide. + rustwide::logging::init_with(LogTracer::new()); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build()?; + + let context = runtime.block_on(async { + // handle for the current runtime from above. + let handle = runtime::Handle::current(); + Ok::<_, anyhow::Error>( + docs_rs_context::Context::new_with_runtime(handle)? + .with_pool() + .await? + .with_storage() + .await? + .with_cdn() + .await? + .with_build_queue() + .await?, + ) + })?; + + let config = Arc::new(Config::from_environment()?); + + queue_builder( + &config, + &context, + RustwideBuilder::init(config.clone(), &context)?, + )?; + + Ok(()) +} diff --git a/crates/bin/docs_rs_builder/src/utils/mod.rs b/crates/bin/docs_rs_builder/src/utils/mod.rs index c6de243c8..d8f4c2505 100644 --- a/crates/bin/docs_rs_builder/src/utils/mod.rs +++ b/crates/bin/docs_rs_builder/src/utils/mod.rs @@ -1,7 +1,7 @@ //! Various utilities for docs.rs pub(crate) mod copy; -pub(crate) mod queue_builder; +pub mod queue_builder; // #[cfg(test)] // mod tests { From 105a187a463a00497d67fdbb69cf9a7194898f80 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 14:09:52 +0100 Subject: [PATCH 38/46] kk --- NOTES.d | 4 ---- NOTES.md | 10 ++++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) delete mode 100644 NOTES.d create mode 100644 NOTES.md diff --git a/NOTES.d b/NOTES.d deleted file mode 100644 index e32a75894..000000000 --- a/NOTES.d +++ /dev/null @@ -1,4 +0,0 @@ -# validat - -* [ ] validate if some dependencies can bemoved from workspace to only crate deps -* [ ] big binary crates should not depend on each other. the shared functionality should be extracte diff --git a/NOTES.md b/NOTES.md new file mode 100644 index 000000000..205408a24 --- /dev/null +++ b/NOTES.md @@ -0,0 +1,10 @@ +# validat + +* [x] validate if some dependencies can bemoved from workspace to only crate deps +* [x] big binary crates should not depend on each other. the shared functionality should be extracte + + +## todo memaining +* [ ] re-add tests & write test helpers etc +* [ ] rewrite `build_queue_next_package` somehow nicer. Either just in the + builder with some queue-lib help, or intelligently in the queue lib From 06074ee51ef35ca91f9d5080f5d7f910b455b93a Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 15:06:05 +0100 Subject: [PATCH 39/46] kjlikj --- Cargo.lock | 1 + crates/bin/docs_rs_builder/src/config.rs | 5 - crates/lib/docs_rs_build_queue/Cargo.toml | 1 + crates/lib/docs_rs_build_queue/src/config.rs | 6 + crates/lib/docs_rs_build_queue/src/lib.rs | 181 ++++++++++++++++--- crates/lib/docs_rs_fastly/src/lib.rs | 3 +- 6 files changed, 162 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 35949cdb7..9b0f41ac7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1776,6 +1776,7 @@ dependencies = [ "chrono", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_fastly", "docs_rs_opentelemetry", "futures-util", "opentelemetry", diff --git a/crates/bin/docs_rs_builder/src/config.rs b/crates/bin/docs_rs_builder/src/config.rs index 30b5678c6..5d0a495cd 100644 --- a/crates/bin/docs_rs_builder/src/config.rs +++ b/crates/bin/docs_rs_builder/src/config.rs @@ -14,7 +14,6 @@ pub struct Config { // Build params pub(crate) build_attempts: u16, - pub(crate) delay_between_build_attempts: Duration, pub(crate) rustwide_workspace: PathBuf, pub(crate) inside_docker: bool, pub(crate) docker_image: Option, @@ -37,10 +36,6 @@ impl Config { // service_sid: maybe_env("DOCSRS_FASTLY_SERVICE_SID_WEB")?, prefix, build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, - delay_between_build_attempts: Duration::from_secs(env::( - "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", - 60, - )?), rustwide_workspace: env("DOCSRS_RUSTWIDE_WORKSPACE", PathBuf::from(".workspace"))?, inside_docker: env("DOCSRS_DOCKER", false)?, docker_image: maybe_env("DOCSRS_LOCAL_DOCKER_IMAGE")? diff --git a/crates/lib/docs_rs_build_queue/Cargo.toml b/crates/lib/docs_rs_build_queue/Cargo.toml index e6324db7d..d3a614c7f 100644 --- a/crates/lib/docs_rs_build_queue/Cargo.toml +++ b/crates/lib/docs_rs_build_queue/Cargo.toml @@ -8,6 +8,7 @@ anyhow = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_database = { path = "../docs_rs_database" } +docs_rs_fastly = { path = "../docs_rs_fastly" } opentelemetry = { workspace = true } futures-util = { workspace = true } serde = { workspace = true } diff --git a/crates/lib/docs_rs_build_queue/src/config.rs b/crates/lib/docs_rs_build_queue/src/config.rs index f1422e01f..b24f76c70 100644 --- a/crates/lib/docs_rs_build_queue/src/config.rs +++ b/crates/lib/docs_rs_build_queue/src/config.rs @@ -1,14 +1,20 @@ use docs_rs_env_vars::env; +use std::time::Duration; #[derive(Debug)] pub struct Config { pub build_attempts: u16, + pub delay_between_build_attempts: Duration, } impl Config { pub fn from_environment() -> anyhow::Result { Ok(Self { build_attempts: env("DOCSRS_BUILD_ATTEMPTS", 5u16)?, + delay_between_build_attempts: Duration::from_secs(env::( + "DOCSRS_DELAY_BETWEEN_BUILD_ATTEMPTS", + 60, + )?), }) } } diff --git a/crates/lib/docs_rs_build_queue/src/lib.rs b/crates/lib/docs_rs_build_queue/src/lib.rs index 2ebea148e..6db496d75 100644 --- a/crates/lib/docs_rs_build_queue/src/lib.rs +++ b/crates/lib/docs_rs_build_queue/src/lib.rs @@ -10,10 +10,13 @@ use docs_rs_database::{ service_config::{ConfigName, get_config, set_config}, types::version::Version, }; +use docs_rs_fastly::Cdn; use docs_rs_opentelemetry::AnyMeterProvider; use futures_util::TryStreamExt as _; -use std::{collections::HashMap, sync::Arc}; +use sqlx::Connection as _; +use std::{collections::HashMap, sync::Arc, time::Instant}; use tokio::runtime; +use tracing::error; pub const PRIORITY_DEFAULT: i32 = 0; /// Used for workspaces to avoid blocking the queue (done through the cratesfyi CLI, not used in code) @@ -42,15 +45,22 @@ pub struct QueuedCrate { #[derive(Debug)] pub struct AsyncBuildQueue { pub db: Pool, + config: Arc, + cdn: Arc, queue_metrics: metrics::BuildQueueMetrics, - max_attempts: i32, } impl AsyncBuildQueue { - pub fn new(db: Pool, config: &Config, otel_meter_provider: &AnyMeterProvider) -> Self { + pub fn new( + db: Pool, + cdn: Arc, + config: Arc, + otel_meter_provider: &AnyMeterProvider, + ) -> Self { AsyncBuildQueue { - max_attempts: config.build_attempts.into(), db, + cdn, + config, queue_metrics: metrics::BuildQueueMetrics::new(otel_meter_provider), } } @@ -115,7 +125,7 @@ impl AsyncBuildQueue { FROM queue WHERE attempt < $1 GROUP BY priority"#, - self.max_attempts, + self.config.build_attempts as i32, ) .fetch(&mut *conn) .map_ok(|row| (row.priority, row.count as usize)) @@ -128,7 +138,7 @@ impl AsyncBuildQueue { Ok(sqlx::query_scalar!( r#"SELECT COUNT(*) as "count!" FROM queue WHERE attempt >= $1;"#, - self.max_attempts, + self.config.build_attempts as i32, ) .fetch_one(&mut *conn) .await? as usize) @@ -149,7 +159,7 @@ impl AsyncBuildQueue { FROM queue WHERE attempt < $1 ORDER BY priority ASC, attempt ASC, id ASC"#, - self.max_attempts + self.config.build_attempts as i32, ) .fetch_all(&mut *conn) .await?) @@ -165,7 +175,7 @@ impl AsyncBuildQueue { name = $2 AND version = $3 ", - self.max_attempts, + self.config.build_attempts as i32, name, version as _, ) @@ -229,6 +239,119 @@ impl AsyncBuildQueue { } } +#[derive(Debug)] +pub struct BuildPackageSummary { + pub successful: bool, + pub should_reattempt: bool, +} + +#[cfg(test)] +impl Default for BuildPackageSummary { + fn default() -> Self { + Self { + successful: true, + should_reattempt: false, + } + } +} + +impl AsyncBuildQueue { + pub async fn process_next_crate( + &self, + f: impl FnOnce(&QueuedCrate) -> Result, + ) -> Result<()> { + let mut conn = self.db.get_async().await?; + let mut transaction = conn.begin().await?; + + // fetch the next available crate from the queue table. + // We are using `SELECT FOR UPDATE` inside a transaction so + // the QueuedCrate is locked until we are finished with it. + // `SKIP LOCKED` here will enable another build-server to just + // skip over taken (=locked) rows and start building the first + // available one. + let to_process = match sqlx::query_as!( + QueuedCrate, + r#"SELECT + id, + name, + version as "version: Version", + priority, + registry, + attempt + FROM queue + WHERE + attempt < $1 AND + (last_attempt IS NULL OR last_attempt < NOW() - make_interval(secs => $2)) + ORDER BY priority ASC, attempt ASC, id ASC + LIMIT 1 + FOR UPDATE SKIP LOCKED"#, + self.config.build_attempts as i32, + self.config.delay_between_build_attempts.as_secs_f64(), + ) + .fetch_optional(&mut *transaction) + .await? + { + Some(krate) => krate, + None => return Ok(()), + }; + + let res = f(&to_process); + + self.cdn + .queue_crate_invalidation(&to_process.name.parse().unwrap()) + .await?; + + async fn increase_attempt_count( + to_process: &QueuedCrate, + transaction: &mut sqlx::PgConnection, + ) -> Result { + let attempt: i32 = sqlx::query_scalar!( + "UPDATE queue + SET + attempt = attempt + 1, + last_attempt = NOW() + WHERE id = $1 + RETURNING attempt;", + to_process.id, + ) + .fetch_one(&mut *transaction) + .await?; + + Ok(attempt) + } + + match res { + Ok(BuildPackageSummary { + should_reattempt: false, + successful: _, + }) => { + sqlx::query!("DELETE FROM queue WHERE id = $1;", to_process.id) + .execute(&mut *transaction) + .await?; + } + Ok(BuildPackageSummary { + should_reattempt: true, + successful: _, + }) => { + increase_attempt_count(&to_process, &mut *transaction).await?; + } + Err(e) => { + let next_attempt = increase_attempt_count(&to_process, &mut *transaction).await?; + error!( + ?e, + name=%to_process.name, + version=%to_process.version, + next_attempt, + "Failed to build package from queue" + ); + } + } + + transaction.commit().await?; + Ok(()) + } +} + #[derive(Debug)] pub struct BuildQueue { runtime: runtime::Handle, @@ -261,27 +384,27 @@ impl BuildQueue { pub fn unlock(&self) -> Result<()> { self.runtime.block_on(self.inner.unlock()) } - // #[cfg(test)] - // pub(crate) fn pending_count(&self) -> Result { - // self.runtime.block_on(self.inner.pending_count()) - // } - // #[cfg(test)] - // pub(crate) fn prioritized_count(&self) -> Result { - // self.runtime.block_on(self.inner.prioritized_count()) - // } - // #[cfg(test)] - // pub(crate) fn pending_count_by_priority(&self) -> Result> { - // self.runtime - // .block_on(self.inner.pending_count_by_priority()) - // } - // #[cfg(test)] - // pub(crate) fn failed_count(&self) -> Result { - // self.runtime.block_on(self.inner.failed_count()) - // } - // #[cfg(test)] - // pub(crate) fn queued_crates(&self) -> Result> { - // self.runtime.block_on(self.inner.queued_crates()) - // } + #[cfg(test)] + pub(crate) fn pending_count(&self) -> Result { + self.runtime.block_on(self.inner.pending_count()) + } + #[cfg(test)] + pub(crate) fn prioritized_count(&self) -> Result { + self.runtime.block_on(self.inner.prioritized_count()) + } + #[cfg(test)] + pub(crate) fn pending_count_by_priority(&self) -> Result> { + self.runtime + .block_on(self.inner.pending_count_by_priority()) + } + #[cfg(test)] + pub(crate) fn failed_count(&self) -> Result { + self.runtime.block_on(self.inner.failed_count()) + } + #[cfg(test)] + pub(crate) fn queued_crates(&self) -> Result> { + self.runtime.block_on(self.inner.queued_crates()) + } } // #[cfg(test)] diff --git a/crates/lib/docs_rs_fastly/src/lib.rs b/crates/lib/docs_rs_fastly/src/lib.rs index e77e81db2..d9a659fe3 100644 --- a/crates/lib/docs_rs_fastly/src/lib.rs +++ b/crates/lib/docs_rs_fastly/src/lib.rs @@ -41,6 +41,7 @@ fn fetch_rate_limit_state(headers: &HeaderMap) -> (Option, Option, @@ -66,7 +67,7 @@ impl Cdn { .default_headers(headers) .build()?, config, - metrics: metrics::CdnMetrics::new(&meter_provider), + metrics: metrics::CdnMetrics::new(meter_provider), }) } From 1a855bc9af9dec330f37515718ac83d1127e1254 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 15:13:53 +0100 Subject: [PATCH 40/46] kk --- Cargo.lock | 2 +- crates/lib/docs_rs_build_queue/Cargo.toml | 2 +- crates/lib/docs_rs_build_queue/src/lib.rs | 31 +++++++--------- crates/lib/docs_rs_utils/src/lib.rs | 43 ++++++++++++++++++++++- 4 files changed, 57 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b0f41ac7..ea4196c8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1776,8 +1776,8 @@ dependencies = [ "chrono", "docs_rs_database", "docs_rs_env_vars", - "docs_rs_fastly", "docs_rs_opentelemetry", + "docs_rs_utils", "futures-util", "opentelemetry", "serde", diff --git a/crates/lib/docs_rs_build_queue/Cargo.toml b/crates/lib/docs_rs_build_queue/Cargo.toml index d3a614c7f..1ac6cb9b2 100644 --- a/crates/lib/docs_rs_build_queue/Cargo.toml +++ b/crates/lib/docs_rs_build_queue/Cargo.toml @@ -8,7 +8,7 @@ anyhow = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_database = { path = "../docs_rs_database" } -docs_rs_fastly = { path = "../docs_rs_fastly" } +docs_rs_utils = { path = "../docs_rs_utils" } opentelemetry = { workspace = true } futures-util = { workspace = true } serde = { workspace = true } diff --git a/crates/lib/docs_rs_build_queue/src/lib.rs b/crates/lib/docs_rs_build_queue/src/lib.rs index 6db496d75..55c363b5d 100644 --- a/crates/lib/docs_rs_build_queue/src/lib.rs +++ b/crates/lib/docs_rs_build_queue/src/lib.rs @@ -10,11 +10,11 @@ use docs_rs_database::{ service_config::{ConfigName, get_config, set_config}, types::version::Version, }; -use docs_rs_fastly::Cdn; use docs_rs_opentelemetry::AnyMeterProvider; +use docs_rs_utils::run_blocking; use futures_util::TryStreamExt as _; use sqlx::Connection as _; -use std::{collections::HashMap, sync::Arc, time::Instant}; +use std::{collections::HashMap, sync::Arc}; use tokio::runtime; use tracing::error; @@ -46,20 +46,13 @@ pub struct QueuedCrate { pub struct AsyncBuildQueue { pub db: Pool, config: Arc, - cdn: Arc, queue_metrics: metrics::BuildQueueMetrics, } impl AsyncBuildQueue { - pub fn new( - db: Pool, - cdn: Arc, - config: Arc, - otel_meter_provider: &AnyMeterProvider, - ) -> Self { + pub fn new(db: Pool, config: Arc, otel_meter_provider: &AnyMeterProvider) -> Self { AsyncBuildQueue { db, - cdn, config, queue_metrics: metrics::BuildQueueMetrics::new(otel_meter_provider), } @@ -258,7 +251,7 @@ impl Default for BuildPackageSummary { impl AsyncBuildQueue { pub async fn process_next_crate( &self, - f: impl FnOnce(&QueuedCrate) -> Result, + f: impl FnOnce(&QueuedCrate) -> Result + Send + 'static, ) -> Result<()> { let mut conn = self.db.get_async().await?; let mut transaction = conn.begin().await?; @@ -295,11 +288,13 @@ impl AsyncBuildQueue { None => return Ok(()), }; - let res = f(&to_process); - - self.cdn - .queue_crate_invalidation(&to_process.name.parse().unwrap()) - .await?; + // FIXME: can the closure also be async? Or is sync better? + let res = run_blocking("builder", { + let to_process = to_process.clone(); + move || f(&to_process) + }) + .await; + // FIXME: how to handle when we want to have the attepmtp count async fn increase_attempt_count( to_process: &QueuedCrate, @@ -333,10 +328,10 @@ impl AsyncBuildQueue { should_reattempt: true, successful: _, }) => { - increase_attempt_count(&to_process, &mut *transaction).await?; + increase_attempt_count(&to_process, &mut transaction).await?; } Err(e) => { - let next_attempt = increase_attempt_count(&to_process, &mut *transaction).await?; + let next_attempt = increase_attempt_count(&to_process, &mut transaction).await?; error!( ?e, name=%to_process.name, diff --git a/crates/lib/docs_rs_utils/src/lib.rs b/crates/lib/docs_rs_utils/src/lib.rs index e665134ab..0433f87c2 100644 --- a/crates/lib/docs_rs_utils/src/lib.rs +++ b/crates/lib/docs_rs_utils/src/lib.rs @@ -1,6 +1,7 @@ pub mod rustc_version; -use anyhow::Result; +use anyhow::{Context as _, Result}; +use std::fmt; use std::{panic, thread, time::Duration}; use tokio::runtime; use tracing::{Span, error, warn}; @@ -153,3 +154,43 @@ pub fn start_async_cron_in_runtime( } }); } + +/// Move the execution of a blocking function into a separate, new thread. +/// +/// Only for long-running / expensive operations that would block the async runtime or its +/// blocking workerpool. +/// +/// The rule should be: +/// * async stuff -> in the tokio runtime, other async functions +/// * blocking I/O -> `spawn_blocking` +/// * CPU-Bound things: +/// - `render_in_threadpool` (continious load like rendering) +/// - `run_blocking` (sporadic CPU bound load) +/// +/// The thread-name will help us better seeing where our CPU load is coming from on the +/// servers. +/// +/// Generally speaking, using tokio's `spawn_blocking` is also ok-ish, if the work is sporadic. +/// But then I wouldn't get thread-names. +pub async fn run_blocking(name: N, f: F) -> Result +where + N: Into + fmt::Display, + F: FnOnce() -> Result + Send + 'static, + R: Send + 'static, +{ + let name = name.into(); + let span = tracing::Span::current(); + let (send, recv) = tokio::sync::oneshot::channel(); + thread::Builder::new() + .name(format!("docsrs-{name}")) + .spawn(move || { + let _guard = span.enter(); + + // `.send` only fails when the receiver is dropped while we work, + // at which point we don't need the result anymore. + let _ = send.send(f()); + }) + .with_context(|| format!("couldn't spawn worker thread for {}", &name))?; + + recv.await.context("sender was dropped")? +} From 8685f20aded6c7ae61d6f34cdb12af4aaea09889 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 22:21:40 +0100 Subject: [PATCH 41/46] save --- .../bin/docs_rs_builder/src/db/add_package.rs | 22 +----------------- crates/bin/docs_rs_watcher/src/build_queue.rs | 3 ++- crates/bin/docs_rs_watcher/src/config.rs | 1 - crates/bin/docs_rs_watcher/src/db/delete.rs | 7 ++++-- crates/bin/docs_rs_web/src/config.rs | 6 ----- crates/lib/docs_rs_context/src/lib.rs | 10 +++++--- .../lib/docs_rs_database/src/crate_details.rs | 23 ++++++++++++++++++- 7 files changed, 37 insertions(+), 35 deletions(-) diff --git a/crates/bin/docs_rs_builder/src/db/add_package.rs b/crates/bin/docs_rs_builder/src/db/add_package.rs index 61286f228..203c5bf50 100644 --- a/crates/bin/docs_rs_builder/src/db/add_package.rs +++ b/crates/bin/docs_rs_builder/src/db/add_package.rs @@ -3,7 +3,7 @@ use anyhow::{Context, Result, anyhow}; use docs_rs_cargo_metadata::Package as MetadataPackage; use docs_rs_cargo_metadata::db::ReleaseDependencyList; use docs_rs_database::{ - crate_details::{latest_release, releases_for_crate}, + crate_details::{latest_release, releases_for_crate, update_latest_version_id}, types::{BuildId, BuildStatus, CrateId, Feature, ReleaseId, version::Version}, }; use docs_rs_registry_api::{CrateData, CrateOwner, ReleaseData}; @@ -129,26 +129,6 @@ pub(crate) async fn finish_release( Ok(()) } -pub async fn update_latest_version_id( - conn: &mut sqlx::PgConnection, - crate_id: CrateId, -) -> Result<()> { - todo!(); - let releases = releases_for_crate(conn, crate_id).await?; - - sqlx::query!( - "UPDATE crates - SET latest_version_id = $2 - WHERE id = $1", - crate_id.0, - latest_release(&releases).map(|release| release.id.0), - ) - .execute(&mut *conn) - .await?; - - Ok(()) -} - pub async fn update_build_status( conn: &mut sqlx::PgConnection, release_id: ReleaseId, diff --git a/crates/bin/docs_rs_watcher/src/build_queue.rs b/crates/bin/docs_rs_watcher/src/build_queue.rs index 63ff3e4d3..5843cd6c8 100644 --- a/crates/bin/docs_rs_watcher/src/build_queue.rs +++ b/crates/bin/docs_rs_watcher/src/build_queue.rs @@ -6,6 +6,7 @@ use crate::{ use anyhow::{Context as _, Result}; use docs_rs_build_queue::AsyncBuildQueue; use docs_rs_database::{ + crate_details::update_latest_version_id, service_config::{ConfigName, get_config, set_config}, types::{CrateId, krate_name::KrateName, version::Version}, }; @@ -193,7 +194,7 @@ async fn set_yanked_inner( %activity, "updating latest version id" ); - // FIXME: update_latest_version_id(&mut *conn, crate_id).await?; + update_latest_version_id(&mut *conn, crate_id).await?; } else { match build_queue.has_build_queued(name, version).await { Ok(false) => { diff --git a/crates/bin/docs_rs_watcher/src/config.rs b/crates/bin/docs_rs_watcher/src/config.rs index 7c2d0b44a..61f44a291 100644 --- a/crates/bin/docs_rs_watcher/src/config.rs +++ b/crates/bin/docs_rs_watcher/src/config.rs @@ -1,6 +1,5 @@ use docs_rs_env_vars::{env, maybe_env, require_env}; use std::{path::PathBuf, time::Duration}; -use url::Url; #[derive(Debug)] pub struct Config { diff --git a/crates/bin/docs_rs_watcher/src/db/delete.rs b/crates/bin/docs_rs_watcher/src/db/delete.rs index 7ed99c8d3..278c52f29 100644 --- a/crates/bin/docs_rs_watcher/src/db/delete.rs +++ b/crates/bin/docs_rs_watcher/src/db/delete.rs @@ -1,5 +1,8 @@ use anyhow::Result; -use docs_rs_database::types::{CrateId, version::Version}; +use docs_rs_database::{ + crate_details::update_latest_version_id, + types::{CrateId, version::Version}, +}; use docs_rs_storage::{AsyncStorage, rustdoc_archive_path, source_archive_path}; use sqlx::Connection; @@ -114,7 +117,7 @@ async fn delete_version_from_database( .await? .unwrap_or(false); - // FIXME: update_latest_version_id(&mut transaction, crate_id).await?; + update_latest_version_id(&mut transaction, crate_id).await?; transaction.commit().await?; Ok(is_library) diff --git a/crates/bin/docs_rs_web/src/config.rs b/crates/bin/docs_rs_web/src/config.rs index 5fd22eba6..0714f9e73 100644 --- a/crates/bin/docs_rs_web/src/config.rs +++ b/crates/bin/docs_rs_web/src/config.rs @@ -9,10 +9,6 @@ pub struct Config { // request timeout in seconds pub(crate) request_timeout: Option, pub(crate) report_request_timeouts: bool, - // - // Max size of the files served by the docs.rs frontend - pub(crate) max_file_size: usize, - pub(crate) max_file_size_html: usize, // The most memory that can be used to parse an HTML file pub(crate) max_parse_memory: usize, /// amount of threads for CPU intensive rendering @@ -48,8 +44,6 @@ impl Config { let prefix: PathBuf = require_env("DOCSRS_PREFIX")?; Ok(Self { cratesio_token: maybe_env("DOCSRS_CRATESIO_TOKEN")?, - max_file_size: env("DOCSRS_MAX_FILE_SIZE", 50 * 1024 * 1024)?, - max_file_size_html: env("DOCSRS_MAX_FILE_SIZE_HTML", 50 * 1024 * 1024)?, // LOL HTML only uses as much memory as the size of the start tag! // https://github.com/rust-lang/docs.rs/pull/930#issuecomment-667729380 max_parse_memory: env("DOCSRS_MAX_PARSE_MEMORY", 5 * 1024 * 1024)?, diff --git a/crates/lib/docs_rs_context/src/lib.rs b/crates/lib/docs_rs_context/src/lib.rs index 1aef54c8c..c43fd7999 100644 --- a/crates/lib/docs_rs_context/src/lib.rs +++ b/crates/lib/docs_rs_context/src/lib.rs @@ -83,12 +83,16 @@ impl Context { let pool = self.pool()?; - let config = docs_rs_build_queue::Config::from_environment()?; - let build_queue = Arc::new(AsyncBuildQueue::new(pool, &config, &self.meter_provider)); + let config = Arc::new(docs_rs_build_queue::Config::from_environment()?); + let build_queue = Arc::new(AsyncBuildQueue::new( + pool, + config.clone(), + &self.meter_provider, + )); let blocking_build_queue = Arc::new(BuildQueue::new(self.runtime.clone(), build_queue.clone())); - self.config.build_queue = Some(Arc::new(config)); + self.config.build_queue = Some(config); self.build_queue = Some(build_queue); self.blocking_build_queue = Some(blocking_build_queue); Ok(self) diff --git a/crates/lib/docs_rs_database/src/crate_details.rs b/crates/lib/docs_rs_database/src/crate_details.rs index 726bf658e..3adbd1234 100644 --- a/crates/lib/docs_rs_database/src/crate_details.rs +++ b/crates/lib/docs_rs_database/src/crate_details.rs @@ -1,8 +1,9 @@ +use anyhow::Result; use chrono::{DateTime, Utc}; use futures_util::TryStreamExt as _; use serde_json::Value; -use crate::types::{BuildStatus, ReleaseId, version::Version}; +use crate::types::{BuildStatus, CrateId, ReleaseId, version::Version}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct Release { @@ -95,3 +96,23 @@ pub fn latest_release(releases: &[Release]) -> Option<&Release> { .find(|release| release.build_status != BuildStatus::InProgress) } } + +pub async fn update_latest_version_id( + conn: &mut sqlx::PgConnection, + crate_id: CrateId, +) -> Result<()> { + todo!(); + let releases = releases_for_crate(conn, crate_id).await?; + + sqlx::query!( + "UPDATE crates + SET latest_version_id = $2 + WHERE id = $1", + crate_id.0, + latest_release(&releases).map(|release| release.id.0), + ) + .execute(&mut *conn) + .await?; + + Ok(()) +} From 667731af106b28b2fb0dd8cc442d7675b443cac3 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 22:36:33 +0100 Subject: [PATCH 42/46] clean --- Cargo.lock | 2 - Cargo.toml | 1 - clippy.toml | 4 - crates/bin/docs_rs_web/Cargo.toml | 1 - crates/bin/docs_rs_web/src/lib.rs | 13 +- crates/lib/docs_rs_cargo_metadata/Cargo.toml | 1 - crates/lib/docs_rs_cargo_metadata/src/db.rs | 2 +- crates/lib/docs_rs_cargo_metadata/src/lib.rs | 3 +- crates/lib/docs_rs_database/Cargo.toml | 2 +- .../lib/docs_rs_database/src/types/version.rs | 195 +++++++++--------- 10 files changed, 106 insertions(+), 118 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ea4196c8a..2114468ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1844,7 +1844,6 @@ dependencies = [ "bincode 2.0.1", "derive_more", "docs_rs_database", - "semver", "serde", "serde_json", ] @@ -2118,7 +2117,6 @@ dependencies = [ "phf_codegen 0.13.1", "rayon", "regex", - "semver", "sentry", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index fc7562b74..04c49ac7a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,6 @@ opentelemetry_sdk = { version = "0.31.0", features = ["rt-tokio"] } rayon = "1.6.1" regex = "1" reqwest = { version = "0.12", features = ["json", "gzip"] } -semver = { version = "1.0.4", features = ["serde"] } sentry = { version = "0.46.0", features = ["panic", "tracing", "tower-http", "anyhow", "backtrace"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/clippy.toml b/clippy.toml index c5f0ebf94..32a6bcb21 100644 --- a/clippy.toml +++ b/clippy.toml @@ -11,10 +11,6 @@ reason = """ async, and should be used instead. """ -[[disallowed-types]] -path = "semver::Version" -reason = "use our own custom db::types::version::Version so you can use it with sqlx" - [[disallowed-types]] path = "axum_extra::headers::IfNoneMatch" reason = "use our own custom web::headers::IfNoneMatch for sane behaviour with missing headers" diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index 59abb23de..b2e36829c 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -41,7 +41,6 @@ opentelemetry = { workspace = true } phf = "0.13.1" rayon = { workspace = true } regex = { workspace = true } -semver = { workspace = true } sentry = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/crates/bin/docs_rs_web/src/lib.rs b/crates/bin/docs_rs_web/src/lib.rs index eabb658b6..ed688c8c9 100644 --- a/crates/bin/docs_rs_web/src/lib.rs +++ b/crates/bin/docs_rs_web/src/lib.rs @@ -16,7 +16,11 @@ use anyhow::{Context as _, Result, anyhow, bail}; use askama::Template; use axum_extra::middleware::option_layer; use docs_rs_context::Context; -use docs_rs_database::types::{CrateId, krate_name::KrateName, version::Version}; +use docs_rs_database::types::{ + CrateId, + krate_name::KrateName, + version::{Error as VersionError, Version, VersionReq}, +}; use serde::Serialize; use tracing::{info, instrument}; @@ -55,7 +59,6 @@ use axum::{ use chrono::{DateTime, Utc}; use error::AxumNope; use page::TemplateData; -use semver::VersionReq; use sentry::integrations::tower as sentry_tower; use serde_with::{DeserializeFromStr, SerializeDisplay}; use std::{ @@ -137,7 +140,7 @@ impl Display for ReqVersion { } impl FromStr for ReqVersion { - type Err = semver::Error; + type Err = VersionError; fn from_str(s: &str) -> Result { if s == "latest" { Ok(ReqVersion::Latest) @@ -182,7 +185,7 @@ impl From<&VersionReq> for ReqVersion { } impl TryFrom for ReqVersion { - type Error = semver::Error; + type Error = VersionError; fn try_from(value: String) -> Result { value.parse() @@ -190,7 +193,7 @@ impl TryFrom for ReqVersion { } impl TryFrom<&str> for ReqVersion { - type Error = semver::Error; + type Error = VersionError; fn try_from(value: &str) -> Result { value.parse() diff --git a/crates/lib/docs_rs_cargo_metadata/Cargo.toml b/crates/lib/docs_rs_cargo_metadata/Cargo.toml index cf9e881e3..d4f944c4d 100644 --- a/crates/lib/docs_rs_cargo_metadata/Cargo.toml +++ b/crates/lib/docs_rs_cargo_metadata/Cargo.toml @@ -7,7 +7,6 @@ edition = "2024" anyhow = { workspace = true } bincode = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } -semver = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } derive_more = { workspace = true } diff --git a/crates/lib/docs_rs_cargo_metadata/src/db.rs b/crates/lib/docs_rs_cargo_metadata/src/db.rs index 636a4aa0f..b71f448ed 100644 --- a/crates/lib/docs_rs_cargo_metadata/src/db.rs +++ b/crates/lib/docs_rs_cargo_metadata/src/db.rs @@ -1,6 +1,6 @@ use super::Dependency; use derive_more::Deref; -use semver::VersionReq; +use docs_rs_database::types::version::VersionReq; use serde::{Deserialize, Serialize}; const DEFAULT_KIND: &str = "normal"; diff --git a/crates/lib/docs_rs_cargo_metadata/src/lib.rs b/crates/lib/docs_rs_cargo_metadata/src/lib.rs index ff027b78f..17cf5befc 100644 --- a/crates/lib/docs_rs_cargo_metadata/src/lib.rs +++ b/crates/lib/docs_rs_cargo_metadata/src/lib.rs @@ -1,8 +1,7 @@ pub mod db; use anyhow::{Context, Result}; -use docs_rs_database::types::version::Version; -use semver::VersionReq; +use docs_rs_database::types::version::{Version, VersionReq}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[cfg(test)] diff --git a/crates/lib/docs_rs_database/Cargo.toml b/crates/lib/docs_rs_database/Cargo.toml index 4e7c10834..c5a727b92 100644 --- a/crates/lib/docs_rs_database/Cargo.toml +++ b/crates/lib/docs_rs_database/Cargo.toml @@ -14,8 +14,8 @@ futures-util = { workspace = true } mime = { workspace = true } mime_guess = "2" opentelemetry = { workspace = true } -semver = { workspace = true } serde = { workspace = true } +semver = { version = "1.0.4", features = ["serde"] } serde_json = { workspace = true } serde_with = { workspace = true } sqlx = { workspace = true } diff --git a/crates/lib/docs_rs_database/src/types/version.rs b/crates/lib/docs_rs_database/src/types/version.rs index 63cdbd921..b35ee0c55 100644 --- a/crates/lib/docs_rs_database/src/types/version.rs +++ b/crates/lib/docs_rs_database/src/types/version.rs @@ -1,123 +1,118 @@ -// FIXME: we might be able to drop this disallowed-types thing when -// the rest of the codebase just uses this subcrate? -#[allow(clippy::disallowed_types)] -mod version_impl { - use anyhow::Result; - use derive_more::{Deref, Display, From, Into}; - use serde_with::{DeserializeFromStr, SerializeDisplay}; - use sqlx::{ - Postgres, - encode::IsNull, - error::BoxDynError, - postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef}, - prelude::*, - }; - use std::{io::Write, str::FromStr}; - - /// NewType around semver::Version to be able to use it with sqlx. - /// - /// Represented as string in the database. - #[derive( - Clone, - Debug, - Deref, - DeserializeFromStr, - Display, - Eq, - From, - Hash, - Into, - PartialEq, - SerializeDisplay, - )] - pub struct Version(pub semver::Version); - - impl Version { - pub const fn new(major: u64, minor: u64, patch: u64) -> Self { - Self(semver::Version::new(major, minor, patch)) - } - - pub fn parse(text: &str) -> Result { - Version::from_str(text) - } +use anyhow::Result; +use derive_more::{Deref, Display, From, Into}; +use serde_with::{DeserializeFromStr, SerializeDisplay}; +use sqlx::{ + Postgres, + encode::IsNull, + error::BoxDynError, + postgres::{PgArgumentBuffer, PgTypeInfo, PgValueRef}, + prelude::*, +}; +use std::{io::Write, str::FromStr}; + +pub use semver::{Error, VersionReq}; + +/// NewType around semver::Version to be able to use it with sqlx. +/// +/// Represented as string in the database. +#[derive( + Clone, + Debug, + Deref, + DeserializeFromStr, + Display, + Eq, + From, + Hash, + Into, + PartialEq, + SerializeDisplay, +)] +pub struct Version(pub semver::Version); + +impl Version { + pub const fn new(major: u64, minor: u64, patch: u64) -> Self { + Self(semver::Version::new(major, minor, patch)) } - impl bincode::Encode for Version { - fn encode( - &self, - encoder: &mut E, - ) -> Result<(), bincode::error::EncodeError> { - let Self(semver::Version { - major, - minor, - patch, - pre: _, - build: _, - }) = self; - major.encode(encoder)?; - minor.encode(encoder)?; - patch.encode(encoder)?; - bincode::Encode::encode(self.0.pre.as_str(), encoder)?; - bincode::Encode::encode(self.0.build.as_str(), encoder)?; - Ok(()) - } + pub fn parse(text: &str) -> Result { + Version::from_str(text) } +} - impl Type for Version { - fn type_info() -> PgTypeInfo { - >::type_info() - } - - fn compatible(ty: &PgTypeInfo) -> bool { - >::compatible(ty) - } +impl bincode::Encode for Version { + fn encode( + &self, + encoder: &mut E, + ) -> Result<(), bincode::error::EncodeError> { + let Self(semver::Version { + major, + minor, + patch, + pre: _, + build: _, + }) = self; + major.encode(encoder)?; + minor.encode(encoder)?; + patch.encode(encoder)?; + bincode::Encode::encode(self.0.pre.as_str(), encoder)?; + bincode::Encode::encode(self.0.build.as_str(), encoder)?; + Ok(()) } +} - impl<'q> Encode<'q, Postgres> for Version { - fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { - write!(**buf, "{}", self.0)?; - Ok(IsNull::No) - } +impl Type for Version { + fn type_info() -> PgTypeInfo { + >::type_info() } - impl<'r> Decode<'r, Postgres> for Version { - fn decode(value: PgValueRef<'r>) -> Result { - let s: &str = Decode::::decode(value)?; - Ok(Self(s.parse()?)) - } + fn compatible(ty: &PgTypeInfo) -> bool { + >::compatible(ty) } +} - impl FromStr for Version { - type Err = semver::Error; +impl<'q> Encode<'q, Postgres> for Version { + fn encode_by_ref(&self, buf: &mut PgArgumentBuffer) -> Result { + write!(**buf, "{}", self.0)?; + Ok(IsNull::No) + } +} - fn from_str(s: &str) -> Result { - Ok(Version(semver::Version::from_str(s)?)) - } +impl<'r> Decode<'r, Postgres> for Version { + fn decode(value: PgValueRef<'r>) -> Result { + let s: &str = Decode::::decode(value)?; + Ok(Self(s.parse()?)) } +} - impl TryFrom<&str> for Version { - type Error = semver::Error; +impl FromStr for Version { + type Err = semver::Error; - fn try_from(value: &str) -> Result { - Ok(Version(semver::Version::from_str(value)?)) - } + fn from_str(s: &str) -> Result { + Ok(Version(semver::Version::from_str(s)?)) } +} - impl TryFrom<&String> for Version { - type Error = semver::Error; +impl TryFrom<&str> for Version { + type Error = semver::Error; - fn try_from(value: &String) -> Result { - Ok(Version(semver::Version::from_str(value)?)) - } + fn try_from(value: &str) -> Result { + Ok(Version(semver::Version::from_str(value)?)) } +} - impl TryFrom for Version { - type Error = semver::Error; +impl TryFrom<&String> for Version { + type Error = semver::Error; - fn try_from(value: String) -> Result { - Ok(Version(semver::Version::from_str(&value)?)) - } + fn try_from(value: &String) -> Result { + Ok(Version(semver::Version::from_str(value)?)) } } -pub use version_impl::Version; +impl TryFrom for Version { + type Error = semver::Error; + + fn try_from(value: String) -> Result { + Ok(Version(semver::Version::from_str(&value)?)) + } +} From ccfe563c431ff12769a5bb858c5637fd6eadf73b Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Fri, 12 Dec 2025 22:46:04 +0100 Subject: [PATCH 43/46] cleanup --- Cargo.lock | 1 - .../bin/docs_rs_builder/src/db/add_package.rs | 2 +- .../src/docbuilder/rustwide_builder.rs | 2 +- crates/bin/docs_rs_builder/src/main.rs | 6 ++---- crates/bin/docs_rs_watcher/Cargo.toml | 6 ++---- crates/bin/docs_rs_watcher/src/lib.rs | 2 +- crates/bin/docs_rs_web/Cargo.toml | 7 +++---- crates/lib/docs_rs_build_queue/Cargo.toml | 8 ++++---- crates/lib/docs_rs_cargo_metadata/Cargo.toml | 2 +- crates/lib/docs_rs_context/Cargo.toml | 6 +++--- crates/lib/docs_rs_database/Cargo.toml | 4 ++-- crates/lib/docs_rs_fastly/Cargo.toml | 19 +++++++++---------- crates/lib/docs_rs_headers/Cargo.toml | 14 +++++++------- crates/lib/docs_rs_storage/Cargo.toml | 4 ++-- crates/lib/docs_rs_utils/Cargo.toml | 6 +++--- crates/lib/docs_rs_web_utils/Cargo.toml | 4 ++-- crates/lib/metadata/Cargo.toml | 8 +++----- 17 files changed, 46 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2114468ec..8bffc0ddf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2070,7 +2070,6 @@ dependencies = [ "sqlx", "tokio", "tracing", - "url", ] [[package]] diff --git a/crates/bin/docs_rs_builder/src/db/add_package.rs b/crates/bin/docs_rs_builder/src/db/add_package.rs index 203c5bf50..a4809f628 100644 --- a/crates/bin/docs_rs_builder/src/db/add_package.rs +++ b/crates/bin/docs_rs_builder/src/db/add_package.rs @@ -3,7 +3,7 @@ use anyhow::{Context, Result, anyhow}; use docs_rs_cargo_metadata::Package as MetadataPackage; use docs_rs_cargo_metadata::db::ReleaseDependencyList; use docs_rs_database::{ - crate_details::{latest_release, releases_for_crate, update_latest_version_id}, + crate_details::update_latest_version_id, types::{BuildId, BuildStatus, CrateId, Feature, ReleaseId, version::Version}, }; use docs_rs_registry_api::{CrateData, CrateOwner, ReleaseData}; diff --git a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs index a260261ed..8f40f81f7 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -208,7 +208,7 @@ impl RustwideBuilder { Ok(RustwideBuilder { workspace: build_workspace(&config)?, toolchain, - config: config.into(), + config, db: context.pool()?.clone(), runtime: context.runtime().clone(), storage: context.blocking_storage()?, diff --git a/crates/bin/docs_rs_builder/src/main.rs b/crates/bin/docs_rs_builder/src/main.rs index a27b9bbd7..b284632cb 100644 --- a/crates/bin/docs_rs_builder/src/main.rs +++ b/crates/bin/docs_rs_builder/src/main.rs @@ -20,8 +20,7 @@ fn main() -> anyhow::Result<()> { let context = runtime.block_on(async { // handle for the current runtime from above. let handle = runtime::Handle::current(); - Ok::<_, anyhow::Error>( - docs_rs_context::Context::new_with_runtime(handle)? + docs_rs_context::Context::new_with_runtime(handle)? .with_pool() .await? .with_storage() @@ -29,8 +28,7 @@ fn main() -> anyhow::Result<()> { .with_cdn() .await? .with_build_queue() - .await?, - ) + .await })?; let config = Arc::new(Config::from_environment()?); diff --git a/crates/bin/docs_rs_watcher/Cargo.toml b/crates/bin/docs_rs_watcher/Cargo.toml index 916e1cb7d..9ae62747b 100644 --- a/crates/bin/docs_rs_watcher/Cargo.toml +++ b/crates/bin/docs_rs_watcher/Cargo.toml @@ -8,14 +8,14 @@ anyhow = { workspace = true } clap = { workspace = true } crates-index = { version = "3.0.0", default-features = false, features = ["git", "git-https", "git-performance", "parallel"] } crates-index-diff = { version = "28.0.0", features = [ "max-performance" ]} -docs_rs_fastly = { path = "../../lib/docs_rs_fastly" } docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } docs_rs_env_vars = { path = "../../lib/docs_rs_env_vars" } +docs_rs_fastly = { path = "../../lib/docs_rs_fastly" } docs_rs_logging = { path = "../../lib/docs_rs_logging" } -docs_rs_repository_stats = { path = "../../lib/docs_rs_repository_stats" } docs_rs_opentelemetry = { path = "../../lib/docs_rs_opentelemetry" } +docs_rs_repository_stats = { path = "../../lib/docs_rs_repository_stats" } docs_rs_storage = { path = "../../lib/docs_rs_storage" } docs_rs_utils = { path = "../../lib/docs_rs_utils" } futures-util = { workspace = true } @@ -25,8 +25,6 @@ rayon = { workspace = true } sqlx = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } -url = { workspace = true } - [dev-dependencies] mockito = { workspace = true } diff --git a/crates/bin/docs_rs_watcher/src/lib.rs b/crates/bin/docs_rs_watcher/src/lib.rs index f460f9a5b..bbfaa2e5d 100644 --- a/crates/bin/docs_rs_watcher/src/lib.rs +++ b/crates/bin/docs_rs_watcher/src/lib.rs @@ -37,7 +37,7 @@ pub async fn watch_registry( let mut conn = context.pool()?.get_async().await?; let storage = context.storage()?; - match get_new_crates(&mut conn, &index, &build_queue, &storage, &*context.cdn()?).await + match get_new_crates(&mut conn, &index, build_queue, &storage, &*context.cdn()?).await { Ok(n) => debug!("{} crates added to queue", n), Err(e) => error!(?e, "Failed to get new crates"), diff --git a/crates/bin/docs_rs_web/Cargo.toml b/crates/bin/docs_rs_web/Cargo.toml index b2e36829c..09d50f4fc 100644 --- a/crates/bin/docs_rs_web/Cargo.toml +++ b/crates/bin/docs_rs_web/Cargo.toml @@ -61,18 +61,17 @@ slug = { workspace = true } getrandom = "0.3.1" clap = { workspace = true } - [build-dependencies] anyhow = { workspace = true } -syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } -walkdir = { workspace = true } grass = { version = "0.13.1", default-features = false } md5 = "0.8.0" phf_codegen = "0.13" +syntect = { version = "5.0.0", default-features = false, features = ["parsing", "dump-create", "yaml-load", "regex-onig"] } +walkdir = { workspace = true } [package.metadata.cargo-machete] ignored = [ - "slug",# used in HTML templates, where machete doesn't look inside "phf", # used in code that's generated by the build-script + "slug",# used in HTML templates, where machete doesn't look inside ] diff --git a/crates/lib/docs_rs_build_queue/Cargo.toml b/crates/lib/docs_rs_build_queue/Cargo.toml index 1ac6cb9b2..9e3c36d69 100644 --- a/crates/lib/docs_rs_build_queue/Cargo.toml +++ b/crates/lib/docs_rs_build_queue/Cargo.toml @@ -5,14 +5,14 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +chrono = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } -docs_rs_database = { path = "../docs_rs_database" } docs_rs_utils = { path = "../docs_rs_utils" } -opentelemetry = { workspace = true } futures-util = { workspace = true } +opentelemetry = { workspace = true } serde = { workspace = true } sqlx = { workspace = true } -tracing = { workspace = true } -chrono = { workspace = true } tokio = { workspace = true } +tracing = { workspace = true } diff --git a/crates/lib/docs_rs_cargo_metadata/Cargo.toml b/crates/lib/docs_rs_cargo_metadata/Cargo.toml index d4f944c4d..cff58e977 100644 --- a/crates/lib/docs_rs_cargo_metadata/Cargo.toml +++ b/crates/lib/docs_rs_cargo_metadata/Cargo.toml @@ -6,7 +6,7 @@ edition = "2024" [dependencies] anyhow = { workspace = true } bincode = { workspace = true } +derive_more = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } serde = { workspace = true } serde_json = { workspace = true } -derive_more = { workspace = true } diff --git a/crates/lib/docs_rs_context/Cargo.toml b/crates/lib/docs_rs_context/Cargo.toml index 8958886bc..26b85c901 100644 --- a/crates/lib/docs_rs_context/Cargo.toml +++ b/crates/lib/docs_rs_context/Cargo.toml @@ -5,10 +5,10 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_database = { path = "../docs_rs_database" } +docs_rs_fastly = { path = "../docs_rs_fastly" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } -docs_rs_build_queue = { path = "../docs_rs_build_queue" } docs_rs_registry_api = { path = "../docs_rs_registry_api" } -docs_rs_fastly = { path = "../docs_rs_fastly" } -tokio = { workspace = true } docs_rs_storage = { path = "../docs_rs_storage" } +tokio = { workspace = true } diff --git a/crates/lib/docs_rs_database/Cargo.toml b/crates/lib/docs_rs_database/Cargo.toml index c5a727b92..86b930880 100644 --- a/crates/lib/docs_rs_database/Cargo.toml +++ b/crates/lib/docs_rs_database/Cargo.toml @@ -7,6 +7,7 @@ build = "build.rs" [dependencies] anyhow = { workspace = true } bincode = { workspace = true } +chrono = { workspace = true } derive_more = { workspace = true } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } @@ -14,8 +15,8 @@ futures-util = { workspace = true } mime = { workspace = true } mime_guess = "2" opentelemetry = { workspace = true } -serde = { workspace = true } semver = { version = "1.0.4", features = ["serde"] } +serde = { workspace = true } serde_json = { workspace = true } serde_with = { workspace = true } sqlx = { workspace = true } @@ -23,7 +24,6 @@ strum = { workspace = true } thiserror = { workspace = true } tokio = { workspace = true } tracing = { workspace = true } -chrono = { workspace = true } [features] testing = [] diff --git a/crates/lib/docs_rs_fastly/Cargo.toml b/crates/lib/docs_rs_fastly/Cargo.toml index 6ab8f8e37..98b0e4dab 100644 --- a/crates/lib/docs_rs_fastly/Cargo.toml +++ b/crates/lib/docs_rs_fastly/Cargo.toml @@ -4,17 +4,16 @@ version = "0.1.0" edition = "2024" [dependencies] -docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } -docs_rs_utils = { path = "../docs_rs_utils" } +anyhow = { workspace = true } +chrono = { workspace = true } +docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } docs_rs_headers = { path = "../docs_rs_headers" } -docs_rs_database = { path = "../docs_rs_database" } -tracing = { workspace = true } -url = { workspace = true } -opentelemetry = { workspace = true } -chrono = { workspace = true } -anyhow = { workspace = true } +docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } +docs_rs_utils = { path = "../docs_rs_utils" } +http = { workspace = true } itertools = { workspace = true } +opentelemetry = { workspace = true } reqwest = { workspace = true } -http = { workspace = true } - +tracing = { workspace = true } +url = { workspace = true } diff --git a/crates/lib/docs_rs_headers/Cargo.toml b/crates/lib/docs_rs_headers/Cargo.toml index 0072408f7..0e6927139 100644 --- a/crates/lib/docs_rs_headers/Cargo.toml +++ b/crates/lib/docs_rs_headers/Cargo.toml @@ -4,13 +4,13 @@ version = "0.1.0" edition = "2024" [dependencies] -md5 = "0.8.0" -headers = "0.4.1" # not sure if we want this in this crate -http = { workspace = true } -derive_more = { workspace = true } -serde = { workspace = true } anyhow = { workspace = true } -itertools = { workspace = true } +askama = { workspace = true } +derive_more = { workspace = true } docs_rs_database = { path = "../docs_rs_database" } docs_rs_web_utils = { path = "../docs_rs_web_utils" } -askama = { workspace = true } +headers = "0.4.1" # not sure if we want this in this crate +http = { workspace = true } +itertools = { workspace = true } +md5 = "0.8.0" +serde = { workspace = true } diff --git a/crates/lib/docs_rs_storage/Cargo.toml b/crates/lib/docs_rs_storage/Cargo.toml index bc95f66ab..1cfe3f296 100644 --- a/crates/lib/docs_rs_storage/Cargo.toml +++ b/crates/lib/docs_rs_storage/Cargo.toml @@ -15,9 +15,9 @@ chrono = { workspace = true } dashmap = "6.0.0" docs_rs_database = { path = "../docs_rs_database" } docs_rs_env_vars = { path = "../docs_rs_env_vars" } +docs_rs_headers = { path = "../docs_rs_headers" } docs_rs_opentelemetry = { path = "../docs_rs_opentelemetry" } docs_rs_utils = { path = "../docs_rs_utils" } -docs_rs_headers = { path = "../docs_rs_headers" } flate2 = "1.1.1" futures-util = { workspace = true } http = { workspace = true } @@ -25,6 +25,7 @@ itertools = { workspace = true } mime = { workspace = true } opentelemetry = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } sqlx = { workspace = true } strum = { workspace = true } tempfile = { workspace = true } @@ -34,7 +35,6 @@ tracing = { workspace = true } walkdir = { workspace = true } zip = {version = "6.0.0", default-features = false, features = ["bzip2"]} zstd = "0.13.0" -serde_json = { workspace = true } [dev-dependencies] criterion = "0.8.0" diff --git a/crates/lib/docs_rs_utils/Cargo.toml b/crates/lib/docs_rs_utils/Cargo.toml index 5dce8b6d9..39b6545a9 100644 --- a/crates/lib/docs_rs_utils/Cargo.toml +++ b/crates/lib/docs_rs_utils/Cargo.toml @@ -6,13 +6,13 @@ build = "build.rs" [dependencies] anyhow = { workspace = true } -tokio = { workspace = true } -tracing = { workspace = true } chrono = { workspace = true } regex = { workspace = true } +tokio = { workspace = true } +tracing = { workspace = true } [build-dependencies] anyhow = { workspace = true } chrono = { workspace = true } -tokio = { workspace = true } time = "0.3" +tokio = { workspace = true } diff --git a/crates/lib/docs_rs_web_utils/Cargo.toml b/crates/lib/docs_rs_web_utils/Cargo.toml index 6b484b5e9..9044f6b24 100644 --- a/crates/lib/docs_rs_web_utils/Cargo.toml +++ b/crates/lib/docs_rs_web_utils/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2024" [dependencies] -percent-encoding = "2.2.0" anyhow = { workspace = true } askama = { workspace = true } +bincode = { workspace = true } http = { workspace = true } +percent-encoding = "2.2.0" url = { workspace = true } -bincode = { workspace = true } diff --git a/crates/lib/metadata/Cargo.toml b/crates/lib/metadata/Cargo.toml index f5942d426..e87bd3675 100644 --- a/crates/lib/metadata/Cargo.toml +++ b/crates/lib/metadata/Cargo.toml @@ -7,12 +7,10 @@ license = "MIT" repository = "https://github.com/rust-lang/docs.rs" description = "Document crates the same way docs.rs would" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [lib] path = "lib.rs" [dependencies] -serde = { version = "1.0", features = ["derive"] } -toml = "0.9" -thiserror = "2" +serde = { workspace = true } +toml = { workspace = true } +thiserror = { workspace = true } From decfd34e520776f8ef30555935265039d100b854 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 13 Dec 2025 00:02:22 +0100 Subject: [PATCH 44/46] kk --- Cargo.lock | 1 + crates/bin/docs_rs_builder/Cargo.toml | 1 + .../src/docbuilder/rustwide_builder.rs | 19 +- .../src/utils/queue_builder.rs | 112 ++++++++--- crates/lib/docs_rs_build_queue/src/lib.rs | 190 +++++++++--------- 5 files changed, 177 insertions(+), 146 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8bffc0ddf..f0de631a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1804,6 +1804,7 @@ name = "docs_rs_builder" version = "0.1.0" dependencies = [ "anyhow", + "docs_rs_build_queue", "docs_rs_build_utils", "docs_rs_cargo_metadata", "docs_rs_context", diff --git a/crates/bin/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml index 0d0c58da5..8c6f6fc34 100644 --- a/crates/bin/docs_rs_builder/Cargo.toml +++ b/crates/bin/docs_rs_builder/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] anyhow = { workspace = true } +docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } docs_rs_build_utils = { path = "../../lib/docs_rs_build_utils" } docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } docs_rs_context = { path = "../../lib/docs_rs_context" } diff --git a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs index 8f40f81f7..aadbc513e 100644 --- a/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs +++ b/crates/bin/docs_rs_builder/src/docbuilder/rustwide_builder.rs @@ -11,6 +11,7 @@ use crate::{ utils::copy::copy_dir_all, }; use anyhow::{Context as _, Error, Result, anyhow, bail}; +use docs_rs_build_queue::BuildPackageSummary; use docs_rs_build_utils::limits::Limits; use docs_rs_cargo_metadata::{CargoMetadata, Package as MetadataPackage}; use docs_rs_context::Context; @@ -195,7 +196,7 @@ pub struct RustwideBuilder { registry_api: Arc, repository_stats_updater: Arc, workspace_initialize_time: Instant, - builder_metrics: Arc, + pub(crate) builder_metrics: Arc, } impl RustwideBuilder { @@ -1436,22 +1437,6 @@ pub(crate) struct BuildResult { pub(crate) successful: bool, } -#[derive(Debug)] -pub struct BuildPackageSummary { - pub successful: bool, - pub should_reattempt: bool, -} - -#[cfg(test)] -impl Default for BuildPackageSummary { - fn default() -> Self { - Self { - successful: true, - should_reattempt: false, - } - } -} - // #[cfg(test)] // mod tests { // use super::*; diff --git a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs index dbe211b26..034976199 100644 --- a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs +++ b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs @@ -1,15 +1,21 @@ -use std::path::Path; - use crate::config::Config; -use crate::docbuilder::rustwide_builder::RustwideBuilder; +use crate::docbuilder::rustwide_builder::{PackageKind, RustwideBuilder}; use anyhow::{Context as _, Result}; +use docs_rs_build_queue::BuildQueue; use docs_rs_context::Context; -use std::time::Duration; +use docs_rs_utils::retry; +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::path::Path; +use std::time::{Duration, Instant}; use std::{fs, io, thread}; -use tracing::{error, warn}; +use tracing::{debug, error, warn}; /// the main build-server loop -pub fn queue_builder(config: &Config, context: &Context, builder: RustwideBuilder) -> Result<()> { +pub fn queue_builder( + config: &Config, + context: &Context, + mut builder: RustwideBuilder, +) -> Result<()> { loop { let temp_dir = &config.temp_dir; if temp_dir.exists() @@ -35,32 +41,80 @@ pub fn queue_builder(config: &Config, context: &Context, builder: RustwideBuilde } } - //TODO: Build this with a new way - - // // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. - // debug!("Checking build queue"); - // let res = catch_unwind(AssertUnwindSafe(|| { - // match build_queue.build_next_queue_package(context, &mut builder) { - // Ok(true) => {} - // Ok(false) => { - // debug!("Queue is empty, going back to sleep"); - // thread::sleep(Duration::from_secs(60)); - // } - // Err(e) => { - // report_error(&e.context("Failed to build crate from queue")); - // } - // } - // })); - - // if let Err(e) = res { - // error!("GRAVE ERROR Building new crates panicked: {:?}", e); - // thread::sleep(Duration::from_secs(60)); - // continue; - // } - return Ok(()); + // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. + debug!("Checking build queue"); + let res = catch_unwind(AssertUnwindSafe(|| { + match build_next_queue_package(&build_queue, &mut builder) { + Ok(true) => {} + Ok(false) => { + debug!("Queue is empty, going back to sleep"); + thread::sleep(Duration::from_secs(60)); + } + Err(e) => { + error!(?e, "Failed to build crate from queue"); + } + } + })); + + if let Err(e) = res { + error!("GRAVE ERROR Building new crates panicked: {:?}", e); + thread::sleep(Duration::from_secs(60)); + continue; + } } } +/// Builds the top package from the queue. Returns whether there was a package in the queue. +/// +/// Note that this will return `Ok(true)` even if the package failed to build. +fn build_next_queue_package( + build_queue: &BuildQueue, + builder: &mut RustwideBuilder, +) -> Result { + let mut processed = false; + + build_queue.process_next_crate(|krate| { + processed = true; + + let kind = krate + .registry + .as_ref() + .map(|r| PackageKind::Registry(r.as_str())) + .unwrap_or(PackageKind::CratesIo); + + if let Err(err) = retry(|| builder.reinitialize_workspace_if_interval_passed(), 3) { + error!(?err, "Reinitialize workspace failed, locking queue"); + build_queue.lock()?; + return Err(err); + } + + if let Err(err) = builder.update_toolchain_and_add_essential_files() { + error!(?err, "Updating toolchain failed, locking queue"); + build_queue.lock()?; + return Err(err); + } + + let instant = Instant::now(); + + let res = builder.build_package(&krate.name, &krate.version, kind, krate.attempt == 0); + + let elapsed = instant.elapsed().as_secs_f64(); + builder.builder_metrics.build_time.record(elapsed, &[]); + builder.builder_metrics.total_builds.add(1, &[]); + // self.runtime + // .block_on(self.inner.queue_crate_invalidation(&to_process.name)); + + res + })?; + + // in case of errors? + // if attempt >= self.inner.config.build_attempts as i32 { + // self.inner.builder_metrics.failed_builds.add(1, &[]); + // } + + Ok(processed) +} + /// Sometimes, when the server hits a hard crash or a build thread panics, /// rustwide_builder won't actually remove the temporary directories it creates. /// Remove them now to avoid running out of disk space. diff --git a/crates/lib/docs_rs_build_queue/src/lib.rs b/crates/lib/docs_rs_build_queue/src/lib.rs index 55c363b5d..09add3d02 100644 --- a/crates/lib/docs_rs_build_queue/src/lib.rs +++ b/crates/lib/docs_rs_build_queue/src/lib.rs @@ -14,7 +14,7 @@ use docs_rs_opentelemetry::AnyMeterProvider; use docs_rs_utils::run_blocking; use futures_util::TryStreamExt as _; use sqlx::Connection as _; -use std::{collections::HashMap, sync::Arc}; +use std::{collections::HashMap, sync::Arc, time::Instant}; use tokio::runtime; use tracing::error; @@ -248,105 +248,6 @@ impl Default for BuildPackageSummary { } } -impl AsyncBuildQueue { - pub async fn process_next_crate( - &self, - f: impl FnOnce(&QueuedCrate) -> Result + Send + 'static, - ) -> Result<()> { - let mut conn = self.db.get_async().await?; - let mut transaction = conn.begin().await?; - - // fetch the next available crate from the queue table. - // We are using `SELECT FOR UPDATE` inside a transaction so - // the QueuedCrate is locked until we are finished with it. - // `SKIP LOCKED` here will enable another build-server to just - // skip over taken (=locked) rows and start building the first - // available one. - let to_process = match sqlx::query_as!( - QueuedCrate, - r#"SELECT - id, - name, - version as "version: Version", - priority, - registry, - attempt - FROM queue - WHERE - attempt < $1 AND - (last_attempt IS NULL OR last_attempt < NOW() - make_interval(secs => $2)) - ORDER BY priority ASC, attempt ASC, id ASC - LIMIT 1 - FOR UPDATE SKIP LOCKED"#, - self.config.build_attempts as i32, - self.config.delay_between_build_attempts.as_secs_f64(), - ) - .fetch_optional(&mut *transaction) - .await? - { - Some(krate) => krate, - None => return Ok(()), - }; - - // FIXME: can the closure also be async? Or is sync better? - let res = run_blocking("builder", { - let to_process = to_process.clone(); - move || f(&to_process) - }) - .await; - // FIXME: how to handle when we want to have the attepmtp count - - async fn increase_attempt_count( - to_process: &QueuedCrate, - transaction: &mut sqlx::PgConnection, - ) -> Result { - let attempt: i32 = sqlx::query_scalar!( - "UPDATE queue - SET - attempt = attempt + 1, - last_attempt = NOW() - WHERE id = $1 - RETURNING attempt;", - to_process.id, - ) - .fetch_one(&mut *transaction) - .await?; - - Ok(attempt) - } - - match res { - Ok(BuildPackageSummary { - should_reattempt: false, - successful: _, - }) => { - sqlx::query!("DELETE FROM queue WHERE id = $1;", to_process.id) - .execute(&mut *transaction) - .await?; - } - Ok(BuildPackageSummary { - should_reattempt: true, - successful: _, - }) => { - increase_attempt_count(&to_process, &mut transaction).await?; - } - Err(e) => { - let next_attempt = increase_attempt_count(&to_process, &mut transaction).await?; - error!( - ?e, - name=%to_process.name, - version=%to_process.version, - next_attempt, - "Failed to build package from queue" - ); - } - } - - transaction.commit().await?; - Ok(()) - } -} - #[derive(Debug)] pub struct BuildQueue { runtime: runtime::Handle, @@ -400,6 +301,95 @@ impl BuildQueue { pub(crate) fn queued_crates(&self) -> Result> { self.runtime.block_on(self.inner.queued_crates()) } + + pub fn process_next_crate( + &self, + f: impl FnOnce(&QueuedCrate) -> Result, + ) -> Result<()> { + let mut conn = self.runtime.block_on(self.inner.db.get_async())?; + let mut transaction = self.runtime.block_on(conn.begin())?; + + // fetch the next available crate from the queue table. + // We are using `SELECT FOR UPDATE` inside a transaction so + // the QueuedCrate is locked until we are finished with it. + // `SKIP LOCKED` here will enable another build-server to just + // skip over taken (=locked) rows and start building the first + // available one. + let to_process = match self.runtime.block_on( + sqlx::query_as!( + QueuedCrate, + r#"SELECT + id, + name, + version as "version: Version", + priority, + registry, + attempt + FROM queue + WHERE + attempt < $1 AND + (last_attempt IS NULL OR last_attempt < NOW() - make_interval(secs => $2)) + ORDER BY priority ASC, attempt ASC, id ASC + LIMIT 1 + FOR UPDATE SKIP LOCKED"#, + self.inner.config.build_attempts as i32, + self.inner.config.delay_between_build_attempts.as_secs_f64(), + ) + .fetch_optional(&mut *transaction), + )? { + Some(krate) => krate, + None => return Ok(()), + }; + + let res = f(&to_process); + + let mut increase_attempt_count = || -> Result { + let next_attempt: i32 = self.runtime.block_on( + sqlx::query_scalar!( + "UPDATE queue + SET + attempt = attempt + 1, + last_attempt = NOW() + WHERE id = $1 + RETURNING attempt;", + to_process.id, + ) + .fetch_one(&mut *transaction), + )?; + + Ok(next_attempt) + }; + + match res { + Ok(BuildPackageSummary { + should_reattempt: false, + successful: _, + }) => { + self.runtime.block_on( + sqlx::query!("DELETE FROM queue WHERE id = $1;", to_process.id) + .execute(&mut *transaction), + )?; + } + Ok(BuildPackageSummary { + should_reattempt: true, + successful: _, + }) => { + increase_attempt_count()?; + } + Err(e) => { + increase_attempt_count()?; + error!( + ?e, + name = %to_process.name, + version = %to_process.version, + "Failed to build package queue" + ); + } + } + + self.runtime.block_on(transaction.commit())?; + Ok(()) + } } // #[cfg(test)] From c24dc9a65d0ff091380beea6c1a11d469a9cea71 Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 13 Dec 2025 00:35:22 +0100 Subject: [PATCH 45/46] try --- Cargo.lock | 1 + crates/bin/docs_rs_builder/Cargo.toml | 1 + .../src/utils/queue_builder.rs | 34 +++++++++++-------- crates/lib/docs_rs_build_queue/src/lib.rs | 17 +++++++--- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f0de631a2..eaab7ecd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1810,6 +1810,7 @@ dependencies = [ "docs_rs_context", "docs_rs_database", "docs_rs_env_vars", + "docs_rs_fastly", "docs_rs_logging", "docs_rs_opentelemetry", "docs_rs_registry_api", diff --git a/crates/bin/docs_rs_builder/Cargo.toml b/crates/bin/docs_rs_builder/Cargo.toml index 8c6f6fc34..aafdc4578 100644 --- a/crates/bin/docs_rs_builder/Cargo.toml +++ b/crates/bin/docs_rs_builder/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" anyhow = { workspace = true } docs_rs_build_queue = { path = "../../lib/docs_rs_build_queue" } docs_rs_build_utils = { path = "../../lib/docs_rs_build_utils" } +docs_rs_fastly = { path = "../../lib/docs_rs_fastly" } docs_rs_cargo_metadata = { path = "../../lib/docs_rs_cargo_metadata" } docs_rs_context = { path = "../../lib/docs_rs_context" } docs_rs_database = { path = "../../lib/docs_rs_database" } diff --git a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs index 034976199..d8ca060d3 100644 --- a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs +++ b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs @@ -3,6 +3,8 @@ use crate::docbuilder::rustwide_builder::{PackageKind, RustwideBuilder}; use anyhow::{Context as _, Result}; use docs_rs_build_queue::BuildQueue; use docs_rs_context::Context; +use docs_rs_database::types::krate_name::KrateName; +use docs_rs_fastly::Cdn; use docs_rs_utils::retry; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::path::Path; @@ -67,13 +69,13 @@ pub fn queue_builder( /// Builds the top package from the queue. Returns whether there was a package in the queue. /// /// Note that this will return `Ok(true)` even if the package failed to build. -fn build_next_queue_package( - build_queue: &BuildQueue, - builder: &mut RustwideBuilder, -) -> Result { +fn build_next_queue_package(context: &Context, builder: &mut RustwideBuilder) -> Result { + let build_queue = context.blocking_build_queue()?; + let runtime = context.runtime(); + let cdn = context.cdn()?; let mut processed = false; - build_queue.process_next_crate(|krate| { + let next_attempt = build_queue.process_next_crate(|krate| { processed = true; let kind = krate @@ -95,22 +97,26 @@ fn build_next_queue_package( } let instant = Instant::now(); - let res = builder.build_package(&krate.name, &krate.version, kind, krate.attempt == 0); - let elapsed = instant.elapsed().as_secs_f64(); - builder.builder_metrics.build_time.record(elapsed, &[]); + builder + .builder_metrics + .build_time + .record(instant.elapsed().as_secs_f64(), &[]); builder.builder_metrics.total_builds.add(1, &[]); - // self.runtime - // .block_on(self.inner.queue_crate_invalidation(&to_process.name)); + + if let Ok(name) = krate.name.parse::() { + runtime.block_on(cdn.queue_crate_invalidation(&name)); + } res })?; - // in case of errors? - // if attempt >= self.inner.config.build_attempts as i32 { - // self.inner.builder_metrics.failed_builds.add(1, &[]); - // } + if let Some(attempt) = next_attempt { + if attempt >= build_queue.config().build_attempts as i32 { + builder.builder_metrics.failed_builds.add(1, &[]); + } + } Ok(processed) } diff --git a/crates/lib/docs_rs_build_queue/src/lib.rs b/crates/lib/docs_rs_build_queue/src/lib.rs index 09add3d02..2bdb519d5 100644 --- a/crates/lib/docs_rs_build_queue/src/lib.rs +++ b/crates/lib/docs_rs_build_queue/src/lib.rs @@ -260,6 +260,10 @@ impl BuildQueue { Self { runtime, inner } } + pub fn config(&self) -> &Config { + &self.inner.config + } + pub fn add_crate( &self, name: &str, @@ -305,7 +309,7 @@ impl BuildQueue { pub fn process_next_crate( &self, f: impl FnOnce(&QueuedCrate) -> Result, - ) -> Result<()> { + ) -> Result> { let mut conn = self.runtime.block_on(self.inner.db.get_async())?; let mut transaction = self.runtime.block_on(conn.begin())?; @@ -338,7 +342,7 @@ impl BuildQueue { .fetch_optional(&mut *transaction), )? { Some(krate) => krate, - None => return Ok(()), + None => return Ok(None), }; let res = f(&to_process); @@ -360,6 +364,8 @@ impl BuildQueue { Ok(next_attempt) }; + let mut next_attempt: Option = None; + match res { Ok(BuildPackageSummary { should_reattempt: false, @@ -369,15 +375,16 @@ impl BuildQueue { sqlx::query!("DELETE FROM queue WHERE id = $1;", to_process.id) .execute(&mut *transaction), )?; + next_attempt = None; } Ok(BuildPackageSummary { should_reattempt: true, successful: _, }) => { - increase_attempt_count()?; + next_attempt = Some(increase_attempt_count()?); } Err(e) => { - increase_attempt_count()?; + next_attempt = Some(increase_attempt_count()?); error!( ?e, name = %to_process.name, @@ -388,7 +395,7 @@ impl BuildQueue { } self.runtime.block_on(transaction.commit())?; - Ok(()) + Ok(next_attempt) } } From 6546f8971c9f3106543daddbf9596340d87ff29b Mon Sep 17 00:00:00 2001 From: Denis Cornehl Date: Sat, 13 Dec 2025 00:39:23 +0100 Subject: [PATCH 46/46] kk --- .../bin/docs_rs_builder/src/utils/queue_builder.rs | 14 ++++++-------- crates/lib/docs_rs_build_queue/src/lib.rs | 3 +-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs index d8ca060d3..293527e4d 100644 --- a/crates/bin/docs_rs_builder/src/utils/queue_builder.rs +++ b/crates/bin/docs_rs_builder/src/utils/queue_builder.rs @@ -1,10 +1,8 @@ use crate::config::Config; use crate::docbuilder::rustwide_builder::{PackageKind, RustwideBuilder}; use anyhow::{Context as _, Result}; -use docs_rs_build_queue::BuildQueue; use docs_rs_context::Context; use docs_rs_database::types::krate_name::KrateName; -use docs_rs_fastly::Cdn; use docs_rs_utils::retry; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::path::Path; @@ -46,7 +44,7 @@ pub fn queue_builder( // If a panic occurs while building a crate, lock the queue until an admin has a chance to look at it. debug!("Checking build queue"); let res = catch_unwind(AssertUnwindSafe(|| { - match build_next_queue_package(&build_queue, &mut builder) { + match build_next_queue_package(context, &mut builder) { Ok(true) => {} Ok(false) => { debug!("Queue is empty, going back to sleep"); @@ -106,16 +104,16 @@ fn build_next_queue_package(context: &Context, builder: &mut RustwideBuilder) -> builder.builder_metrics.total_builds.add(1, &[]); if let Ok(name) = krate.name.parse::() { - runtime.block_on(cdn.queue_crate_invalidation(&name)); + runtime.block_on(cdn.queue_crate_invalidation(&name))?; } res })?; - if let Some(attempt) = next_attempt { - if attempt >= build_queue.config().build_attempts as i32 { - builder.builder_metrics.failed_builds.add(1, &[]); - } + if let Some(attempt) = next_attempt + && attempt >= build_queue.config().build_attempts as i32 + { + builder.builder_metrics.failed_builds.add(1, &[]); } Ok(processed) diff --git a/crates/lib/docs_rs_build_queue/src/lib.rs b/crates/lib/docs_rs_build_queue/src/lib.rs index 2bdb519d5..d4c5b8f85 100644 --- a/crates/lib/docs_rs_build_queue/src/lib.rs +++ b/crates/lib/docs_rs_build_queue/src/lib.rs @@ -11,10 +11,9 @@ use docs_rs_database::{ types::version::Version, }; use docs_rs_opentelemetry::AnyMeterProvider; -use docs_rs_utils::run_blocking; use futures_util::TryStreamExt as _; use sqlx::Connection as _; -use std::{collections::HashMap, sync::Arc, time::Instant}; +use std::{collections::HashMap, sync::Arc}; use tokio::runtime; use tracing::error;