use super::error::config_error::*;
use super::hough_transform::circle::{CircleLimits, CircleOutputFormat};
use std::string::ToString;
use std::{ffi, fs, io, path, process, str};
use structopt::{clap, StructOpt};
#[derive(Debug, StructOpt)]
#[structopt(name = "hougse", about, author, rename_all = "kebab-case")]
pub struct Config {
#[structopt(name = "INPUT", short = "i", long = "input", parse(from_os_str))]
pub image_path: Option<path::PathBuf>,
#[structopt(name = "OUTPUT", short = "o", long = "output", parse(from_os_str))]
pub output_path: Option<path::PathBuf>,
#[structopt(skip = false)]
pub output_to_text: bool,
#[structopt(long = "overwrite")]
overwrite: bool,
#[structopt(name = "FORMAT", short = "f", long = "format")]
pub output_format: Option<CircleOutputFormat>,
#[structopt(name = "LIMIT", short = "l", long = "limits")]
pub circle_limits: Option<CircleLimits>,
#[structopt(name = "COMPLETION", long = "generate-completions")]
pub completions: Option<Option<String>>,
#[structopt(name = "COMPLETION_PATH", long = "completion-path", parse(from_os_str))]
pub completion_path: Option<path::PathBuf>,
}
impl Default for Config {
fn default() -> Self {
Self {
image_path: None,
output_path: None,
output_to_text: false,
overwrite: false,
output_format: Some(CircleOutputFormat::default()),
circle_limits: Some(CircleLimits::default()),
completions: None,
completion_path: None,
}
}
}
impl Config {
pub const SUPPORTED_READ_IMAGE_TYPES: [&'static str; 6] =
["tiff", "tif", "png", "jpg", "webp", "gif"];
pub const SUPPORTED_WRITE_IMAGE_TYPES: [&'static str; 6] =
["tiff", "tif", "png", "jpg", "webp", "gif"];
pub const SUPPORTED_COMPLETION_SHELLS: [(&'static str, clap::Shell); 5] = [
("bash", clap::Shell::Bash),
("fish", clap::Shell::Fish),
("zsh", clap::Shell::Zsh),
("powershell", clap::Shell::PowerShell),
("elvish", clap::Shell::Elvish),
];
pub fn new() -> Self {
Self::default()
}
pub fn parse() -> Result<Self, ConfigError> {
let mut conf_from_cli = Config::from_args();
if let Some(_) = &conf_from_cli.completions {
if let Some(completion_path) = &conf_from_cli.completion_path {
conf_from_cli.generate_completions(Some(&mut completion_path.clone()))?
} else {
conf_from_cli.generate_completions(None)?
}
process::exit(0)
}
conf_from_cli.check_image_path()?;
conf_from_cli.check_output_file()?;
if let None = conf_from_cli.circle_limits {
conf_from_cli.circle_limits = Some(CircleLimits::default());
}
Ok(conf_from_cli)
}
fn check_image_path(&self) -> Result<(), InputImageError> {
if let Some(file) = &self.image_path {
if !(*file).exists() {
return Err(InputImageError::ImageFileNotFound(file.to_path_buf()));
}
if let Some(ext) = (*file).extension() {
if let Some(ext) = ext.to_str() {
if !Self::SUPPORTED_READ_IMAGE_TYPES
.iter()
.any(|ext_read| *ext_read == ext)
{
return Err(InputImageError::UnsupportedReadExtention(
file.to_path_buf(),
ffi::OsString::from(ext),
));
}
} else {
return Err(
InputImageError::InvalidExtension(
file.to_path_buf(),
ffi::OsString::from(ext),
),
);
}
} else {
return Err(InputImageError::NoExtention(file.to_path_buf()));
};
} else {
return Err(InputImageError::NoFileGiven);
}
Ok(())
}
fn check_output_file(&mut self) -> Result<(), OutputFileError> {
self.generate_output();
if let Some(ext) = (&self.output_path).as_ref().unwrap().extension() {
if !Self::SUPPORTED_WRITE_IMAGE_TYPES
.iter()
.any(|ext_write| *ext_write == ext)
&& !self.output_to_text
{
return Err(OutputFileError::UnsupportedWriteExtention(
(&self.output_path).as_ref().unwrap().to_path_buf(),
ffi::OsString::from(ext),
));
}
}
if true == self.output_path.as_ref().unwrap().exists() && self.overwrite == false {
return Err(
OutputFileError::FileAlreadyExists(
(&self.output_path).as_ref().unwrap().to_path_buf(),
),
);
}
Ok(())
}
fn determine_output_to_text(&self) -> Result<bool, OutputFileError> {
if let Some(output) = &self.output_path {
match output.extension() {
None => Ok(true),
Some(ext) if ext == "txt" => Ok(true),
_ => Ok(false),
}
} else {
Err(OutputFileError::NoFileGiven)
}
}
fn generate_output(&mut self) {
match self.determine_output_to_text() {
Ok(to_text) => self.output_to_text = to_text,
Err(e) if e == OutputFileError::NoFileGiven =>
{
let mut output_path = self.image_path.clone().unwrap();
output_path.set_extension("tiff");
self.output_path = Some(output_path);
}
Err(_) => {}
}
}
fn generate_completions(
&self,
completion_path_opt: Option<&mut path::PathBuf>,
) -> Result<(), CompletionGenerationError> {
let mut app = Config::clap();
if let Some(shell_asked) = &self.completions.as_ref().unwrap() {
let shell;
if let Some((shell_string, clap_shell)) = Config::SUPPORTED_COMPLETION_SHELLS
.iter()
.find(|(shell_string, clap_shell)| shell_string == shell_asked)
{
shell = *clap_shell;
if let Some(completion_path) = completion_path_opt {
if completion_path.is_dir() {
app.gen_completions("houghse", shell, completion_path.to_str().unwrap());
} else if completion_path.is_file() {
app.gen_completions_to(
"houghse",
shell,
&mut fs::File::create(completion_path.to_str().unwrap())?,
)
} else {
return Err(CompletionGenerationError::CompletionPathNotFound(
completion_path.to_path_buf(),
));
}
} else {
app.gen_completions_to("houghse", shell, &mut io::stdout());
}
} else {
return Err(CompletionGenerationError::InvalidShell(
shell_asked.to_string(),
));
}
} else {
todo!()
}
Ok(())
}
}
impl std::fmt::Display for Config {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", {
if let Some(completion) = &self.completions {
if let Some(completion_path) = &self.completion_path {
format!(
"{} -> {}",
completion.as_ref().unwrap(),
completion_path.display()
)
} else {
format!("{}", completion.as_ref().unwrap())
}
} else {
format!(
"{}{}{}{}",
{
if let Some(image) = &self.image_path {
format!("Image path: {}\n", image.display())
} else {
"".to_string()
}
},
{
if let Some(output) = &self.output_path {
format!("Output path: {}{}\n", output.display(), {
if [self.overwrite, self.output_to_text].contains(&true) {
format!(
"{}{}",
{
if self.overwrite {
" (overwrite"
} else {
" ("
}
},
{
if self.output_to_text && self.overwrite {
", text)"
} else {
"text)"
}
},
)
} else {
"".to_string()
}
})
} else {
"".to_string()
}
},
{
if self.output_to_text {
"".to_string()
} else {
if let Some(format) = &self.output_format {
format!(" Output format:\n{}\n", format)
} else {
format!(" Output format:\n{}", CircleOutputFormat::default())
}
}
},
{
if let Some(limits) = &self.circle_limits {
if limits != &CircleLimits::default() {
format!(" Circle limits:\n{}\n", limits)
} else {
"no limits!".to_string()
}
} else {
"no limits!".to_string()
}
}
)
}
})
}
}
impl str::FromStr for CircleOutputFormat {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
todo!()
}
}
impl str::FromStr for CircleLimits {
type Err = ConfigError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
todo!()
}
}
#[cfg(test)]
mod test_config {
use super::super::error::config_error::{InputImageError, OutputFileError};
use super::*;
use std::path::PathBuf;
use std::str::FromStr;
#[test]
fn input_file_does_not_exist() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("does_not_exist").unwrap());
assert_eq!(
test.check_image_path().unwrap_err(),
InputImageError::ImageFileNotFound(test.image_path.unwrap())
)
}
#[test]
fn invalid_read_extension() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("./tests/image.txt").unwrap());
assert_eq!(
test.check_image_path().unwrap_err(),
InputImageError::UnsupportedReadExtention(
test.image_path.unwrap(),
ffi::OsString::from("txt")
)
)
}
#[test]
fn output_file_generation() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("./tests/generate.tif").unwrap());
test.generate_output();
assert_ne!(test.output_path, None);
assert_eq!(
Some(path::PathBuf::from("./tests/generate.tiff")),
test.output_path
);
}
#[test]
fn test_output_to_text_no_extension() {
let mut test = Config::default();
test.output_path = Some(PathBuf::from_str("./tests/image.tif").unwrap());
test.output_path = Some(PathBuf::from_str("./tests/text").unwrap());
test.generate_output();
assert_eq!(test.output_to_text, true);
}
#[test]
fn test_output_to_text_with_extension() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("./tests/image.tif").unwrap());
test.output_path = Some(PathBuf::from_str("./tests/text.txt").unwrap());
test.generate_output();
assert_eq!(test.output_to_text, true);
}
#[test]
fn unsupported_output_format() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("./tests/image.tif").unwrap());
test.output_path = Some(PathBuf::from_str("./tests/output.unsupported_ext").unwrap());
test.generate_output();
assert_eq!(
test.check_output_file(),
Err(OutputFileError::UnsupportedWriteExtention(
test.output_path.unwrap(),
ffi::OsString::from("unsupported_ext"),
))
)
}
#[test]
fn output_file_exists_and_not_set_to_overwrite() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("./tests/image.tif").unwrap());
test.output_path = Some(PathBuf::from_str("./tests/exists.tif").unwrap());
test.generate_output();
assert_eq!(
test.check_output_file(),
Err(OutputFileError::FileAlreadyExists(
test.output_path.unwrap()
))
)
}
#[test]
fn output_file_exists_and_set_to_overwrite() {
let mut test = Config::default();
test.image_path = Some(PathBuf::from_str("./tests/image.tif").unwrap());
test.output_path = Some(PathBuf::from_str("./tests/exists.tif").unwrap());
test.overwrite = true;
test.generate_output();
assert_eq!(test.check_output_file(), Ok(()))
}
#[test]
fn shell_completion_gen_file_created() {
let mut test = Config::default();
test.completions = Some(Some(String::from("zsh")));
}
}