1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#![warn(missing_docs)]
//! # cmps
//!
//! This library is the backend for the cmps cli tool.
//! It can be used to create files and automatically fill them with a default template.
//! The template is determined from the file extension.
use dirs;
use log::*;
use std::env;
use std::fs;
use std::fs::File;
use std::io;
use std::io::prelude::*;
use std::path::{Path, PathBuf};

/// The main function of this library.
/// It takes a path to either a non-existing file, or an empty existing file.
/// The extension should usually match the extension in the path, but can be overriden by providing a different extension.
/// If extension is None, an empty file will be created.
pub fn compose<P: AsRef<Path>>(path: P, extension: Option<&str>) -> io::Result<File> {
    trace!("Entered compose function.");
    let mut file = match fs::metadata(&path) {
        Ok(metadata) => {
            if metadata.len() == 0 {
                File::create(&path)?
            } else {
                return Err(io::Error::new(
                    io::ErrorKind::AlreadyExists,
                    "File already exists!",
                ));
            }
        }
        Err(error) => match error.kind() {
            io::ErrorKind::NotFound => File::create(&path)?,
            _ => return Err(error),
        },
    };

    if let Some(extension) = extension {
        // Convert Result<usize> into Result<File>
        if let Err(error) = fill_file(&mut file, extension) {
            return Err(error);
        };
    }
    Ok(file)
}

fn fill_file(file: &mut File, extension: &str) -> io::Result<usize> {
    trace!("Entered fill_file function.");
    let contents = template_contents(extension).unwrap_or_else(|| {
        warn!(
            "No template file found for extension {}, creating an empty file",
            &extension
        );
        String::default()
    });

    return file.write(contents.as_bytes());
}

fn template_contents(extension: &str) -> Option<String> {
    trace!("Entered template_contents function.");
    let extension_path: PathBuf = ["templates", extension].iter().collect();
    let base_paths = [
        dirs::config_dir().unwrap().join("cmps"),
        dirs::data_local_dir().unwrap().join("cmps"),
        PathBuf::from(env!("CARGO_MANIFEST_DIR")),
    ];
    let template_paths = base_paths.iter().map(|path| path.join(&extension_path));

    for path in template_paths {
        if path.exists() {
            let contents = fs::read_to_string(&path);
            match contents {
                Ok(contents) => return Some(contents),
                Err(error) => warn!(
                    "Template file '{}' could not be read!\nError: {}",
                    path.display(),
                    &error
                ),
            }
        } else {
            info!(
                "Template file '{}' does not exist, skipping...",
                path.display()
            );
        }
    }
    None
}