-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
20bcab8
commit ca31ca4
Showing
4 changed files
with
196 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
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::{arg, command, value_parser, Arg, ArgAction, ValueEnum}; | ||
use glob::glob; | ||
|
||
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!(<FILE>) | ||
.help("Input Files...") | ||
.num_args(1..) | ||
.value_parser(value_parser!(String)) | ||
.action(ArgAction::Append), | ||
) | ||
.get_matches(); | ||
let args: Vec<&str> = matches | ||
.get_many::<String>("FILE") | ||
.unwrap_or_default() | ||
.map(|v| v.as_str()) | ||
.collect::<Vec<_>>(); | ||
let ext: Option<&FileType> = matches.get_one::<FileType>("FileType"); | ||
let out: Option<&PathBuf> = matches.get_one::<PathBuf>("output"); | ||
for i in args { | ||
for entry in glob(i).expect("Failed to read glob pattern") { | ||
match entry { | ||
Ok(path) => write_out_file(&path, out, ext), | ||
Err(e) => println!("{:?}", e), | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
#[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), | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,94 +1,7 @@ | ||
// Take a look at the license at the top of the repository in the LICENSE file. | ||
|
||
extern crate minifier; | ||
|
||
use std::env; | ||
use std::ffi::OsStr; | ||
use std::fs::{File, OpenOptions}; | ||
use std::io::{self, Read, Write}; | ||
use std::path::{Path, PathBuf}; | ||
|
||
use minifier::{css, js, json}; | ||
|
||
fn print_help() { | ||
println!( | ||
r##"For now, this minifier supports the following type of files: | ||
* .css | ||
* .js | ||
* .json"## | ||
); | ||
} | ||
|
||
pub fn get_all_data(file_path: &str) -> 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 call_minifier<F>(file_path: &str, func: F) | ||
where | ||
F: Fn(&str) -> String, | ||
{ | ||
match get_all_data(file_path) { | ||
Ok(content) => { | ||
let mut out = PathBuf::from(file_path); | ||
let original_extension = out | ||
.extension() | ||
.unwrap_or_else(|| OsStr::new("")) | ||
.to_str() | ||
.unwrap_or("") | ||
.to_owned(); | ||
out.set_extension(format!("min.{}", original_extension)); | ||
if let Ok(mut file) = OpenOptions::new() | ||
.truncate(true) | ||
.write(true) | ||
.create(true) | ||
.open(out.clone()) | ||
{ | ||
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), | ||
} | ||
} | ||
mod cli; | ||
|
||
fn main() { | ||
let args: Vec<_> = env::args().skip(1).collect(); | ||
|
||
if args.is_empty() { | ||
println!("Missing files to work on...\nExample: ./minifier file.js\n"); | ||
print_help(); | ||
return; | ||
} | ||
for arg in &args { | ||
let p = Path::new(arg); | ||
|
||
if !p.is_file() { | ||
eprintln!("\"{}\" isn't a file", arg); | ||
continue; | ||
} | ||
match p | ||
.extension() | ||
.unwrap_or_else(|| OsStr::new("")) | ||
.to_str() | ||
.unwrap_or("") | ||
{ | ||
"css" => call_minifier(arg, |s| { | ||
css::minify(s).expect("css minification failed").to_string() | ||
}), | ||
"js" => call_minifier(arg, |s| js::minify(s).to_string()), | ||
"json" => call_minifier(arg, |s| json::minify(s).to_string()), | ||
// "html" | "htm" => call_minifier(arg, html::minify), | ||
x => println!("\"{}\": this format isn't supported", x), | ||
} | ||
} | ||
cli::Cli::init(); | ||
} |