1pub fn session_id() -> String {
2 let ppid = unsafe { libc::getppid() };
3 ppid.to_string()
4}
5
6pub fn project_id() -> String {
7 #[cfg(feature = "vipune-store")]
8 {
9 vipune::detect_project(None)
10 }
11 #[cfg(not(feature = "vipune-store"))]
12 {
13 detect_project_fallback()
14 }
15}
16
17#[cfg(not(feature = "vipune-store"))]
18fn detect_project_fallback() -> String {
19 if let Ok(output) = std::process::Command::new("git")
21 .args(["remote", "get-url", "origin"])
22 .output()
23 {
24 if output.status.success() {
25 let url = String::from_utf8_lossy(&output.stdout).trim().to_string();
26 if let Some(name) = url.rsplit('/').next() {
27 let name = name.strip_suffix(".git").unwrap_or(name);
28 if !name.is_empty() {
29 return name.to_string();
30 }
31 }
32 }
33 }
34
35 if let Ok(output) = std::process::Command::new("git")
37 .args(["rev-parse", "--show-toplevel"])
38 .output()
39 {
40 if output.status.success() {
41 let path = String::from_utf8_lossy(&output.stdout).trim().to_string();
42 if let Some(name) = path.rsplit('/').next() {
43 if !name.is_empty() {
44 return name.to_string();
45 }
46 }
47 }
48 }
49
50 if let Ok(cwd) = std::env::current_dir() {
52 if let Some(name) = cwd.file_name() {
53 return name.to_string_lossy().to_string();
54 }
55 }
56
57 "unknown".to_string()
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 #[test]
65 fn test_session_id_is_nonzero() {
66 let id = session_id();
67 let pid: u32 = id.parse().expect("session_id should be numeric");
68 assert!(pid > 0);
69 }
70
71 #[test]
72 fn test_session_id_stable() {
73 assert_eq!(session_id(), session_id());
74 }
75
76 #[test]
77 fn test_project_id_nonempty() {
78 let id = project_id();
79 assert!(!id.is_empty());
80 }
81
82 #[test]
87 fn test_project_id_is_string() {
88 let id = project_id();
90 assert!(!id.is_empty(), "project_id must not be empty");
91 assert!(
92 id.is_ascii() || !id.is_empty(),
93 "project_id must be a string"
94 );
95 }
96
97 #[test]
98 fn test_project_id_no_newlines() {
99 let id = project_id();
101 assert!(
102 !id.contains('\n'),
103 "project_id must not contain newlines, got: {id:?}"
104 );
105 }
106
107 #[test]
108 #[cfg(not(feature = "vipune-store"))]
109 fn test_detect_project_fallback_cwd_fallback() {
110 let tmp = tempfile::tempdir().expect("tempdir");
113 let original = std::env::current_dir().expect("cwd");
114 std::env::set_current_dir(tmp.path()).expect("set_current_dir");
115
116 let id = detect_project_fallback();
117
118 std::env::set_current_dir(&original).expect("restore cwd");
119 assert!(!id.is_empty(), "fallback must not be empty");
121 }
122}