Skip to main content

kaizen/core/
project_identity.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2//! Workspace label derived at export boundary.
3
4use std::path::Path;
5
6pub fn project_name(workspace: &Path) -> Option<String> {
7    github_origin(workspace)
8        .as_deref()
9        .and_then(project_name_from_github_origin)
10        .or_else(|| fallback_name(workspace))
11}
12
13pub fn project_name_from_github_origin(origin: &str) -> Option<String> {
14    github_path(origin.trim()).and_then(repo_basename)
15}
16
17fn github_origin(workspace: &Path) -> Option<String> {
18    let out = std::process::Command::new("git")
19        .arg("-C")
20        .arg(workspace)
21        .args(["remote", "get-url", "origin"])
22        .output()
23        .ok()?;
24    out.status
25        .success()
26        .then(|| String::from_utf8(out.stdout).ok())?
27        .map(|s| s.trim().to_string())
28        .filter(|s| !s.is_empty())
29}
30
31fn github_path(origin: &str) -> Option<&str> {
32    origin
33        .strip_prefix("https://github.com/")
34        .or_else(|| origin.strip_prefix("http://github.com/"))
35        .or_else(|| origin.strip_prefix("git@github.com:"))
36        .or_else(|| origin.strip_prefix("ssh://git@github.com/"))
37}
38
39fn repo_basename(path: &str) -> Option<String> {
40    path.rsplit('/')
41        .next()
42        .map(|s| s.strip_suffix(".git").unwrap_or(s))
43        .filter(|s| !s.is_empty())
44        .map(str::to_string)
45}
46
47fn fallback_name(workspace: &Path) -> Option<String> {
48    workspace
49        .file_name()
50        .and_then(|s| s.to_str())
51        .filter(|s| !s.is_empty())
52        .map(str::to_string)
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58
59    #[test]
60    fn parses_github_origin_forms() {
61        let cases = [
62            ("https://github.com/org/kaizen.git", "kaizen"),
63            ("git@github.com:org/kaizen.git", "kaizen"),
64            ("ssh://git@github.com/org/kaizen.git", "kaizen"),
65        ];
66        for (origin, expected) in cases {
67            assert_eq!(
68                project_name_from_github_origin(origin).as_deref(),
69                Some(expected)
70            );
71        }
72    }
73
74    #[test]
75    fn ignores_non_github_origin() {
76        assert_eq!(
77            project_name_from_github_origin("https://gitlab.com/org/kaizen.git"),
78            None
79        );
80    }
81}