blob: 5da0300288a36638ebac236f9380f5572a3153de [file] [log] [blame] [edit]
use std::convert::From;
use std::env;
use std::fs::{File, OpenOptions};
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
use clap::builder::PossibleValue;
use clap::{command, value_parser, Arg, ArgAction, ValueEnum};
extern crate minifier;
use minifier::{css, js, json};
pub struct Cli;
impl Cli {
pub fn init() {
let matches = command!()
.arg(
Arg::new("FileType")
.short('t')
.long("type")
.help(
"File Extention without dot. This option is optional.
If you don't provide this option, all input files
type will detect via extension of input file.
",
)
.required(false)
.value_parser(value_parser!(FileType)),
)
.arg(
Arg::new("output")
.short('o')
.long("out")
.help("Output file or directory (Default is parent dir of input files)")
.required(false)
.value_parser(value_parser!(PathBuf)),
)
.arg(
Arg::new("FILE")
.help("Input Files...")
.num_args(1..)
.value_parser(value_parser!(PathBuf))
.action(ArgAction::Append),
)
.get_matches();
let args: Vec<&PathBuf> = matches
.get_many::<PathBuf>("FILE")
.unwrap_or_default()
.collect::<Vec<_>>();
let ext: Option<&FileType> = matches.get_one::<FileType>("FileType");
let out: Option<&PathBuf> = matches.get_one::<PathBuf>("output");
for path in args.into_iter() {
write_out_file(path, out, ext);
}
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum FileType {
// Html,
Css,
Js,
Json,
Unknown,
}
impl FileType {
fn as_str(&self) -> &str {
match self {
Self::Css => "css",
Self::Js => "js",
Self::Json => "json",
Self::Unknown => "unknown",
}
}
}
impl ValueEnum for FileType {
fn value_variants<'a>() -> &'a [Self] {
&[FileType::Css, FileType::Js, FileType::Json]
}
fn to_possible_value(&self) -> Option<PossibleValue> {
Some(match *self {
FileType::Css => PossibleValue::new("css")
.help("All the files will be consider as CSS, regardless of their extension."),
FileType::Js => PossibleValue::new("js").help(
"All the files will be consider as JavaScript, regardless of their extension.",
),
FileType::Json => PossibleValue::new("json")
.help("All the files will be consider as JSON, regardless of their extension."),
FileType::Unknown => panic!("unknow file"),
})
}
}
impl std::str::FromStr for FileType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
for variant in Self::value_variants() {
if variant.to_possible_value().unwrap().matches(s, false) {
return Ok(*variant);
};
}
Err(format!("Invalid variant: {s}"))
}
}
impl From<&PathBuf> for FileType {
fn from(value: &PathBuf) -> Self {
let ext = value.extension();
if ext.is_none() {
return Self::Unknown;
};
match ext.unwrap().to_ascii_lowercase().to_str().unwrap() {
"css" => Self::Css,
"js" => Self::Js,
"json" => Self::Json,
_ => Self::Unknown,
}
}
}
pub fn get_all_data<T: AsRef<Path>>(file_path: T) -> io::Result<String> {
let mut file = File::open(file_path)?;
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
Ok(data)
}
fn write_out_file(file_path: &PathBuf, out_path: Option<&PathBuf>, ext: Option<&FileType>) {
let file_ext = if let Some(v) = ext {
v
} else {
&FileType::from(file_path)
};
if file_ext == &FileType::Unknown {
eprintln!("{:?}: unknow file extension...", file_path);
return;
};
match get_all_data(file_path) {
Ok(content) => {
let out = if out_path.is_some() {
let mut op = out_path.unwrap().clone();
if op.is_dir() {
op.push(file_path);
op.set_extension(format!("min.{}", file_ext.as_str()));
};
if op.parent().is_some() && !op.parent().unwrap().is_dir() {
std::fs::create_dir_all(op.parent().unwrap()).unwrap();
};
op
} else {
let mut p = file_path.clone();
p.set_extension(format!("min.{}", file_ext.as_str()));
p
};
if let Ok(mut file) = OpenOptions::new()
.truncate(true)
.write(true)
.create(true)
.open(&out)
{
let func = |s: &str| -> String {
match file_ext {
FileType::Css => {
css::minify(s).expect("css minification failed").to_string()
}
FileType::Js => js::minify(s).to_string(),
FileType::Json => json::minify(s).to_string(),
FileType::Unknown => panic!("{:?}: unknow file extension...", file_path),
}
};
if let Err(e) = write!(file, "{}", func(&content)) {
eprintln!("Impossible to write into {:?}: {}", out, e);
} else {
println!("{:?}: done -> generated into {:?}", file_path, out);
}
} else {
eprintln!("Impossible to create new file: {:?}", out);
}
}
Err(e) => eprintln!("{:?}: {}", file_path, e),
}
}