pricc_generator/
lib.rs

1use clap::Parser;
2use std::{
3    fs::{self},
4    path::PathBuf,
5};
6
7// Import all templates
8const TEMPLATE_MAIN: &str = include_str!("../templates/main.c.template");
9const TEMPLATE_HEADER: &str = include_str!("../templates/header.h.template");
10const TEMPLATE_MAKEFILE: &str = include_str!("../templates/Makefile.template");
11const TEMPLATE_README: &str = include_str!("../templates/README.md.template");
12const TEMPLATE_GITIGNORE: &str = include_str!("../templates/gitignore.template");
13const TEMPLATE_TEST: &str = include_str!("../templates/test.c.template");
14
15#[derive(Debug)]
16pub enum PriccError {
17    ConfigError(String),
18    IoError(std::io::Error),
19    TemplateError(String),
20}
21
22impl std::error::Error for PriccError {}
23impl std::fmt::Display for PriccError {
24    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
25        match self {
26            PriccError::ConfigError(msg) => write!(f, "Configuration error: {}", msg),
27            PriccError::IoError(e) => write!(f, "IO error: {}", e),
28            PriccError::TemplateError(msg) => write!(f, "Template error: {}", msg),
29        }
30    }
31}
32
33impl From<std::io::Error> for PriccError {
34    fn from(error: std::io::Error) -> Self {
35        PriccError::IoError(error)
36    }
37}
38
39#[derive(Debug)]
40pub struct PriccConfig {
41    pub name: String,
42    pub author: Option<String>,
43    pub proj_version: String,
44    pub description: Option<String>,
45    pub c_standard: CStandard,
46    pub include_tests: bool,
47}
48
49#[derive(Debug, Clone, Copy, clap::ValueEnum)]
50pub enum CStandard {
51    C89,
52    C99,
53    C11,
54    C17,
55}
56
57impl CStandard {
58    fn to_flag(&self) -> &'static str {
59        match self {
60            CStandard::C89 => "-std=c89",
61            CStandard::C99 => "-std=c99",
62            CStandard::C11 => "-std=c11",
63            CStandard::C17 => "-std=c17",
64        }
65    }
66}
67
68impl Default for PriccConfig {
69    fn default() -> Self {
70        Self {
71            name: String::new(),
72            author: None,
73            proj_version: "0.1.0".to_string(),
74            description: None,
75            c_standard: CStandard::C11,
76            include_tests: false,
77        }
78    }
79}
80
81impl PriccConfig {
82    pub fn new(name: String) -> Result<Self, PriccError> {
83        if name.is_empty() {
84            return Err(PriccError::ConfigError(
85                "Project name cannot be empty".to_string(),
86            ));
87        }
88
89        Ok(Self {
90            name,
91            ..Default::default()
92        })
93    }
94
95    pub fn with_author(mut self, author: String) -> Self {
96        self.author = Some(author);
97        self
98    }
99
100    pub fn with_tests(mut self) -> Self {
101        self.include_tests = true;
102        self
103    }
104}
105
106struct Template<'a> {
107    content: &'a str,
108}
109
110impl<'a> Template<'a> {
111    fn new(content: &'a str) -> Self {
112        Self { content }
113    }
114
115    fn render(&self, vars: &[(&str, &str)]) -> String {
116        let mut result = self.content.to_string();
117        for (key, value) in vars {
118            result = result.replace(&format!("{{{{{}}}}}", key), value);
119        }
120        result
121    }
122}
123
124pub struct ProjectBuilder {
125    config: PriccConfig,
126    root_dir: PathBuf,
127}
128
129impl ProjectBuilder {
130    pub fn new(config: PriccConfig) -> Self {
131        Self {
132            root_dir: PathBuf::from(&config.name),
133            config,
134        }
135    }
136
137    pub fn build(&self) -> Result<(), PriccError> {
138        self.create_directory_structure()?;
139        self.create_source_files()?;
140        self.create_build_files()?;
141        self.create_docs()?;
142        if self.config.include_tests {
143            self.create_test_structure()?;
144        }
145        self.init_git()?;
146
147        Ok(())
148    }
149
150    fn create_directory_structure(&self) -> Result<(), PriccError> {
151        let dirs = ["src", "include", "docs", "build", "bin"];
152
153        for dir in dirs.iter() {
154            fs::create_dir_all(self.root_dir.join(dir))?;
155        }
156
157        Ok(())
158    }
159
160    fn create_source_files(&self) -> Result<(), PriccError> {
161        // Create main.c
162        let main_template = Template::new(TEMPLATE_MAIN);
163        let main_content = main_template.render(&[
164            ("name", &self.config.name),
165            (
166                "author",
167                self.config.author.as_deref().unwrap_or("Anonymous"),
168            ),
169            (
170                "description",
171                self.config.description.as_deref().unwrap_or("A C project"),
172            ),
173        ]);
174
175        fs::write(self.root_dir.join("src").join("main.c"), main_content)?;
176
177        // Create header file
178        let header_template = Template::new(TEMPLATE_HEADER);
179        let header_content = header_template.render(&[
180            ("name", &self.config.name),
181            ("guard", &self.config.name.to_uppercase()),
182            (
183                "author",
184                self.config.author.as_deref().unwrap_or("Anonymous"),
185            ),
186            (
187                "description",
188                self.config.description.as_deref().unwrap_or("A C project"),
189            ),
190        ]);
191
192        fs::write(
193            self.root_dir
194                .join("include")
195                .join(format!("{}.h", self.config.name)),
196            header_content,
197        )?;
198
199        Ok(())
200    }
201
202    fn create_build_files(&self) -> Result<(), PriccError> {
203        let makefile_template = Template::new(TEMPLATE_MAKEFILE);
204        let makefile_content = makefile_template.render(&[
205            ("name", &self.config.name),
206            ("cstandard", self.config.c_standard.to_flag()),
207        ]);
208
209        fs::write(self.root_dir.join("Makefile"), makefile_content)?;
210
211        Ok(())
212    }
213
214    fn create_docs(&self) -> Result<(), PriccError> {
215        let readme_template = Template::new(TEMPLATE_README);
216        let readme_content = readme_template.render(&[
217            ("name", &self.config.name),
218            (
219                "description",
220                self.config.description.as_deref().unwrap_or("A C project"),
221            ),
222            (
223                "author",
224                self.config.author.as_deref().unwrap_or("Anonymous"),
225            ),
226            ("version", &self.config.proj_version),
227        ]);
228
229        fs::write(self.root_dir.join("README.md"), readme_content)?;
230
231        Ok(())
232    }
233
234    fn create_test_structure(&self) -> Result<(), PriccError> {
235        fs::create_dir_all(self.root_dir.join("tests"))?;
236
237        let test_template = Template::new(TEMPLATE_TEST);
238        let test_content = test_template.render(&[("name", &self.config.name)]);
239
240        fs::write(
241            self.root_dir.join("tests").join("test_main.c"),
242            test_content,
243        )?;
244
245        Ok(())
246    }
247
248    fn init_git(&self) -> Result<(), PriccError> {
249        let gitignore_template = Template::new(TEMPLATE_GITIGNORE);
250        let gitignore_content = gitignore_template.render(&[("name", &self.config.name)]);
251
252        fs::write(self.root_dir.join(".gitignore"), gitignore_content)?;
253
254        std::process::Command::new("git")
255            .arg("init")
256            .current_dir(&self.root_dir)
257            .output()
258            .map_err(|e| PriccError::IoError(e))?;
259
260        Ok(())
261    }
262}
263
264#[derive(Parser)]
265#[command(
266    name = "pricc",
267    about = "A C project generator",
268    version,
269    author = "Your Name <your.email@example.com>"
270)]
271pub struct Cli {
272    /// Name of the project
273    #[arg(required = true)]
274    name: String,
275
276    /// Author of the project
277    #[arg(short, long)]
278    author: Option<String>,
279
280    /// Description of the project
281    #[arg(short, long)]
282    description: Option<String>,
283
284    /// C standard to use
285    #[arg(short, long, value_enum, default_value_t = CStandard::C11)]
286    standard: CStandard,
287
288    /// Include test setup
289    #[arg(short, long)]
290    tests: bool,
291
292    /// Project version number
293    #[arg(short = 'v', long = "proj-version", default_value = "0.1.0")]
294    proj_version: String,
295}
296
297impl From<Cli> for PriccConfig {
298    fn from(cli: Cli) -> Self {
299        let mut config = PriccConfig::new(cli.name).expect("Invalid project name");
300
301        if let Some(author) = cli.author {
302            config = config.with_author(author);
303        }
304
305        if cli.tests {
306            config = config.with_tests();
307        }
308
309        config.description = cli.description;
310        config.proj_version = cli.proj_version;
311        config.c_standard = cli.standard;
312
313        config
314    }
315}
316
317pub fn run() -> Result<(), PriccError> {
318    let cli = Cli::parse();
319    let config: PriccConfig = cli.into();
320    let builder = ProjectBuilder::new(config);
321    builder.build()
322}