mirror of
https://github.com/deps-rs/deps.rs.git
synced 2024-11-22 10:26:30 +00:00
Allow analyzing crates in sub-directories of repo root (#170)
* Allow analyzing crates in sub-directories (#95) * Add field to the main page form for selecting an inner path * chore: make clippy happy * Display sub-directory tree in status overview * Append the query parameter to the SVG links * Clippy fixes * Update assets/links.js Co-authored-by: Eduardo Pinho <enet4mikeenet@gmail.com> Co-authored-by: Eduardo Pinho <enet4mikeenet@gmail.com>
This commit is contained in:
parent
477d83a4e0
commit
f9d545f9ff
6 changed files with 96 additions and 28 deletions
14
assets/links.js
vendored
14
assets/links.js
vendored
|
@ -4,6 +4,12 @@ function buildRepoLink() {
|
||||||
let hoster = formRef.elements["hosterSelect"].value.toLowerCase();
|
let hoster = formRef.elements["hosterSelect"].value.toLowerCase();
|
||||||
let owner = formRef.elements["owner"].value;
|
let owner = formRef.elements["owner"].value;
|
||||||
let repoName = formRef.elements["repoName"].value;
|
let repoName = formRef.elements["repoName"].value;
|
||||||
|
let innerPath = formRef.elements["innerPath"].value;
|
||||||
|
|
||||||
|
let qparams = "";
|
||||||
|
if (innerPath.length > 0) {
|
||||||
|
qparams = "?path=" + encodeURIComponent(innerPath);
|
||||||
|
}
|
||||||
|
|
||||||
if (hoster === "gitea") {
|
if (hoster === "gitea") {
|
||||||
let baseUrl = formRef.elements["baseUrl"].value;
|
let baseUrl = formRef.elements["baseUrl"].value;
|
||||||
|
@ -18,9 +24,9 @@ function buildRepoLink() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.href = `/repo/${hoster}/${baseUrl}/${owner}/${repoName}`;
|
window.location.assign(`/repo/${hoster}/${baseUrl}/${owner}/${repoName}${qparams}`);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = `/repo/${hoster}/${owner}/${repoName}`;
|
window.location.assign(`/repo/${hoster}/${owner}/${repoName}${qparams}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +38,8 @@ function buildCrateLink() {
|
||||||
|
|
||||||
if (crateVer.length == 0) {
|
if (crateVer.length == 0) {
|
||||||
// default to latest version
|
// default to latest version
|
||||||
window.location.href = `/crate/${crate}`;
|
window.location.assign(`/crate/${crate}`);
|
||||||
} else {
|
} else {
|
||||||
window.location.href = `/crate/${crate}/${crateVer}`;
|
window.location.assign(`/crate/${crate}/${crateVer}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,20 +170,25 @@ impl Engine {
|
||||||
pub async fn analyze_repo_dependencies(
|
pub async fn analyze_repo_dependencies(
|
||||||
&self,
|
&self,
|
||||||
repo_path: RepoPath,
|
repo_path: RepoPath,
|
||||||
|
sub_path: &Option<String>,
|
||||||
) -> Result<AnalyzeDependenciesOutcome, Error> {
|
) -> Result<AnalyzeDependenciesOutcome, Error> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
|
|
||||||
let entry_point = RelativePath::new("/").to_relative_path_buf();
|
let mut entry_point = RelativePath::new("/").to_relative_path_buf();
|
||||||
|
|
||||||
|
if let Some(inner_path) = sub_path {
|
||||||
|
entry_point.push(inner_path);
|
||||||
|
}
|
||||||
|
|
||||||
let engine = self.clone();
|
let engine = self.clone();
|
||||||
|
|
||||||
let manifest_output = crawl_manifest(self.clone(), repo_path.clone(), entry_point).await?;
|
let manifest_output = crawl_manifest(self.clone(), repo_path.clone(), entry_point).await?;
|
||||||
|
|
||||||
let engine_for_analyze = engine.clone();
|
|
||||||
let futures = manifest_output
|
let futures = manifest_output
|
||||||
.crates
|
.crates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(crate_name, deps)| async {
|
.map(|(crate_name, deps)| async {
|
||||||
let analyzed_deps = analyze_dependencies(engine_for_analyze.clone(), deps).await?;
|
let analyzed_deps = analyze_dependencies(engine.clone(), deps).await?;
|
||||||
Ok::<_, Error>((crate_name, analyzed_deps))
|
Ok::<_, Error>((crate_name, analyzed_deps))
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
|
@ -186,7 +186,7 @@ impl App {
|
||||||
let qual = params.find("qual").expect("route param 'qual' not found");
|
let qual = params.find("qual").expect("route param 'qual' not found");
|
||||||
let name = params.find("name").expect("route param 'name' not found");
|
let name = params.find("name").expect("route param 'name' not found");
|
||||||
|
|
||||||
let badge_knobs = BadgeKnobs::from_query_string(req.uri().query());
|
let extra_knobs = ExtraConfig::from_query_string(req.uri().query());
|
||||||
|
|
||||||
let repo_path_result = RepoPath::from_parts(site, qual, name);
|
let repo_path_result = RepoPath::from_parts(site, qual, name);
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ impl App {
|
||||||
Ok(repo_path) => {
|
Ok(repo_path) => {
|
||||||
let analyze_result = server
|
let analyze_result = server
|
||||||
.engine
|
.engine
|
||||||
.analyze_repo_dependencies(repo_path.clone())
|
.analyze_repo_dependencies(repo_path.clone(), &extra_knobs.path)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
match analyze_result {
|
match analyze_result {
|
||||||
|
@ -214,7 +214,7 @@ impl App {
|
||||||
None,
|
None,
|
||||||
format,
|
format,
|
||||||
SubjectPath::Repo(repo_path),
|
SubjectPath::Repo(repo_path),
|
||||||
badge_knobs,
|
extra_knobs,
|
||||||
);
|
);
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ impl App {
|
||||||
Some(analysis_outcome),
|
Some(analysis_outcome),
|
||||||
format,
|
format,
|
||||||
SubjectPath::Repo(repo_path),
|
SubjectPath::Repo(repo_path),
|
||||||
badge_knobs,
|
extra_knobs,
|
||||||
);
|
);
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
@ -345,7 +345,7 @@ impl App {
|
||||||
};
|
};
|
||||||
|
|
||||||
let crate_path_result = CratePath::from_parts(name, &version);
|
let crate_path_result = CratePath::from_parts(name, &version);
|
||||||
let badge_knobs = BadgeKnobs::from_query_string(req.uri().query());
|
let badge_knobs = ExtraConfig::from_query_string(req.uri().query());
|
||||||
|
|
||||||
match crate_path_result {
|
match crate_path_result {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
|
@ -393,11 +393,13 @@ impl App {
|
||||||
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
|
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
|
||||||
format: StatusFormat,
|
format: StatusFormat,
|
||||||
subject_path: SubjectPath,
|
subject_path: SubjectPath,
|
||||||
badge_knobs: BadgeKnobs,
|
badge_knobs: ExtraConfig,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
match format {
|
match format {
|
||||||
StatusFormat::Svg => views::badge::response(analysis_outcome.as_ref(), badge_knobs),
|
StatusFormat::Svg => views::badge::response(analysis_outcome.as_ref(), badge_knobs),
|
||||||
StatusFormat::Html => views::html::status::render(analysis_outcome, subject_path),
|
StatusFormat::Html => {
|
||||||
|
views::html::status::render(analysis_outcome, subject_path, badge_knobs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,27 +432,34 @@ fn not_found() -> Response<Body> {
|
||||||
static SELF_BASE_URL: Lazy<String> =
|
static SELF_BASE_URL: Lazy<String> =
|
||||||
Lazy::new(|| env::var("BASE_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()));
|
Lazy::new(|| env::var("BASE_URL").unwrap_or_else(|_| "http://localhost:8080".to_string()));
|
||||||
|
|
||||||
|
/// Configuration options supplied through Get Parameters
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone, Default)]
|
||||||
pub struct BadgeKnobs {
|
pub struct ExtraConfig {
|
||||||
|
/// Badge style to show
|
||||||
style: BadgeStyle,
|
style: BadgeStyle,
|
||||||
|
/// Whether the inscription _"dependencies"_ should be abbreviated as _"deps"_ in the badge.
|
||||||
compact: bool,
|
compact: bool,
|
||||||
|
/// Path in which the crate resides within the repository
|
||||||
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BadgeKnobs {
|
impl ExtraConfig {
|
||||||
fn from_query_string(qs: Option<&str>) -> Self {
|
fn from_query_string(qs: Option<&str>) -> Self {
|
||||||
#[derive(Debug, Clone, Default, Deserialize)]
|
#[derive(Debug, Clone, Default, Deserialize)]
|
||||||
struct BadgeKnobsPartial {
|
struct ExtraConfigPartial {
|
||||||
style: Option<BadgeStyle>,
|
style: Option<BadgeStyle>,
|
||||||
compact: Option<bool>,
|
compact: Option<bool>,
|
||||||
|
path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let badge_knobs = qs
|
let extra_config = qs
|
||||||
.and_then(|qs| serde_urlencoded::from_str::<BadgeKnobsPartial>(qs).ok())
|
.and_then(|qs| serde_urlencoded::from_str::<ExtraConfigPartial>(qs).ok())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
style: badge_knobs.style.unwrap_or_default(),
|
style: extra_config.style.unwrap_or_default(),
|
||||||
compact: badge_knobs.compact.unwrap_or_default(),
|
compact: extra_config.compact.unwrap_or_default(),
|
||||||
|
path: extra_config.path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,11 @@ use hyper::header::CONTENT_TYPE;
|
||||||
use hyper::{Body, Response};
|
use hyper::{Body, Response};
|
||||||
|
|
||||||
use crate::engine::AnalyzeDependenciesOutcome;
|
use crate::engine::AnalyzeDependenciesOutcome;
|
||||||
use crate::server::BadgeKnobs;
|
use crate::server::ExtraConfig;
|
||||||
|
|
||||||
pub fn badge(
|
pub fn badge(
|
||||||
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
||||||
badge_knobs: BadgeKnobs,
|
badge_knobs: ExtraConfig,
|
||||||
) -> Badge {
|
) -> Badge {
|
||||||
let subject = if badge_knobs.compact {
|
let subject = if badge_knobs.compact {
|
||||||
"deps"
|
"deps"
|
||||||
|
@ -74,7 +74,7 @@ pub fn badge(
|
||||||
|
|
||||||
pub fn response(
|
pub fn response(
|
||||||
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
analysis_outcome: Option<&AnalyzeDependenciesOutcome>,
|
||||||
badge_knobs: BadgeKnobs,
|
badge_knobs: ExtraConfig,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
let badge = badge(analysis_outcome, badge_knobs).to_svg();
|
let badge = badge(analysis_outcome, badge_knobs).to_svg();
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,16 @@ fn link_forms() -> Markup {
|
||||||
p class="help" id="baseUrlHelp" { "Base URL of the Git instance the project is hosted on. Only relevant for Gitea Instances." }
|
p class="help" id="baseUrlHelp" { "Base URL of the Git instance the project is hosted on. Only relevant for Gitea Instances." }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div class="field" {
|
||||||
|
label class="label" { "Path in Repository" }
|
||||||
|
|
||||||
|
div class="control" {
|
||||||
|
input class="input" type="text" id="innerPath" placeholder="project1/rust-stuff";
|
||||||
|
}
|
||||||
|
|
||||||
|
p class="help" id="baseUrlHelp" { "Path within the repository where the " code { "Cargo.toml" } " file is located." }
|
||||||
|
}
|
||||||
|
|
||||||
input type="submit" class="button is-primary" value="Check" onclick="buildRepoLink();";
|
input type="submit" class="button is-primary" value="Check" onclick="buildRepoLink();";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use crate::models::crates::{AnalyzedDependencies, AnalyzedDependency, CrateName}
|
||||||
use crate::models::repo::RepoSite;
|
use crate::models::repo::RepoSite;
|
||||||
use crate::models::SubjectPath;
|
use crate::models::SubjectPath;
|
||||||
use crate::server::views::badge;
|
use crate::server::views::badge;
|
||||||
use crate::server::BadgeKnobs;
|
use crate::server::ExtraConfig;
|
||||||
|
|
||||||
fn get_crates_url(name: impl AsRef<str>) -> String {
|
fn get_crates_url(name: impl AsRef<str>) -> String {
|
||||||
format!("https://crates.io/crates/{}", name.as_ref())
|
format!("https://crates.io/crates/{}", name.as_ref())
|
||||||
|
@ -161,6 +161,23 @@ fn render_title(subject_path: &SubjectPath) -> Markup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Renders a path within a repository as HTML.
|
||||||
|
///
|
||||||
|
/// Panics, when the string is empty.
|
||||||
|
fn render_path(inner_path: &str) -> Markup {
|
||||||
|
let path_icon = PreEscaped(fa(FaType::Regular, "folder-open").unwrap());
|
||||||
|
|
||||||
|
let mut splitted = inner_path.trim_matches('/').split('/');
|
||||||
|
let init = splitted.next().unwrap().to_string();
|
||||||
|
let path_spaced = splitted.fold(init, |b, val| b + " / " + val);
|
||||||
|
|
||||||
|
html! {
|
||||||
|
{ (path_icon) }
|
||||||
|
" / "
|
||||||
|
(path_spaced)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn dependencies_pluralized(count: usize) -> &'static str {
|
fn dependencies_pluralized(count: usize) -> &'static str {
|
||||||
if count == 1 {
|
if count == 1 {
|
||||||
"dependency"
|
"dependency"
|
||||||
|
@ -332,6 +349,7 @@ fn render_failure(subject_path: SubjectPath) -> Markup {
|
||||||
fn render_success(
|
fn render_success(
|
||||||
analysis_outcome: AnalyzeDependenciesOutcome,
|
analysis_outcome: AnalyzeDependenciesOutcome,
|
||||||
subject_path: SubjectPath,
|
subject_path: SubjectPath,
|
||||||
|
extra_config: ExtraConfig,
|
||||||
) -> Markup {
|
) -> Markup {
|
||||||
let self_path = match subject_path {
|
let self_path = match subject_path {
|
||||||
SubjectPath::Repo(ref repo_path) => format!(
|
SubjectPath::Repo(ref repo_path) => format!(
|
||||||
|
@ -347,7 +365,7 @@ fn render_success(
|
||||||
let status_base_url = format!("{}/{}", &super::SELF_BASE_URL as &str, self_path);
|
let status_base_url = format!("{}/{}", &super::SELF_BASE_URL as &str, self_path);
|
||||||
|
|
||||||
let status_data_uri =
|
let status_data_uri =
|
||||||
badge::badge(Some(&analysis_outcome), BadgeKnobs::default()).to_svg_data_uri();
|
badge::badge(Some(&analysis_outcome), extra_config.clone()).to_svg_data_uri();
|
||||||
|
|
||||||
let hero_class = if analysis_outcome.any_always_insecure() {
|
let hero_class = if analysis_outcome.any_always_insecure() {
|
||||||
"is-danger"
|
"is-danger"
|
||||||
|
@ -357,6 +375,15 @@ fn render_success(
|
||||||
"is-success"
|
"is-success"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// NOTE(feliix42): While we could encode the whole `ExtraConfig` struct here, I've decided
|
||||||
|
// against doing so as this would always append the defaults for badge style and compactness
|
||||||
|
// settings to the URL, bloating it unnecessarily, we can do that once it's needed.
|
||||||
|
let options = serde_urlencoded::to_string([(
|
||||||
|
"path",
|
||||||
|
extra_config.path.clone().unwrap_or_default().as_str(),
|
||||||
|
)])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
section class=(format!("hero {}", hero_class)) {
|
section class=(format!("hero {}", hero_class)) {
|
||||||
div class="hero-head" { (super::render_navbar()) }
|
div class="hero-head" { (super::render_navbar()) }
|
||||||
|
@ -366,17 +393,27 @@ fn render_success(
|
||||||
(render_title(&subject_path))
|
(render_title(&subject_path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if let Some(ref path) = extra_config.path {
|
||||||
|
p class="subtitle" {
|
||||||
|
(render_path(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
img src=(status_data_uri);
|
img src=(status_data_uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
div class="hero-footer" {
|
div class="hero-footer" {
|
||||||
div class="container" {
|
div class="container" {
|
||||||
pre class="is-size-7" {
|
pre class="is-size-7" {
|
||||||
|
@if extra_config.path.is_some() {
|
||||||
|
(format!("[![dependency status]({}/status.svg?{opt})]({}?{opt})", status_base_url, status_base_url, opt = options))
|
||||||
|
} @else {
|
||||||
(format!("[![dependency status]({}/status.svg)]({})", status_base_url, status_base_url))
|
(format!("[![dependency status]({}/status.svg)]({})", status_base_url, status_base_url))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
section class="section" {
|
section class="section" {
|
||||||
div class="container" {
|
div class="container" {
|
||||||
@if analysis_outcome.any_always_insecure() {
|
@if analysis_outcome.any_always_insecure() {
|
||||||
|
@ -416,6 +453,7 @@ fn render_success(
|
||||||
pub fn render(
|
pub fn render(
|
||||||
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
|
analysis_outcome: Option<AnalyzeDependenciesOutcome>,
|
||||||
subject_path: SubjectPath,
|
subject_path: SubjectPath,
|
||||||
|
extra_config: ExtraConfig,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
let title = match subject_path {
|
let title = match subject_path {
|
||||||
SubjectPath::Repo(ref repo_path) => {
|
SubjectPath::Repo(ref repo_path) => {
|
||||||
|
@ -427,7 +465,7 @@ pub fn render(
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(outcome) = analysis_outcome {
|
if let Some(outcome) = analysis_outcome {
|
||||||
super::render_html(&title, render_success(outcome, subject_path))
|
super::render_html(&title, render_success(outcome, subject_path, extra_config))
|
||||||
} else {
|
} else {
|
||||||
super::render_html(&title, render_failure(subject_path))
|
super::render_html(&title, render_failure(subject_path))
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue