backhopper_xref_reader/
application.rs1use std::ffi::OsStr;
8use std::path::{Path, PathBuf};
9
10use backhopper_core::ApplicationName;
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14#[serde(tag = "kind", content = "value", rename_all = "snake_case")]
15pub enum ApplicationAssignment {
16 Assigned(ApplicationName),
17 NoLayoutMatch { path: PathBuf },
18 Ambiguous { candidates: Vec<ApplicationName> },
19}
20
21impl ApplicationAssignment {
22 pub fn assigned(&self) -> Option<&ApplicationName> {
23 if let ApplicationAssignment::Assigned(a) = self {
24 Some(a)
25 } else {
26 None
27 }
28 }
29}
30
31#[derive(Debug, Clone, Default, PartialEq, Eq)]
39pub struct ProjectLayout {
40 prefixes: Vec<PathBuf>,
41}
42
43impl ProjectLayout {
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn rabbitmq_main() -> Self {
49 let mut l = Self::new();
50 l.add_prefix("deps");
51 l.add_prefix("apps");
52 l
53 }
54
55 pub fn add_prefix(&mut self, prefix: impl Into<PathBuf>) -> &mut Self {
56 self.prefixes.push(prefix.into());
57 self
58 }
59
60 pub fn resolve(&self, path: &Path) -> ApplicationAssignment {
63 let mut candidates: Vec<ApplicationName> = Vec::new();
64 for prefix in &self.prefixes {
65 if let Some(app) = match_under_prefix(prefix, path) {
66 candidates.push(app);
67 }
68 }
69 candidates.sort();
70 candidates.dedup();
71 match candidates.len() {
72 0 => ApplicationAssignment::NoLayoutMatch {
73 path: path.to_path_buf(),
74 },
75 1 => ApplicationAssignment::Assigned(candidates.remove(0)),
76 _ => ApplicationAssignment::Ambiguous { candidates },
77 }
78 }
79}
80
81fn match_under_prefix(prefix: &Path, path: &Path) -> Option<ApplicationName> {
82 let components: Vec<&OsStr> = path.components().map(|c| c.as_os_str()).collect();
84 let prefix_os: Vec<&OsStr> = prefix.components().map(|c| c.as_os_str()).collect();
85 if prefix_os.is_empty() {
86 return None;
87 }
88 let plen = prefix_os.len();
89 let mut i = 0;
90 while i + plen < components.len() {
91 if components[i..i + plen] == prefix_os[..] {
92 let app = components[i + plen].to_string_lossy();
93 return ApplicationName::new(app.into_owned()).ok();
94 }
95 i += 1;
96 }
97 None
98}