-
Notifications
You must be signed in to change notification settings - Fork 2
I went a little bit ahead of myself... #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ba0a2f4
71547f1
8da6ad5
df7a7bc
f8a6705
5743bb9
4bceb28
8133f50
d145d96
0f383eb
f2b46d3
ca343b7
548d337
b4897d8
d635741
2006fdd
082ecbd
0aec0e0
eb3964f
a305594
b36cecb
1e522d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,13 +2,23 @@ | |
extern crate serde_derive; | ||
extern crate regex; | ||
extern crate structopt; | ||
#[macro_use] | ||
extern crate failure; | ||
#[macro_use] | ||
extern crate lazy_static; | ||
|
||
lazy_static! { | ||
static ref REGEX_PATTERN: Regex = Regex::new(r"/(.*)/").unwrap(); | ||
} | ||
|
||
use exitfailure::ExitFailure; | ||
use failure::{Context, ResultExt}; | ||
use failure::{Context, Error, ResultExt}; | ||
use glob::Pattern; | ||
use regex::Regex; | ||
use std::fs; | ||
use std::path::{Path, PathBuf}; | ||
use std::convert::{TryFrom, TryInto}; | ||
use std::{ | ||
fs, | ||
path::{Path, PathBuf}, | ||
}; | ||
use structopt::StructOpt; | ||
|
||
#[derive(StructOpt, Debug)] | ||
|
@@ -38,20 +48,38 @@ struct SubConfigByPattern { | |
|
||
pub struct Ficon { | ||
option: CliOption, | ||
config: Config, | ||
validated_config: ValidatedConfig, | ||
} | ||
|
||
struct ValidatedSubConfig { | ||
pattern: Pattern, | ||
convention: String, | ||
convention_regex: Option<Regex>, | ||
} | ||
|
||
struct ValidatedConfig { | ||
default_convention: Regex, | ||
patterns: Vec<ValidatedSubConfig>, | ||
} | ||
|
||
pub fn filename_of(path: &Path) -> &str { | ||
path.file_stem() | ||
.expect("file stem is missing") | ||
.to_str() | ||
.expect("can't cast file stem to string") | ||
} | ||
|
||
impl Ficon { | ||
const DEFAULT_CONFIG_FILE: &'static str = "Ficon.toml"; | ||
|
||
pub fn new() -> Result<Ficon, ExitFailure> { | ||
pub fn new() -> Result<Ficon, Error> { | ||
let option: CliOption = CliOption::from_args(); | ||
|
||
let config_path = if option.path.is_dir() { | ||
Ok(format!( | ||
"{}/{}", | ||
option.path.display(), | ||
Ficon::DEFAULT_CONFIG_FILE | ||
Self::DEFAULT_CONFIG_FILE | ||
)) | ||
} else { | ||
Err(Context::new(format!( | ||
|
@@ -61,82 +89,127 @@ impl Ficon { | |
}?; | ||
|
||
let config = fs::read_to_string(&config_path) | ||
.with_context(|_| format!("Config file is missing: {}", &config_path))?; | ||
.with_context(|_| format!("Config file is missing: '{}'", &config_path))?; | ||
|
||
let config: Config = toml::from_str(&config).with_context(|_| { | ||
format!( | ||
"Error while parsing configuration file at '{}'", | ||
config_path | ||
) | ||
})?; | ||
|
||
Ok(Ficon { | ||
option, | ||
validated_config: <_ as TryInto<ValidatedConfig>>::try_into(config).with_context( | ||
|_| format!("Validation of configuration at '{}' failed", config_path), | ||
)?, | ||
}) | ||
} | ||
|
||
pub fn target_dir(&self) -> &Path { | ||
&self.option.path | ||
} | ||
|
||
let config: Config = toml::from_str(&config) | ||
.with_context(|_| "Error while parsing configuration file")?; | ||
pub fn check(&mut self, path: &Path) -> Result<bool, Error> { | ||
let convention_regex = self.validated_config.convention_for(path)?; | ||
let file_name = filename_of(path) | ||
// ignore multiple extension by default | ||
// TODO: make this configurable | ||
.split('.') | ||
.next() | ||
.unwrap_or(""); | ||
|
||
Ok(Ficon { option, config }) | ||
Ok(convention_regex.is_match(file_name)) | ||
} | ||
} | ||
|
||
pub fn target_dir(&self) -> &Path { | ||
return self.option.path.as_ref(); | ||
impl TryFrom<Config> for ValidatedConfig { | ||
type Error = Error; | ||
|
||
fn try_from(value: Config) -> Result<ValidatedConfig, Error> { | ||
Ok(ValidatedConfig { | ||
default_convention: Self::new_regex_for_convention(&value.default.convention)?, | ||
patterns: match value.for_patterns { | ||
Some(mut pattern_configs) => pattern_configs | ||
.drain(..) | ||
.map(|conf| { | ||
Pattern::new(&conf.pattern) | ||
.with_context(|_| format!("Failed to parse pattern '{}'", conf.pattern)) | ||
.map(|pattern| ValidatedSubConfig { | ||
convention: conf.convention, | ||
convention_regex: None, | ||
pattern, | ||
}) | ||
}) | ||
.collect::<Result<_, _>>()?, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the best tricks in the book. Collect into a |
||
None => Vec::default(), | ||
}, | ||
}) | ||
} | ||
} | ||
|
||
pub fn check(&self, path: &Path) -> Result<bool, ExitFailure> { | ||
let convention_str = self.config.convention_for(path); | ||
let reg_pattern = Regex::new(r"/(.*)/").unwrap(); | ||
|
||
let convention_regex = match convention_str.as_str() { | ||
"any" => Ficon::convention_from_regex(r".*"), | ||
"kebab" => Ficon::convention_from_regex(r"^[a-z][a-z\-\d]*[a-z\d]$"), | ||
"snake" => Ficon::convention_from_regex(r"^[a-z][a-z_\d]*[a-z\d]$"), | ||
"upper_snake" => Ficon::convention_from_regex(r"^[A-Z][A-Z_\d]*$"), | ||
"camel" => Ficon::convention_from_regex(r"^[a-z][A-Za-z\d]*$"), | ||
"pascal" => Ficon::convention_from_regex(r"^[A-Z][A-Za-z\d]*$"), | ||
impl ValidatedConfig { | ||
fn new_regex_for_convention(convention: &str) -> Result<Regex, Error> { | ||
fn new_regex(pattern: &str) -> Result<Regex, Error> { | ||
Regex::new(pattern) | ||
.with_context(|_| format!("Invalid convention definition: {}", pattern)) | ||
.map_err(Into::into) | ||
} | ||
|
||
match convention { | ||
"any" => new_regex(r".*"), | ||
"kebab" => new_regex(r"^[a-z][a-z\-\d]*[a-z\d]$"), | ||
"snake" => new_regex(r"^[a-z][a-z_\d]*[a-z\d]$"), | ||
"upper_snake" => new_regex(r"^[A-Z][A-Z_\d]*$"), | ||
"camel" => new_regex(r"^[a-z][A-Za-z\d]*$"), | ||
"pascal" => new_regex(r"^[A-Z][A-Za-z\d]*$"), | ||
convention => { | ||
if reg_pattern.is_match(convention_str.as_str()) { | ||
let convention = reg_pattern.replace(convention, "$1").to_string(); | ||
Regex::new(convention.as_str()) | ||
if REGEX_PATTERN.is_match(convention) { | ||
let convention = REGEX_PATTERN.replace(convention, "$1"); | ||
Regex::new(&convention) | ||
.with_context(|_| format!("{} is not a valid regexp", convention)) | ||
.map_err(Into::into) | ||
} else { | ||
Err(Context::new(format!( | ||
bail!( | ||
"convention is not predefined or defined as regexp: {}", | ||
convention | ||
))) | ||
) | ||
} | ||
} | ||
}; | ||
|
||
let file_name = path | ||
.file_stem() | ||
.expect("file stem is missing") | ||
.to_str() | ||
.expect("can't cast file stem to string"); | ||
|
||
// ignore multiple extension by default | ||
// TODO: make this configurable | ||
let file_name = file_name.split(".").next().unwrap_or(""); | ||
|
||
let convention = convention_regex.with_context(|_| "fail to parse convention")?; | ||
|
||
Ok(convention.is_match(file_name)) | ||
} | ||
} | ||
|
||
fn convention_from_regex(pattern: &str) -> Result<Regex, Context<String>> { | ||
Regex::new(pattern).with_context(|_| format!("Invalid convention definition: {}", pattern)) | ||
fn convention_for(&mut self, path: &Path) -> Result<&Regex, Error> { | ||
match self | ||
.patterns | ||
.iter_mut() | ||
.find(|p| p.pattern.matches_path(path)) | ||
{ | ||
Some(pattern) => { | ||
let convention = &pattern.convention; | ||
Ok(get_or_insert_with_error( | ||
&mut pattern.convention_regex, | ||
|| Self::new_regex_for_convention(convention), | ||
)?) | ||
} | ||
None => Ok(&self.default_convention), | ||
} | ||
} | ||
} | ||
|
||
impl Config { | ||
fn convention_for(&self, path: &Path) -> String { | ||
let pattern_configs = &self.for_patterns; | ||
|
||
let empty_vec = vec![]; | ||
let pattern_configs = pattern_configs.as_ref().map_or(&empty_vec, |e| e); | ||
|
||
let matched_formats: Vec<&SubConfigByPattern> = pattern_configs | ||
.iter() | ||
.filter(|conf| { | ||
let pattern = Pattern::new(conf.pattern.as_str()).expect("invalid glob pattern"); | ||
|
||
pattern.matches_path(path) | ||
}) | ||
.collect(); | ||
|
||
return matched_formats | ||
.first() | ||
.map(|e| e.convention.clone()) | ||
.unwrap_or(self.default.convention.clone()); | ||
} | ||
/// Like Option::get_or_insert_with(), but supports errors | ||
fn get_or_insert_with_error<T, E>( | ||
input: &mut Option<T>, | ||
f: impl FnOnce() -> Result<T, E>, | ||
) -> Result<&T, E> { | ||
Ok(match input { | ||
Some(ref v) => v, | ||
None => { | ||
*input = Some(f()?); | ||
match input { | ||
Some(ref v) => v, | ||
None => unreachable!(), | ||
} | ||
} | ||
}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe you can use OnceCell instead of lazy_static I guess