use anyhow::{anyhow, Result};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs::read_to_string;
use std::path::{Path, PathBuf};
use tinted_builder::{Scheme, SchemeSystem};
use wax::{Glob, Program};
#[derive(Debug, Clone)]
pub enum SchemeFile {
Yaml(PathBuf),
Yml(PathBuf),
}
impl SchemeFile {
pub fn new(path: impl AsRef<Path>) -> Result<Self> {
let extension = path
.as_ref()
.extension()
.unwrap_or_default()
.to_str()
.unwrap_or_default();
match extension {
"yaml" => Ok(Self::Yaml(path.as_ref().to_path_buf())),
"yml" => Ok(Self::Yml(path.as_ref().to_path_buf())),
_ => Err(anyhow!(
"E111: Invalid scheme file extension: {}",
path.as_ref().display()
)),
}
}
pub fn get_scheme(&self) -> Result<Scheme> {
match self {
Self::Yaml(path) | Self::Yml(path) => {
let scheme_str = read_to_string(path)?;
let scheme: serde_yaml::Value = serde_yaml::from_str(&scheme_str)?;
if let serde_yaml::Value::Mapping(map) = scheme {
match map.get("system") {
Some(serde_yaml::Value::String(system_str))
if system_str == &SchemeSystem::Base24.to_string() =>
{
let scheme_inner =
serde_yaml::from_value(serde_yaml::Value::Mapping(map))?;
let scheme = Scheme::Base24(scheme_inner);
Ok(scheme)
}
Some(_) => {
let scheme_inner =
serde_yaml::from_value(serde_yaml::Value::Mapping(map))?;
let scheme = Scheme::Base16(scheme_inner);
Ok(scheme)
}
None => {
if let Some(scheme_meta) = map.get("scheme") {
if let Some(system) = scheme_meta.get("system") {
if system == &SchemeSystem::Tinted8.to_string() {
let scheme_inner = serde_yaml::from_value(
serde_yaml::Value::Mapping(map),
)?;
let scheme = Scheme::Tinted8(scheme_inner);
Ok(scheme)
} else {
Err(anyhow!("E110: Unknown or unsupported scheme system"))
}
} else {
Err(anyhow!("E111: Missing required field `scheme.system`"))
}
} else {
Err(anyhow!("E111: Missing required field `system`"))
}
}
}
} else {
Err(anyhow!("E112: Unable to parse scheme file"))
}
}
}
}
#[must_use]
pub fn get_path(&self) -> PathBuf {
match self {
Self::Yaml(path) | Self::Yml(path) => path.clone(),
}
}
}
#[derive(Debug, Deserialize)]
pub struct TemplateConfig {
pub filename: Option<String>,
#[serde(rename = "supported-systems")]
pub supported_systems: Option<Vec<SchemeSystem>>,
pub supports: Option<HashMap<String, String>>,
pub options: Option<HashMap<String, String>>,
#[deprecated]
pub extension: Option<String>,
#[deprecated]
pub output: Option<String>,
}
#[derive(Debug)]
pub struct ParsedFilename {
pub directory: PathBuf,
pub filestem: String,
pub file_extension: Option<String>,
}
impl ParsedFilename {
#[must_use]
pub fn get_path(&self) -> PathBuf {
let directory = &self.directory;
let filestem = &self.filestem;
let file_extension = &self
.file_extension
.as_ref()
.map(|ext| format!(".{ext}"))
.unwrap_or_default();
directory.join(format!("{filestem}{file_extension}"))
}
}
pub fn get_scheme_files(
dirpath: impl AsRef<Path>,
ignores: &[String],
is_recursive: bool,
) -> Result<Vec<SchemeFile>> {
let glob_ignores: Vec<Glob> = ignores
.iter()
.map(|s| Glob::new(s))
.collect::<Result<_, _>>()?;
let mut scheme_paths: Vec<SchemeFile> = vec![];
for item in dirpath.as_ref().read_dir()? {
let file_path = item?.path();
if glob_ignores.iter().any(|g| g.is_match(file_path.as_path())) {
continue;
}
if file_path.is_dir() && is_recursive {
let inner_scheme_paths_result = get_scheme_files(&file_path, ignores, true);
if let Ok(inner_scheme_paths) = inner_scheme_paths_result {
scheme_paths.extend(inner_scheme_paths);
}
continue;
}
if file_path.is_file() {
let scheme_file_type_result = SchemeFile::new(&file_path);
match scheme_file_type_result {
Ok(scheme_file_type) => scheme_paths.push(scheme_file_type),
Err(err) => {
return Err(err);
}
}
}
}
scheme_paths.sort_by_key(SchemeFile::get_path);
Ok(scheme_paths)
}
pub fn parse_filename(template_path: impl AsRef<Path>, filepath: &str) -> ParsedFilename {
let p = Path::new(filepath);
let directory: PathBuf = p.parent().map_or_else(
|| template_path.as_ref().to_path_buf(),
|dir| template_path.as_ref().join(dir),
);
let filestem = p
.file_stem()
.and_then(|s| s.to_str())
.filter(|s| !s.is_empty())
.map(String::from)
.unwrap_or_default();
let file_extension = p.extension().and_then(|e| e.to_str()).map(String::from);
ParsedFilename {
directory,
filestem,
file_extension,
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::Path;
#[test]
fn test_parse_filename_with_directory_and_extension() {
let template_path = Path::new("/home/user/templates");
let result = parse_filename(template_path, "some-directory/name/file.txt");
assert_eq!(result.directory, template_path.join("some-directory/name"));
assert_eq!(result.filestem, "file");
assert_eq!(result.file_extension, Some("txt".to_string()));
}
#[test]
fn test_parse_filename_with_filename_and_extension() {
let template_path = Path::new("/home/user/templates");
let result = parse_filename(template_path, "filename.ext");
assert_eq!(result.directory, template_path);
assert_eq!(result.filestem, "filename");
assert_eq!(result.file_extension, Some("ext".to_string()));
}
#[test]
fn test_parse_filename_with_only_filename() {
let template_path = Path::new("/home/user/templates");
let result = parse_filename(template_path, "file");
assert_eq!(result.directory, template_path);
assert_eq!(result.filestem, "file");
assert_eq!(result.file_extension, None);
}
#[test]
fn test_parse_filename_with_directory_and_no_extension() {
let template_path = Path::new("/home/user/templates");
let result = parse_filename(template_path, "some-directory/file");
assert_eq!(result.directory, template_path.join("some-directory"));
assert_eq!(result.filestem, "file");
assert_eq!(result.file_extension, None);
}
}