mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-23 18:56:30 +00:00
Compare commits
7 commits
13f5983c0a
...
7c50788d59
Author | SHA1 | Date | |
---|---|---|---|
|
7c50788d59 | ||
|
ff6d9e880f | ||
|
b016ff1f8f | ||
|
5a215ebbfb | ||
|
62891bb2db | ||
|
e720a5f4b5 | ||
|
9c5c5f88e0 |
8 changed files with 1408 additions and 602 deletions
1765
Cargo.lock
generated
1765
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
10
Cargo.toml
10
Cargo.toml
|
@ -18,9 +18,10 @@ actix-web = "4"
|
||||||
actix-web-lab = "0.20"
|
actix-web-lab = "0.20"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
cadence = "1"
|
cadence = "1"
|
||||||
crates-index = { version = "2", default-features = false, features = ["git"] }
|
crates-index = { version = "3", default-features = false, features = ["git"] }
|
||||||
derive_more = "0.99"
|
derive_more = { version = "1", features = ["display", "error", "from"] }
|
||||||
dotenvy = "0.15"
|
dotenvy = "0.15"
|
||||||
|
either = "1.12"
|
||||||
font-awesome-as-a-crate = "0.3"
|
font-awesome-as-a-crate = "0.3"
|
||||||
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
futures-util = { version = "0.3", default-features = false, features = ["std"] }
|
||||||
error_reporter = "1"
|
error_reporter = "1"
|
||||||
|
@ -30,13 +31,14 @@ maud = "0.26"
|
||||||
mime = "0.3"
|
mime = "0.3"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
pulldown-cmark = "0.11"
|
pulldown-cmark = "0.12"
|
||||||
relative-path = { version = "1", features = ["serde"] }
|
relative-path = { version = "1", features = ["serde"] }
|
||||||
reqwest = { version = "0.12", features = ["json"] }
|
reqwest = { version = "0.12", features = ["json"] }
|
||||||
rustsec = "0.29"
|
rustsec = "0.29"
|
||||||
semver = { version = "1.0", features = ["serde"] }
|
semver = { version = "1", features = ["serde"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_urlencoded = "0.7"
|
serde_urlencoded = "0.7"
|
||||||
|
serde_with = "3"
|
||||||
tokio = { version = "1.24.2", features = ["rt", "macros", "sync", "time"] }
|
tokio = { version = "1.24.2", features = ["rt", "macros", "sync", "time"] }
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
tracing = "0.1.30"
|
tracing = "0.1.30"
|
||||||
|
|
13
README.md
13
README.md
|
@ -19,10 +19,15 @@ To analyze the state of your dependencies you can use the following URLs:
|
||||||
|
|
||||||
On the analysis page, you will also find the markdown code to include a fancy badge in your project README so visitors (and you) can see at a glance if your dependencies are still up to date!
|
On the analysis page, you will also find the markdown code to include a fancy badge in your project README so visitors (and you) can see at a glance if your dependencies are still up to date!
|
||||||
|
|
||||||
Badges have a few style options, specified with query parameters, that match the styles from `shields.io`:
|
Badges have a few options, specified with query parameters:
|
||||||
- `?style=flat` (default)
|
- `style`: which matches the styles from `shields.io`:
|
||||||
- `?style=flat-square`
|
- `?style=flat` (default)
|
||||||
- `?style=for-the-badge`
|
- `?style=flat-square`
|
||||||
|
- `?style=for-the-badge`
|
||||||
|
- `subject`: customize the text on the left (which is the same concept as `label` in `shields.io`, and [URL-Encoding](https://developer.mozilla.org/en-US/docs/Glossary/percent-encoding) is needed for spaces or special characters!). e.g.:
|
||||||
|
- `?subject=yourdeps`
|
||||||
|
- `?subject=git%20deps`
|
||||||
|
- `?subject=deps%3Acore`
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
@ -9,22 +9,22 @@ use crate::server::views::html::error::{render, render_404};
|
||||||
|
|
||||||
#[derive(Debug, Display)]
|
#[derive(Debug, Display)]
|
||||||
pub(crate) enum ServerError {
|
pub(crate) enum ServerError {
|
||||||
#[display(fmt = "Could not retrieve popular items")]
|
#[display("Could not retrieve popular items")]
|
||||||
PopularItemsFailed,
|
PopularItemsFailed,
|
||||||
|
|
||||||
#[display(fmt = "Crate not found")]
|
#[display("Crate not found")]
|
||||||
CrateNotFound,
|
CrateNotFound,
|
||||||
|
|
||||||
#[display(fmt = "Could not parse crate path")]
|
#[display("Could not parse crate path")]
|
||||||
BadCratePath,
|
BadCratePath,
|
||||||
|
|
||||||
#[display(fmt = "Could not fetch crate information")]
|
#[display("Could not fetch crate information")]
|
||||||
CrateFetchFailed,
|
CrateFetchFailed,
|
||||||
|
|
||||||
#[display(fmt = "Could not parse repository path")]
|
#[display("Could not parse repository path")]
|
||||||
BadRepoPath,
|
BadRepoPath,
|
||||||
|
|
||||||
#[display(fmt = "Crate/repo analysis failed")]
|
#[display("Crate/repo analysis failed")]
|
||||||
AnalysisFailed(Markup),
|
AnalysisFailed(Markup),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,8 +38,11 @@ use crate::{
|
||||||
repo::RepoPath,
|
repo::RepoPath,
|
||||||
SubjectPath,
|
SubjectPath,
|
||||||
},
|
},
|
||||||
|
utils::common::{safe_truncate, UntaggedEither, WrappedBool},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MAX_SUBJECT_WIDTH: usize = 100;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
enum StatusFormat {
|
enum StatusFormat {
|
||||||
Html,
|
Html,
|
||||||
|
@ -310,18 +313,35 @@ static SELF_BASE_URL: Lazy<String> =
|
||||||
pub struct ExtraConfig {
|
pub struct ExtraConfig {
|
||||||
/// Badge style to show
|
/// Badge style to show
|
||||||
style: BadgeStyle,
|
style: BadgeStyle,
|
||||||
|
|
||||||
/// Whether the inscription _"dependencies"_ should be abbreviated as _"deps"_ in the badge.
|
/// Whether the inscription _"dependencies"_ should be abbreviated as _"deps"_ in the badge.
|
||||||
compact: bool,
|
compact: bool,
|
||||||
|
|
||||||
|
/// Custom text on the left (it's the same concept as `label` in shields.io).
|
||||||
|
subject: Option<String>,
|
||||||
|
|
||||||
/// Path in which the crate resides within the repository
|
/// Path in which the crate resides within the repository
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExtraConfig {
|
impl ExtraConfig {
|
||||||
fn from_query_string(qs: Option<&str>) -> Self {
|
fn from_query_string(qs: Option<&str>) -> Self {
|
||||||
|
/// This wrapper can make the deserialization process infallible.
|
||||||
|
#[derive(Debug, Clone, Deserialize)]
|
||||||
|
#[serde(transparent)]
|
||||||
|
struct QueryParam<T>(UntaggedEither<T, String>);
|
||||||
|
|
||||||
|
impl<T> QueryParam<T> {
|
||||||
|
fn opt(self) -> Option<T> {
|
||||||
|
either::Either::from(self.0).left()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
struct ExtraConfigPartial {
|
struct ExtraConfigPartial {
|
||||||
style: Option<BadgeStyle>,
|
style: Option<QueryParam<BadgeStyle>>,
|
||||||
compact: Option<bool>,
|
compact: Option<QueryParam<WrappedBool>>,
|
||||||
|
subject: Option<String>,
|
||||||
path: Option<String>,
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,9 +350,33 @@ impl ExtraConfig {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
style: extra_config.style.unwrap_or_default(),
|
style: extra_config
|
||||||
compact: extra_config.compact.unwrap_or_default(),
|
.style
|
||||||
|
.and_then(|qp| qp.opt())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
compact: extra_config
|
||||||
|
.compact
|
||||||
|
.and_then(|qp| qp.opt())
|
||||||
|
.unwrap_or_default()
|
||||||
|
.0,
|
||||||
|
subject: extra_config
|
||||||
|
.subject
|
||||||
|
.filter(|t| !t.is_empty())
|
||||||
|
.map(|subject| safe_truncate(&subject, MAX_SUBJECT_WIDTH).to_owned()),
|
||||||
path: extra_config.path,
|
path: extra_config.path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns subject for badge.
|
||||||
|
///
|
||||||
|
/// Returns `subject` if set, or "dependencies" / "deps" depending on value of `compact`.
|
||||||
|
pub(crate) fn subject(&self) -> &str {
|
||||||
|
if let Some(subject) = &self.subject {
|
||||||
|
subject
|
||||||
|
} else if self.compact {
|
||||||
|
"deps"
|
||||||
|
} else {
|
||||||
|
"dependencies"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,7 @@ pub fn badge(
|
||||||
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
||||||
badge_knobs: ExtraConfig,
|
badge_knobs: ExtraConfig,
|
||||||
) -> Badge {
|
) -> Badge {
|
||||||
let subject = if badge_knobs.compact {
|
let subject = badge_knobs.subject().to_owned();
|
||||||
"deps"
|
|
||||||
} else {
|
|
||||||
"dependencies"
|
|
||||||
}
|
|
||||||
.to_owned();
|
|
||||||
|
|
||||||
let opts = match analysis_outcome {
|
let opts = match analysis_outcome {
|
||||||
Some(outcome) => {
|
Some(outcome) => {
|
||||||
|
|
150
src/utils/common.rs
Normal file
150
src/utils/common.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
use either::Either;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_with::{DeserializeFromStr, SerializeDisplay};
|
||||||
|
use std::{
|
||||||
|
fmt::{self, Debug, Display, Formatter},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// An `untagged` version of `Either`.
|
||||||
|
///
|
||||||
|
/// The reason this structure is needed is that `either::Either` is
|
||||||
|
/// by default an `Externally Tagged` enum, and it is possible to
|
||||||
|
/// implement `untagged` via `#[serde(with = "either::serde_untagged_optional")]`
|
||||||
|
/// as well. But this approach can cause problems with deserialization,
|
||||||
|
/// resulting in having to manually add the `#[serde(default)]` tag,
|
||||||
|
/// and this leads to less readable as well as less flexible code.
|
||||||
|
/// So it would be better if we manually implement this `UntaggedEither` here,
|
||||||
|
/// while providing a two-way conversion to `either::Either`.
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum UntaggedEither<L, R> {
|
||||||
|
Left(L),
|
||||||
|
Right(R),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> From<UntaggedEither<L, R>> for Either<L, R> {
|
||||||
|
fn from(value: UntaggedEither<L, R>) -> Self {
|
||||||
|
match value {
|
||||||
|
UntaggedEither::Left(l) => Self::Left(l),
|
||||||
|
UntaggedEither::Right(r) => Self::Right(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<L, R> From<Either<L, R>> for UntaggedEither<L, R> {
|
||||||
|
fn from(value: Either<L, R>) -> Self {
|
||||||
|
match value {
|
||||||
|
Either::Left(l) => UntaggedEither::Left(l),
|
||||||
|
Either::Right(r) => UntaggedEither::Right(r),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A generic newtype which serialized using `Display` and deserialized using `FromStr`.
|
||||||
|
#[derive(Default, Clone, DeserializeFromStr, SerializeDisplay)]
|
||||||
|
pub struct SerdeDisplayFromStr<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: Debug> Debug for SerdeDisplayFromStr<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
Debug::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Display> Display for SerdeDisplayFromStr<T> {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
Display::fmt(&self.0, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: FromStr> FromStr for SerdeDisplayFromStr<T> {
|
||||||
|
type Err = T::Err;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
s.parse::<T>().map(Self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reason it's needed here is that using `Deserialize` generated
|
||||||
|
/// by default by `serde` will cause deserialization to fail if
|
||||||
|
/// both untyped formats (such as `urlencoded`) and `untagged enum`
|
||||||
|
/// are used. The Wrap type here forces the deserialization process to
|
||||||
|
/// be delegated to `FromStr`.
|
||||||
|
pub type WrappedBool = SerdeDisplayFromStr<bool>;
|
||||||
|
|
||||||
|
/// Returns truncated string accounting for multi-byte characters.
|
||||||
|
pub(crate) fn safe_truncate(s: &str, len: usize) -> &str {
|
||||||
|
if len == 0 {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.len() <= len {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.is_char_boundary(len) {
|
||||||
|
return &s[0..len];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only 3 cases possible: 1, 2, or 3 bytes need to be removed for a new,
|
||||||
|
// valid UTF-8 string to appear when truncated, just enumerate them,
|
||||||
|
// Underflow is not possible since position 0 is always a valid boundary.
|
||||||
|
|
||||||
|
if let Some((slice, _rest)) = s.split_at_checked(len - 1) {
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((slice, _rest)) = s.split_at_checked(len - 2) {
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some((slice, _rest)) = s.split_at_checked(len - 3) {
|
||||||
|
return slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("all branches covered");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn safe_truncation() {
|
||||||
|
assert_eq!(safe_truncate("", 0), "");
|
||||||
|
assert_eq!(safe_truncate("", 1), "");
|
||||||
|
assert_eq!(safe_truncate("", 9), "");
|
||||||
|
|
||||||
|
assert_eq!(safe_truncate("a", 0), "");
|
||||||
|
assert_eq!(safe_truncate("a", 1), "a");
|
||||||
|
assert_eq!(safe_truncate("a", 9), "a");
|
||||||
|
|
||||||
|
assert_eq!(safe_truncate("lorem\nipsum", 0), "");
|
||||||
|
assert_eq!(safe_truncate("lorem\nipsum", 5), "lorem");
|
||||||
|
assert_eq!(safe_truncate("lorem\nipsum", usize::MAX), "lorem\nipsum");
|
||||||
|
|
||||||
|
assert_eq!(safe_truncate("café", 1), "c");
|
||||||
|
assert_eq!(safe_truncate("café", 2), "ca");
|
||||||
|
assert_eq!(safe_truncate("café", 3), "caf");
|
||||||
|
assert_eq!(safe_truncate("café", 4), "caf");
|
||||||
|
assert_eq!(safe_truncate("café", 5), "café");
|
||||||
|
|
||||||
|
// 2-byte char
|
||||||
|
assert_eq!(safe_truncate("é", 0), "");
|
||||||
|
assert_eq!(safe_truncate("é", 1), "");
|
||||||
|
assert_eq!(safe_truncate("é", 2), "é");
|
||||||
|
|
||||||
|
// 3-byte char
|
||||||
|
assert_eq!(safe_truncate("⊕", 0), "");
|
||||||
|
assert_eq!(safe_truncate("⊕", 1), "");
|
||||||
|
assert_eq!(safe_truncate("⊕", 2), "");
|
||||||
|
assert_eq!(safe_truncate("⊕", 3), "⊕");
|
||||||
|
|
||||||
|
// 4-byte char
|
||||||
|
assert_eq!(safe_truncate("🦊", 0), "");
|
||||||
|
assert_eq!(safe_truncate("🦊", 1), "");
|
||||||
|
assert_eq!(safe_truncate("🦊", 2), "");
|
||||||
|
assert_eq!(safe_truncate("🦊", 3), "");
|
||||||
|
assert_eq!(safe_truncate("🦊", 4), "🦊");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
pub mod common;
|
||||||
pub mod index;
|
pub mod index;
|
||||||
|
|
Loading…
Reference in a new issue