diff --git a/assets/links.js b/assets/links.js index 96b5cd5..56e2ccb 100644 --- a/assets/links.js +++ b/assets/links.js @@ -4,6 +4,12 @@ function buildRepoLink() { let hoster = formRef.elements["hosterSelect"].value.toLowerCase(); let owner = formRef.elements["owner"].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") { let baseUrl = formRef.elements["baseUrl"].value; @@ -18,9 +24,9 @@ function buildRepoLink() { return; } - window.location.href = `/repo/${hoster}/${baseUrl}/${owner}/${repoName}`; + window.location.assign(`/repo/${hoster}/${baseUrl}/${owner}/${repoName}${qparams}`); } 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) { // default to latest version - window.location.href = `/crate/${crate}`; + window.location.assign(`/crate/${crate}`); } else { - window.location.href = `/crate/${crate}/${crateVer}`; + window.location.assign(`/crate/${crate}/${crateVer}`); } } diff --git a/src/engine/mod.rs b/src/engine/mod.rs index f38400d..89399ec 100644 --- a/src/engine/mod.rs +++ b/src/engine/mod.rs @@ -170,20 +170,25 @@ impl Engine { pub async fn analyze_repo_dependencies( &self, repo_path: RepoPath, + sub_path: &Option, ) -> Result { 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 manifest_output = crawl_manifest(self.clone(), repo_path.clone(), entry_point).await?; - let engine_for_analyze = engine.clone(); let futures = manifest_output .crates .into_iter() .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)) }) .collect::>(); diff --git a/src/server/mod.rs b/src/server/mod.rs index 121beab..ea54c19 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -186,7 +186,7 @@ impl App { let qual = params.find("qual").expect("route param 'qual' 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); @@ -204,7 +204,7 @@ impl App { Ok(repo_path) => { let analyze_result = server .engine - .analyze_repo_dependencies(repo_path.clone()) + .analyze_repo_dependencies(repo_path.clone(), &extra_knobs.path) .await; match analyze_result { @@ -214,7 +214,7 @@ impl App { None, format, SubjectPath::Repo(repo_path), - badge_knobs, + extra_knobs, ); Ok(response) } @@ -223,7 +223,7 @@ impl App { Some(analysis_outcome), format, SubjectPath::Repo(repo_path), - badge_knobs, + extra_knobs, ); Ok(response) } @@ -345,7 +345,7 @@ impl App { }; 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 { Err(err) => { @@ -393,11 +393,13 @@ impl App { analysis_outcome: Option, format: StatusFormat, subject_path: SubjectPath, - badge_knobs: BadgeKnobs, + badge_knobs: ExtraConfig, ) -> Response { match format { 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 { static SELF_BASE_URL: Lazy = Lazy::new(|| env::var("BASE_URL").unwrap_or_else(|_| "http://localhost:8080".to_string())); +/// Configuration options supplied through Get Parameters #[derive(Debug, Clone, Default)] -pub struct BadgeKnobs { +pub struct ExtraConfig { + /// Badge style to show style: BadgeStyle, + /// Whether the inscription _"dependencies"_ should be abbreviated as _"deps"_ in the badge. compact: bool, + /// Path in which the crate resides within the repository + path: Option, } -impl BadgeKnobs { +impl ExtraConfig { fn from_query_string(qs: Option<&str>) -> Self { #[derive(Debug, Clone, Default, Deserialize)] - struct BadgeKnobsPartial { + struct ExtraConfigPartial { style: Option, compact: Option, + path: Option, } - let badge_knobs = qs - .and_then(|qs| serde_urlencoded::from_str::(qs).ok()) + let extra_config = qs + .and_then(|qs| serde_urlencoded::from_str::(qs).ok()) .unwrap_or_default(); Self { - style: badge_knobs.style.unwrap_or_default(), - compact: badge_knobs.compact.unwrap_or_default(), + style: extra_config.style.unwrap_or_default(), + compact: extra_config.compact.unwrap_or_default(), + path: extra_config.path, } } } diff --git a/src/server/views/badge.rs b/src/server/views/badge.rs index 7dc4bbe..270071e 100644 --- a/src/server/views/badge.rs +++ b/src/server/views/badge.rs @@ -3,11 +3,11 @@ use hyper::header::CONTENT_TYPE; use hyper::{Body, Response}; use crate::engine::AnalyzeDependenciesOutcome; -use crate::server::BadgeKnobs; +use crate::server::ExtraConfig; pub fn badge( analysis_outcome: Option<&AnalyzeDependenciesOutcome>, - badge_knobs: BadgeKnobs, + badge_knobs: ExtraConfig, ) -> Badge { let subject = if badge_knobs.compact { "deps" @@ -74,7 +74,7 @@ pub fn badge( pub fn response( analysis_outcome: Option<&AnalyzeDependenciesOutcome>, - badge_knobs: BadgeKnobs, + badge_knobs: ExtraConfig, ) -> Response { let badge = badge(analysis_outcome, badge_knobs).to_svg(); diff --git a/src/server/views/html/index.rs b/src/server/views/html/index.rs index c63fea0..626fe57 100644 --- a/src/server/views/html/index.rs +++ b/src/server/views/html/index.rs @@ -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." } } + 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();"; } } diff --git a/src/server/views/html/status.rs b/src/server/views/html/status.rs index ee5fa6e..721acc6 100644 --- a/src/server/views/html/status.rs +++ b/src/server/views/html/status.rs @@ -11,7 +11,7 @@ use crate::models::crates::{AnalyzedDependencies, AnalyzedDependency, CrateName} use crate::models::repo::RepoSite; use crate::models::SubjectPath; use crate::server::views::badge; -use crate::server::BadgeKnobs; +use crate::server::ExtraConfig; fn get_crates_url(name: impl AsRef) -> String { 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 { if count == 1 { "dependency" @@ -332,6 +349,7 @@ fn render_failure(subject_path: SubjectPath) -> Markup { fn render_success( analysis_outcome: AnalyzeDependenciesOutcome, subject_path: SubjectPath, + extra_config: ExtraConfig, ) -> Markup { let self_path = match subject_path { 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_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() { "is-danger" @@ -357,6 +375,15 @@ fn render_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! { section class=(format!("hero {}", hero_class)) { div class="hero-head" { (super::render_navbar()) } @@ -366,13 +393,23 @@ fn render_success( (render_title(&subject_path)) } + @if let Some(ref path) = extra_config.path { + p class="subtitle" { + (render_path(path)) + } + } + img src=(status_data_uri); } } div class="hero-footer" { div class="container" { pre class="is-size-7" { - (format!("[![dependency status]({}/status.svg)]({})", status_base_url, status_base_url)) + @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)) + } } } } @@ -416,6 +453,7 @@ fn render_success( pub fn render( analysis_outcome: Option, subject_path: SubjectPath, + extra_config: ExtraConfig, ) -> Response { let title = match subject_path { SubjectPath::Repo(ref repo_path) => { @@ -427,7 +465,7 @@ pub fn render( }; 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 { super::render_html(&title, render_failure(subject_path)) }