Skip to main content

cargo_spec/
init.rs

1use crate::{
2    errors::SpecError,
3    toml_parser::{Config, Metadata, Specification},
4};
5use miette::{IntoDiagnostic, Result, WrapErr};
6use std::{
7    collections::HashMap,
8    env,
9    fs::{create_dir, File},
10    io::Write,
11    path::PathBuf,
12};
13
14pub const DEFAULT_MANIFEST: &str = "Specification.toml";
15pub const DEFAULT_TEMPLATE: &str = "specification_template.md";
16
17pub fn new(name: String) -> Result<()> {
18    let path = env::current_dir().into_diagnostic()?;
19    init(Some(name), path)
20}
21
22pub fn init(name: Option<String>, path: PathBuf) -> Result<()> {
23    // we extrapolate the name of the spec from the directory name
24    let mut name = if let Some(name) = name {
25        name
26    } else {
27        match path.file_name() {
28            Some(dir_name) => dir_name.to_string_lossy().to_string(),
29            None => {
30                return Err(SpecError::BadPath(path)).into_diagnostic();
31            }
32        }
33    };
34
35    // if the directory doesn't exist, create it
36    if !path.is_dir() {
37        create_dir(&path).into_diagnostic().wrap_err_with(|| {
38            format!(
39                "cannot create the specification directory {}",
40                path.display()
41            )
42        })?;
43    } else {
44        // otherwise make sure there isn't already a spec in there
45        let read_dir = path.read_dir().into_diagnostic()?;
46
47        for dir_entry in read_dir.flatten() {
48            let spec_file_detected = dir_entry.file_name().to_string_lossy() == DEFAULT_MANIFEST;
49            let template_file_detected =
50                dir_entry.file_name().to_string_lossy() == DEFAULT_TEMPLATE;
51            if spec_file_detected || template_file_detected {
52                return Err(SpecError::SpecAlreadyExists(path)).into_diagnostic();
53            }
54        }
55    }
56
57    // create the files
58    let manifest_path = path.join(DEFAULT_MANIFEST);
59    let mut manifest_file = File::create(&manifest_path).into_diagnostic().wrap_err_with(|| format!("cannot create the specification file {}, make sure you pass a specification toml file via --specification-path", manifest_path.display()))?;
60
61    let template_path = path.join(DEFAULT_TEMPLATE);
62    let mut template_file = File::create(&template_path).into_diagnostic().wrap_err_with(|| format!("cannot create the specification template file {}, make sure you pass a specification toml file via --specification-path", template_path.display()))?;
63
64    // fill the specification manifest
65    let metadata = Metadata {
66        name: name.clone(),
67        description: Some("some description".to_string()),
68        version: None,
69        authors: vec!["your name".to_string()],
70    };
71    let config = Config {
72        template: DEFAULT_TEMPLATE.to_string(),
73    };
74    let specification = Specification {
75        metadata,
76        config,
77        sections: HashMap::new(),
78    };
79
80    let manifest_content =
81        toml::to_vec(&specification).expect("couldn't serialize the default manifest");
82
83    manifest_file
84        .write_all(&manifest_content)
85        .into_diagnostic()
86        .wrap_err_with(|| {
87            format!(
88                "couldn't write to the specification file {}",
89                manifest_path.display()
90            )
91        })?;
92
93    // create the template file
94    let title = {
95        let first_char = name.remove(0);
96        let first_char = first_char.to_uppercase();
97        format!("{first_char}{name}")
98    };
99    let template_content = format!("# {title}\n\n My specification\n");
100
101    template_file
102        .write_all(template_content.as_bytes())
103        .into_diagnostic()
104        .wrap_err_with(|| {
105            format!(
106                "couldn't write to the specification template file {}",
107                template_path.display()
108            )
109        })?;
110
111    Ok(())
112}