From cf53c6a7a82e7759341b788e7073db1a9dc23080 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Tue, 23 May 2023 00:05:13 +0200 Subject: [PATCH] Refactor command line format enumerator (#1045) --- rust-code-analysis-cli/src/formats.rs | 281 ++++++++++++++++++-------- rust-code-analysis-cli/src/main.rs | 8 +- 2 files changed, 198 insertions(+), 91 deletions(-) diff --git a/rust-code-analysis-cli/src/formats.rs b/rust-code-analysis-cli/src/formats.rs index b658835..0275dfa 100644 --- a/rust-code-analysis-cli/src/formats.rs +++ b/rust-code-analysis-cli/src/formats.rs @@ -1,6 +1,5 @@ use std::fs::{create_dir_all, File}; use std::io::Write; -use std::io::{Error, ErrorKind}; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -21,97 +20,24 @@ impl Format { pub fn dump_formats( &self, - space: &T, - path: &Path, - output_path: &Option, + space: T, + path: PathBuf, + output_path: Option<&PathBuf>, pretty: bool, - ) -> std::io::Result<()> { - if output_path.is_none() { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - + ) { + if let Some(output_path) = output_path { match self { - Format::Cbor => Err(Error::new( - ErrorKind::Other, - "Cbor format cannot be printed to stdout", - )), - Format::Json => { - let json_data = if pretty { - serde_json::to_string_pretty(&space).unwrap() - } else { - serde_json::to_string(&space).unwrap() - }; - writeln!(stdout, "{json_data}") - } - Format::Toml => { - let toml_data = if pretty { - toml::to_string_pretty(&space).unwrap() - } else { - toml::to_string(&space).unwrap() - }; - writeln!(stdout, "{toml_data}") - } - Format::Yaml => writeln!(stdout, "{}", serde_yaml::to_string(&space).unwrap()), + Self::Cbor => Cbor::with_writer(space, path, output_path), + Self::Json => Json::with_pretty_writer(space, path, output_path, pretty), + Self::Toml => Toml::with_pretty_writer(space, path, output_path, pretty), + Self::Yaml => Yaml::with_writer(space, path, output_path), } } else { - let format_ext = match self { - Format::Cbor => ".cbor", - Format::Json => ".json", - Format::Toml => ".toml", - Format::Yaml => ".yml", - }; - - // Remove root / - let path = path.strip_prefix("/").unwrap_or(path); - - // Remove root ./ - let path = path.strip_prefix("./").unwrap_or(path); - - // Replace .. with . to keep files inside the output folder - let cleaned_path: Vec<&str> = path - .iter() - .map(|os_str| { - let s_str = os_str.to_str().unwrap(); - if s_str == ".." { - "." - } else { - s_str - } - }) - .collect(); - - // Create the filename - let filename = cleaned_path.join("/") + format_ext; - - // Build the file path - let format_path = output_path.as_ref().unwrap().join(filename); - - // Create directories - create_dir_all(format_path.parent().unwrap()).unwrap(); - - let mut format_file = File::create(format_path)?; match self { - Format::Cbor => serde_cbor::to_writer(format_file, &space) - .map_err(|e| Error::new(ErrorKind::Other, e.to_string())), - Format::Json => { - if pretty { - serde_json::to_writer_pretty(format_file, &space) - .map_err(|e| Error::new(ErrorKind::Other, e.to_string())) - } else { - serde_json::to_writer(format_file, &space) - .map_err(|e| Error::new(ErrorKind::Other, e.to_string())) - } - } - Format::Toml => { - let toml_data = if pretty { - toml::to_string_pretty(&space).unwrap() - } else { - toml::to_string(&space).unwrap() - }; - format_file.write_all(toml_data.as_bytes()) - } - Format::Yaml => serde_yaml::to_writer(format_file, &space) - .map_err(|e| Error::new(ErrorKind::Other, e.to_string())), + Self::Json => Json::write_on_stdout_pretty(space, pretty), + Self::Toml => Toml::write_on_stdout_pretty(space, pretty), + Self::Yaml => Yaml::write_on_stdout(space), + Self::Cbor => panic!("Cbor format cannot be printed to stdout"), } } } @@ -130,3 +56,184 @@ impl FromStr for Format { } } } + +#[inline(always)] +fn print_on_stdout(content: String) { + writeln!(std::io::stdout().lock(), "{content}").unwrap(); +} + +trait WriteOnStdout { + #[inline(always)] + fn write_on_stdout(content: T) { + print_on_stdout(Self::format(content)); + } + + fn format(content: T) -> String; +} + +trait WritePrettyOnStdout: WriteOnStdout { + fn write_on_stdout_pretty(content: T, pretty: bool) { + print_on_stdout(if pretty { + Self::format_pretty(content) + } else { + Self::format(content) + }); + } + fn format_pretty(content: T) -> String; +} + +fn handle_path(path: PathBuf, output_path: &Path, extension: &str) -> PathBuf { + // Remove root / + let path = path.as_path().strip_prefix("/").unwrap_or(path.as_path()); + + // Remove root ./ + let path = path.strip_prefix("./").unwrap_or(path); + + // Replace .. with . to keep files inside the output folder + let cleaned_path: Vec<&str> = path + .iter() + .map(|os_str| { + let s_str = os_str.to_str().unwrap(); + if s_str == ".." { + "." + } else { + s_str + } + }) + .collect(); + + // Create the filename + let filename = cleaned_path.join("/") + extension; + + // Build the file path + output_path.join(filename) +} + +trait WriteFile { + const EXTENSION: &'static str; + + fn open_file(path: PathBuf, output_path: &Path) -> File { + // Handle output path + let format_path = handle_path(path, output_path, Self::EXTENSION); + + // Create directories + create_dir_all(format_path.parent().unwrap()).unwrap(); + + File::create(format_path).unwrap() + } + + fn with_writer(content: T, path: PathBuf, output_path: &Path); +} + +trait WritePrettyFile: WriteFile { + fn with_pretty_writer( + content: T, + path: PathBuf, + output_path: &Path, + pretty: bool, + ); +} + +struct Json; + +impl WriteOnStdout for Json { + fn format(content: T) -> String { + serde_json::to_string(&content).unwrap() + } +} + +impl WritePrettyOnStdout for Json { + fn format_pretty(content: T) -> String { + serde_json::to_string_pretty(&content).unwrap() + } +} + +impl WriteFile for Json { + const EXTENSION: &'static str = ".json"; + + fn with_writer(content: T, path: PathBuf, output_path: &Path) { + serde_json::to_writer(Self::open_file(path, output_path), &content).unwrap() + } +} + +impl WritePrettyFile for Json { + fn with_pretty_writer( + content: T, + path: PathBuf, + output_path: &Path, + pretty: bool, + ) { + if pretty { + serde_json::to_writer_pretty(Self::open_file(path, output_path), &content).unwrap(); + } else { + Self::with_writer(content, path, output_path); + } + } +} + +struct Toml; + +impl WriteOnStdout for Toml { + fn format(content: T) -> String { + toml::to_string(&content).unwrap() + } +} + +impl WritePrettyOnStdout for Toml { + fn format_pretty(content: T) -> String { + toml::to_string_pretty(&content).unwrap() + } +} + +impl WriteFile for Toml { + const EXTENSION: &'static str = ".toml"; + + fn with_writer(content: T, path: PathBuf, output_path: &Path) { + Self::open_file(path, output_path) + .write_all(Self::format(content).as_bytes()) + .unwrap(); + } +} + +impl WritePrettyFile for Toml { + fn with_pretty_writer( + content: T, + path: PathBuf, + output_path: &Path, + pretty: bool, + ) { + if pretty { + Self::open_file(path, output_path) + .write_all(Self::format_pretty(&content).as_bytes()) + .unwrap(); + } else { + Self::with_writer(content, path, output_path); + } + } +} + +struct Yaml; + +impl WriteOnStdout for Yaml { + fn format(content: T) -> String { + serde_yaml::to_string(&content).unwrap() + } +} + +impl WriteFile for Yaml { + const EXTENSION: &'static str = ".yml"; + + fn with_writer(content: T, path: PathBuf, output_path: &Path) { + serde_yaml::to_writer(Self::open_file(path, output_path), &content).unwrap() + } +} + +struct Cbor; + +impl WriteFile for Cbor { + const EXTENSION: &'static str = ".cbor"; + + fn with_writer(content: T, path: PathBuf, output_path: &Path) { + serde_cbor::to_writer(Self::open_file(path, output_path), &content).unwrap() + } +} diff --git a/rust-code-analysis-cli/src/main.rs b/rust-code-analysis-cli/src/main.rs index fb38bc0..21ad14a 100644 --- a/rust-code-analysis-cli/src/main.rs +++ b/rust-code-analysis-cli/src/main.rs @@ -92,10 +92,9 @@ fn act_on_file(path: PathBuf, cfg: &Config) -> std::io::Result<()> { } else if cfg.metrics { if let Some(output_format) = &cfg.output_format { if let Some(space) = get_function_spaces(&language, source, &path, pr) { - output_format.dump_formats(&space, &path, &cfg.output, cfg.pretty) - } else { - Ok(()) + output_format.dump_formats(space, path, cfg.output.as_ref(), cfg.pretty); } + Ok(()) } else { let cfg = MetricsCfg { path }; let path = cfg.path.clone(); @@ -104,7 +103,8 @@ fn act_on_file(path: PathBuf, cfg: &Config) -> std::io::Result<()> { } else if cfg.ops { if let Some(output_format) = &cfg.output_format { let ops = get_ops(&language, source, &path, pr).unwrap(); - output_format.dump_formats(&ops, &path, &cfg.output, cfg.pretty) + output_format.dump_formats(ops, path, cfg.output.as_ref(), cfg.pretty); + Ok(()) } else { let cfg = OpsCfg { path }; let path = cfg.path.clone();