project_init/
lib.rs

1//! This library provides the functions/structs/methods used by the main
2//! binary. They are included
3//! here in the hopes that they can be illuminating to users.
4#![allow(clippy::too_many_arguments)]
5#![allow(clippy::cognitive_complexity)]
6
7extern crate case;
8extern crate clap;
9extern crate colored;
10extern crate git2;
11extern crate heck;
12extern crate rustache;
13#[macro_use]
14extern crate serde_derive;
15extern crate tempdir;
16extern crate time;
17extern crate toml;
18
19use case::*;
20use colored::*;
21use heck::*;
22use rustache::{HashBuilder, VecBuilder};
23use std::fs;
24use std::fs::File;
25use std::io::prelude::*;
26use std::path::Path;
27use std::path::PathBuf;
28use toml::Value::Table;
29
30pub mod includes;
31pub mod render;
32pub mod repo;
33pub mod types;
34
35/// Given a filepath, read the .toml file there as containing the
36/// directories/templates.
37/// If no such file is found, read from global template directory in
38/// `$HOME/.pi_templates/`.
39pub fn read_toml_dir(template_path: &str, home: PathBuf) -> (types::Project, bool) {
40    let (mut template_file, is_global_template) = if let Ok(f) = File::open(&template_path) {
41        (f, false)
42    } else if let Ok(f) = {
43        let mut p = home;
44        p.push(".pi_templates/");
45        p.push(template_path);
46        File::open(p)
47    } {
48        (f, true)
49    } else {
50        println!(
51            "{}: File {:?} could not be opened. Check that it exists.",
52            "Error".red(),
53            template_path
54        );
55        std::process::exit(0x0f00);
56    };
57    let mut template = String::new();
58    template_file
59        .read_to_string(&mut template)
60        .expect("Failed to read file"); // we can panic because we already errored if the file didn't exist.
61    (read_toml_str(&template, template_path), is_global_template)
62}
63
64/// Read a string containing a toml file
65pub fn read_toml_str(template: &str, template_path: &str) -> types::Project {
66    let extract = toml::from_str(template);
67    if let Ok(t) = extract {
68        t
69    } else if let Err(e) = extract {
70        println!("Error parsing {:?}: {}", template_path, e);
71        std::process::exit(0x0f00);
72    } else {
73        std::process::exit(0x0f00);
74    }
75}
76
77/// Given a `PathBuf`, read the .toml file there as a configuration file.
78pub fn read_toml_config(config_path: &std::path::PathBuf) -> types::Config {
79    let file = if let Ok(f) = File::open(&config_path) {
80        Some(f)
81    } else {
82        println!(
83            "{}: File {:?} could not be opened. Check that it exists.",
84            "Warning".yellow(),
85            config_path
86        );
87        None
88    };
89    let mut toml_str = String::new();
90    let maybe_file = file.map(|mut x| x.read_to_string(&mut toml_str));
91    let extract = toml::from_str(&toml_str);
92    if maybe_file.is_some() && maybe_file.unwrap().is_ok() {
93        if let Ok(t) = extract {
94            t
95        } else if let Err(e) = extract {
96            println!("Error parsing {:?}: {}", config_path, e);
97            std::process::exit(0x0f00);
98        } else {
99            std::process::exit(0x0f00);
100        }
101    } else {
102        eprintln!(
103            "{}: No ~/.pi.toml found. Using defaults.",
104            "Warning".yellow()
105        );
106        types::Config {
107            version_control: None,
108            author: None,
109            license: None,
110            user: None,
111        }
112    }
113}
114
115pub fn init_helper(
116    home: PathBuf,
117    project_dir: &str,
118    decoded: types::Config,
119    author: types::Author,
120    name: &str,
121    year: i32,
122    current_date: &str,
123    force: bool,
124    parsed_toml: types::Project,
125    is_global_project: bool,
126) {
127    let project = if is_global_project {
128        let mut p = home;
129        p.push(".pi_templates/");
130        p.push(project_dir);
131        p.to_str().unwrap().to_string()
132    } else {
133        project_dir.to_string()
134    };
135    let parsed_dirs = parsed_toml.files;
136    let parsed_config = parsed_toml.config;
137
138    // set license if it's set
139    let (license_contents, license_name) =
140        // prefer project-specific license over global
141        if let Some(l) = parsed_toml.license {
142            match l.as_str() {
143                "BSD3" => (Some(includes::BSD3), "BSD3"),
144                "BSD" => (Some(includes::BSD), "BSD"),
145                "MIT" => (Some(includes::MIT), "MIT"),
146                "GPL3" => (Some(includes::GPL3), "GLP3"),
147                "AllRightsReserved" => (Some(includes::BSD3), "AllRightsReserved"),
148                _ => { println!("{}: requested license not found. Defaulting to AllRightsReserved","Warning".yellow()) 
149                        ; (Some(includes::ALL_RIGHTS_RESERVED), "AllRightsReserved") }
150            }
151        }
152        else if let Some(l) = decoded.license {
153            match l.as_str() {
154                "BSD3" => (Some(includes::BSD3), "BSD3"),
155                "BSD" => (Some(includes::BSD), "BSD"),
156                "MIT" => (Some(includes::MIT), "MIT"),
157                "GPL3" => (Some(includes::GPL3), "GLP3"),
158                "AllRightsReserved" => (Some(includes::BSD3), "AllRightsReserved"),
159                _ => { println!("{}: requested license not found. Defaulting to AllRightsReserved","Warning".yellow()) 
160                        ; (Some(includes::ALL_RIGHTS_RESERVED), "AllRightsReserved") }
161            }
162        }
163        else {
164            (None,"")
165        };
166
167    // set version
168    let version = if let Some(config) = parsed_config.clone() {
169        if let Some(v) = config.version {
170            v
171        } else {
172            "0.1.0".to_string()
173        }
174    } else {
175        eprintln!(
176            "{}: no version info found, defaulting to '0.1.0'",
177            "Warning".yellow()
178        );
179        "0.1.0".to_string()
180    };
181
182    // set github username to null if it's not provided
183    let github_username = if let Some(uname) = author.github_username {
184        uname
185    } else {
186        eprintln!(
187            "{}: no github username found, defaulting to null",
188            "Warning".yellow()
189        );
190        "".to_string()
191    };
192
193    // make user_keys into a vector; prepare to insert them into the `HashBuilder`
194    let user_keys = if let Some(u) = parsed_toml.user {
195        match u.toml {
196            Table(t) => Some(t),
197            _ => None,
198        }
199    } else {
200        None
201    };
202
203    // make user_keys into a vector; prepare to insert them into the `HashBuilder`
204    let user_keys_global = if let Some(u) = decoded.user {
205        match u.toml {
206            Table(t) => Some(t),
207            _ => None,
208        }
209    } else {
210        None
211    };
212
213    // Make a hash for inserting stuff into templates.
214    let mut hash = HashBuilder::new();
215    // project-specific
216    if let Some(x) = user_keys {
217        for (key, value) in &x {
218            if let Some(a) = value.as_str() {
219                hash = hash.insert(key, a);
220            }
221        }
222    }
223    // global
224    if let Some(x) = user_keys_global {
225        for (key, value) in &x {
226            if let Some(a) = value.as_str() {
227                hash = hash.insert(key, a);
228            }
229        }
230    }
231    // add the normal stuff
232    hash = hash
233        .insert("project", name)
234        .insert("Project", name.to_capitalized())
235        .insert("ProjectCamelCase", name.to_camel_case())
236        .insert("year", year)
237        .insert("name", author.name)
238        .insert("version", version)
239        .insert("email", author.email)
240        .insert("github_username", github_username)
241        .insert("license", license_name)
242        .insert("date", current_date);
243
244    // check if the directory exists and exit, if we haven't forced an overwrite.
245    if Path::new(name).exists() && !force {
246        println!(
247            "Path '{}' already exists. Rerun with -f or --force to overwrite.",
248            name
249        );
250        std::process::exit(0x0f00);
251    };
252
253    // create directories
254    let _ = fs::create_dir(name);
255    if let Some(dirs_pre) = parsed_dirs.directories {
256        render::render_dirs(dirs_pre, &hash, name);
257    }
258
259    // create a list of files contained in the project, and create those files.
260    // TODO should include templates/scripts/etc.
261    let files = if let Some(files_pre) = parsed_dirs.files {
262        render::render_files(files_pre, &hash, name) // FIXME files need to have a newline insert in between them?
263    } else {
264        VecBuilder::new()
265    };
266
267    // create license if it was asked for
268    if let Some(lic) = license_contents {
269        render::render_file(lic, name, "LICENSE", &hash);
270    }
271
272    // render readme if requested
273    if let Some(readme) = parsed_toml.with_readme {
274        if readme {
275            render::render_file(includes::README, name, "README.md", &hash);
276        }
277    }
278
279    // Make a hash for inserting stuff into templates.
280    hash = hash.insert("files", files);
281
282    // render templates
283    render::render_templates(&project, name, &hash, parsed_dirs.templates, false);
284
285    // render scripts, i.e. files that should be executable.
286    render::render_templates(&project, name, &hash, parsed_dirs.scripts, true);
287
288    // initialize version control
289    if let Some(config) = parsed_config {
290        if let Some(vc) = config.version_control {
291            match vc.as_str() {
292                "git" => repo::git_init(name),
293                "hg" | "mercurial" => repo::hg_init(name),
294                "pijul" => repo::pijul_init(name),
295                "darcs" => repo::darcs_init(name),
296                _ => {
297                    eprintln!(
298                        "{}: version control {} is not yet supported. Supported version control tools are darcs, pijul, mercurial, and git.",
299                        "Error".red(),
300                        vc
301                    );
302                }
303            }
304        }
305    } else if let Some(vc) = decoded.version_control {
306        match vc.as_str() {
307            "git" => repo::git_init(name),
308            "hg" | "mercurial" => repo::hg_init(name),
309            "pijul" => repo::pijul_init(name),
310            "darcs" => repo::darcs_init(name),
311            _ => {
312                eprintln!(
313                    "{}: version control {} is not yet supported. Supported version control tools are darcs, pijul, mercurial, and git.",
314                    "Error".red(),
315                    vc
316                );
317            }
318        }
319    }
320
321    // Print that we're done
322    println!("Finished initializing project in {}/", name);
323}