npmgen_core/project/
identity.rs1use super::ProjectError;
2
3#[derive(Debug, Clone)]
7pub struct Identity {
8 pub scope: String,
9 pub name: String,
10 pub git_url: String,
11}
12
13impl Identity {
14 pub fn from_repository(
17 repository: &str,
18 scope_override: Option<&str>,
19 ) -> Result<Self, ProjectError> {
20 let trimmed = repository.trim();
23 let trimmed = trimmed.strip_suffix('/').unwrap_or(trimmed);
24 let path = trimmed.strip_suffix(".git").unwrap_or(trimmed);
25 let mut segments = path.rsplit('/');
26 let name = segments.next().unwrap_or_default();
27 let owner = segments.next().unwrap_or_default();
28 if owner.is_empty() || name.is_empty() {
29 return Err(ProjectError::MissingRepository);
30 }
31 Ok(Self {
32 scope: scope_override
33 .map(str::to_owned)
34 .unwrap_or_else(|| format!("@{owner}")),
35 name: name.to_owned(),
36 git_url: format!("git+{path}.git"),
37 })
38 }
39}
40
41#[cfg(test)]
42mod tests {
43 use super::*;
44
45 #[test]
46 fn derives_scope_name_and_git_url() {
47 let identity = Identity::from_repository("https://github.com/gglinnk/nocmd", None).unwrap();
48 assert_eq!(identity.scope, "@gglinnk");
49 assert_eq!(identity.name, "nocmd");
50 assert_eq!(identity.git_url, "git+https://github.com/gglinnk/nocmd.git");
51 }
52
53 #[test]
54 fn tolerates_trailing_slash_and_dot_git() {
55 let identity =
56 Identity::from_repository("https://github.com/acme/tool.git/", None).unwrap();
57 assert_eq!(identity.scope, "@acme");
58 assert_eq!(identity.name, "tool");
59 assert_eq!(identity.git_url, "git+https://github.com/acme/tool.git");
60 }
61
62 #[test]
63 fn scope_override_is_used_verbatim() {
64 let identity =
65 Identity::from_repository("https://github.com/gglinnk/tool", Some("@other")).unwrap();
66 assert_eq!(identity.scope, "@other");
67 }
68
69 #[test]
70 fn missing_owner_is_rejected() {
71 assert!(Identity::from_repository("nocmd", None).is_err());
72 assert!(Identity::from_repository("", None).is_err());
73 }
74}