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