boots_core/config/
parser.rs

1use super::types::*;
2use crate::error::{BootsError, Result};
3use std::process::Command;
4
5/// Read git config value, returning empty string if not found or on error
6fn get_git_config(key: &str) -> String {
7    Command::new("git")
8        .args(["config", "--get", key])
9        .output()
10        .ok()
11        .and_then(|output| {
12            if output.status.success() {
13                String::from_utf8(output.stdout).ok()
14            } else {
15                None
16            }
17        })
18        .map(|s| s.trim().to_string())
19        .unwrap_or_default()
20}
21
22pub fn parse_options(
23    project_type: ProjectType,
24    name: &str,
25    options: Option<&str>,
26) -> Result<ProjectConfig> {
27    let author_name = get_git_config("user.name");
28    let author_email = get_git_config("user.email");
29
30    let mut config = ProjectConfig {
31        name: name.to_string(),
32        project_type,
33        persistence: None,
34        frontend: None,
35        has_grpc: false,
36        has_http: true,
37        has_client: false,
38        author_name,
39        author_email,
40    };
41
42    if let Some(opts) = options {
43        let opts_list: Vec<&str> = opts.split(',').map(|s| s.trim()).collect();
44
45        // If 'sample' option is present, ignore all other options
46        if opts_list.contains(&"sample") {
47            let other_options: Vec<&str> = opts_list
48                .iter()
49                .filter(|o| **o != "sample")
50                .copied()
51                .collect();
52            if !other_options.is_empty() {
53                eprintln!(
54                    "Warning: 'sample' option ignores other options: {:?}",
55                    other_options
56                );
57            }
58            // Sample project uses postgres and spa by default
59            config.persistence = Some(PersistenceType::Postgres);
60            config.frontend = Some(FrontendType::Spa);
61            return Ok(config);
62        }
63
64        for option in opts_list {
65            match option {
66                "postgres" => config.persistence = Some(PersistenceType::Postgres),
67                "sqlite" => config.persistence = Some(PersistenceType::Sqlite),
68                "file" => config.persistence = Some(PersistenceType::File),
69                "grpc" => config.has_grpc = true,
70                "http" => config.has_http = true,
71                "client" => config.has_client = true,
72                "persistence" => {
73                    if config.persistence.is_none() {
74                        config.persistence = Some(PersistenceType::File);
75                    }
76                }
77                // Frontend options: fe:spa or fe:ssr
78                "fe:spa" | "fe-spa" | "spa" => config.frontend = Some(FrontendType::Spa),
79                "fe:ssr" | "fe-ssr" | "ssr" => config.frontend = Some(FrontendType::Ssr),
80                "" => continue,
81                _ => return Err(BootsError::InvalidOption(option.to_string())),
82            }
83        }
84    }
85
86    Ok(config)
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92
93    #[test]
94    fn test_parse_service_default() {
95        let config = parse_options(ProjectType::Service, "test-svc", None).unwrap();
96        assert_eq!(config.project_type, ProjectType::Service);
97        assert!(config.persistence.is_none());
98        assert!(!config.has_grpc);
99        assert!(config.has_http);
100    }
101
102    #[test]
103    fn test_parse_service_with_postgres() {
104        let config = parse_options(ProjectType::Service, "test-svc", Some("postgres")).unwrap();
105        assert_eq!(config.persistence, Some(PersistenceType::Postgres));
106    }
107
108    #[test]
109    fn test_parse_service_with_multiple_options() {
110        let config =
111            parse_options(ProjectType::Service, "test-svc", Some("postgres,grpc")).unwrap();
112        assert_eq!(config.persistence, Some(PersistenceType::Postgres));
113        assert!(config.has_grpc);
114    }
115
116    #[test]
117    fn test_parse_cli_with_client() {
118        let config = parse_options(ProjectType::Cli, "test-cli", Some("client")).unwrap();
119        assert!(config.has_client);
120    }
121
122    #[test]
123    fn test_invalid_option() {
124        let result = parse_options(ProjectType::Service, "test", Some("invalid_option"));
125        assert!(result.is_err());
126    }
127
128    #[test]
129    fn test_parse_service_with_spa() {
130        let config = parse_options(ProjectType::Service, "test-svc", Some("fe:spa")).unwrap();
131        assert_eq!(config.frontend, Some(FrontendType::Spa));
132    }
133
134    #[test]
135    fn test_parse_service_with_ssr() {
136        let config = parse_options(ProjectType::Service, "test-svc", Some("fe:ssr")).unwrap();
137        assert_eq!(config.frontend, Some(FrontendType::Ssr));
138    }
139
140    #[test]
141    fn test_parse_service_with_postgres_and_spa() {
142        let config =
143            parse_options(ProjectType::Service, "test-svc", Some("postgres,fe:spa")).unwrap();
144        assert_eq!(config.persistence, Some(PersistenceType::Postgres));
145        assert_eq!(config.frontend, Some(FrontendType::Spa));
146    }
147
148    #[test]
149    fn test_parse_sample_default() {
150        let config = parse_options(ProjectType::Sample, "test-board", Some("sample")).unwrap();
151        assert_eq!(config.project_type, ProjectType::Sample);
152        assert_eq!(config.persistence, Some(PersistenceType::Postgres));
153        assert_eq!(config.frontend, Some(FrontendType::Spa));
154    }
155
156    #[test]
157    fn test_parse_sample_ignores_other_options() {
158        // sample should ignore postgres,grpc and use default sample config
159        let config = parse_options(
160            ProjectType::Sample,
161            "test-board",
162            Some("sample,grpc,sqlite"),
163        )
164        .unwrap();
165        assert_eq!(config.persistence, Some(PersistenceType::Postgres)); // not sqlite
166        assert!(!config.has_grpc); // grpc ignored
167        assert_eq!(config.frontend, Some(FrontendType::Spa));
168    }
169}