mdmg 0.1.6

A scaffold prototype code tool
Documentation
use itertools::{concat, Itertools};

use crate::error::MdmgError;
use crate::file::FileName;
use crate::template::Template;
use crate::Result;

use std::fs::{read_dir, read_to_string};
use std::path::{Path, PathBuf};

pub trait TemplateRepository {
    fn list(&self) -> Result<Vec<FileName>>;
    fn resolve(&self, template_name: String) -> Result<Template>;
}

pub struct FSTemplateRepository {
    path: PathBuf,
}

impl FSTemplateRepository {
    pub fn new<T: Into<PathBuf>>(path: T) -> FSTemplateRepository {
        FSTemplateRepository { path: path.into() }
    }
}

impl FSTemplateRepository {
    #[cfg(not(target_os = "windows"))]
    fn xdg_files(&self) -> Vec<FileName> {
        let xdg_dir = xdg::BaseDirectories::with_prefix("mdmg")
            .map(|x| x.list_data_files(""))
            .unwrap_or(vec![]);
        xdg_dir.iter().fold(vec![], |acc, path| {
            let file_name_opt: Option<FileName> = path
                .file_stem()
                .and_then(|name| name.to_str())
                .and_then(|name| Some(FileName::new(name)));

            match file_name_opt {
                Some(file_name) => acc
                    .into_iter()
                    .chain(vec![file_name])
                    .collect::<Vec<FileName>>(),
                None => acc,
            }
        })
    }

    #[cfg(target_os = "windows")]
    fn xdg_files(&self) -> Vec<FileName> {
        vec![]
    }

    #[cfg(not(target_os = "windows"))]
    fn find_xdg_template_path(&self, name: String) -> Option<PathBuf> {
        xdg::BaseDirectories::with_prefix("mdmg")
            .map(|x| x.find_data_file(name))
            .unwrap_or(None)
    }

    #[cfg(target_os = "windows")]
    fn find_xdg_template_path(&self, name: String) -> Option<PathBuf> {
        None
    }
}

impl TemplateRepository for FSTemplateRepository {
    fn list(&self) -> Result<Vec<FileName>> {
        let local_dir = read_dir(&self.path)?.flatten();

        let local_dir_file_names = local_dir
            .map(|entry| {
                let filename_result = entry.file_name().into_string();
                filename_result
                    .map(|filename| {
                        let path = Path::new(&filename);
                        match path.file_stem() {
                            Some(name) => FileName::new(name.to_string_lossy()),
                            None => FileName::new(filename),
                        }
                    })
                    .map_err(MdmgError::FileNameConvertError)
            })
            .collect::<Result<Vec<_>>>()
            .unwrap_or(vec![]);

        let file_names = concat(vec![local_dir_file_names, self.xdg_files()]);

        Ok(file_names.into_iter().sorted().collect::<Vec<FileName>>())
    }
    fn resolve(&self, template_name: String) -> Result<Template> {
        let template_file_name = format!("{}.md", template_name);
        let local_template_path_buf = PathBuf::from(&self.path).join(&template_file_name);
        let local_template_path = if local_template_path_buf.exists() {
            Some(local_template_path_buf)
        } else {
            None
        };
        let xdg_data_dir_template_path = self.find_xdg_template_path(template_file_name);
        let template_body = [local_template_path, xdg_data_dir_template_path]
            .into_iter()
            .find_map(|s| s.map(|p| read_to_string(p).ok()))
            .flatten();

        let body = template_body.ok_or(MdmgError::TemplateIsNotFound(template_name))?;

        Ok(Template::new(body.trim()))
    }
}

#[cfg(test)]
mod tests {
    use super::{FSTemplateRepository, FileName, TemplateRepository};
    use crate::template::Template;
    use std::env::{current_dir, set_var, var};

    fn with_xdg_data_path<O: FnOnce() -> ()>(closure: O) {
        let xdg_data_dir = var("XDG_DATA_HOME").unwrap_or("".to_string());
        let current = current_dir().unwrap();
        set_var("XDG_DATA_HOME", current.join("support/xdg_data_dir"));
        closure();
        set_var("XDG_DATA_HOME", xdg_data_dir);
    }

    #[test]
    #[cfg_attr(not(feature = "fs-test"), ignore)]
    #[cfg(not(target_os = "windows"))]
    pub fn test_fstemplate_repository_list_return_to_files() {
        with_xdg_data_path(|| {
            let repository =
                FSTemplateRepository::new("./support/fs_template_repository_list_test");
            let result = repository.list().expect("result is error");
            assert_eq!(
                result,
                vec![
                    FileName::new("file1"),
                    FileName::new("file2"),
                    FileName::new("file3"),
                    FileName::new("file4")
                ]
            )
        })
    }

    #[test]
    #[cfg_attr(not(feature = "fs-test"), ignore)]
    pub fn test_fstemplate_repository_resolve_return_to_template_not_found() {
        let repository = FSTemplateRepository::new("./support/fs_template_repository_resolve_test");
        let err = repository.resolve("not_found".to_string()).is_err();
        assert!(err)
    }

    #[test]
    #[cfg_attr(not(feature = "fs-test"), ignore)]
    pub fn test_fstemplate_repository_resolve_return_to_template() {
        let repository = FSTemplateRepository::new("./support/fs_template_repository_resolve_test");
        let template = repository
            .resolve("foobar".to_string())
            .expect("template foobar is not found");
        assert_eq!(template, Template::new("testing"));
    }

    #[test]
    #[cfg_attr(not(feature = "fs-test"), ignore)]
    #[cfg(not(target_os = "windows"))]
    pub fn test_fstemplate_repository_resolve_return_to_template_when_selected_xdg_data_dir_templates(
    ) {
        with_xdg_data_path(|| {
            let repository =
                FSTemplateRepository::new("./support/fs_template_repository_resolve_test");
            let template = repository
                .resolve("file4".to_string())
                .expect("template foobar is not found");
            assert_eq!(template, Template::new("xdg data dir"));
        })
    }
}