Skip to content

Commit

Permalink
more options added to cli #97
Browse files Browse the repository at this point in the history
  • Loading branch information
BiswajitThakur committed Aug 3, 2024
1 parent 20bcab8 commit ca31ca4
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 90 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ edition = "2021"
html = ["regex"]

[dependencies]
clap = { version = "4.5.13", features = ["cargo", "derive"] }
glob = "0.3.1"
regex = { version = "1.5.5", optional = true }

[lib]
Expand Down
191 changes: 191 additions & 0 deletions src/cli.rs
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),
}
}
2 changes: 1 addition & 1 deletion src/js/tools.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,7 @@ fn aggregate_strings_inner<'a, 'b: 'a>(
}
let x = strs.entry(token).or_insert_with(|| Vec::with_capacity(1));
x.push(pos);
if x.len() > 1 && validated.get(token).is_none() {
if x.len() > 1 && !validated.contains_key(token) {
let len = str_token.len();
// Computation here is simple, we declare new variables when creating this so
// the total of characters must be shorter than:
Expand Down
91 changes: 2 additions & 89 deletions src/main.rs
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();
}

0 comments on commit ca31ca4

Please sign in to comment.