diff --git a/Cargo.lock b/Cargo.lock index 1f123de0a..6ff526222 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1523,11 +1523,7 @@ dependencies = [ "pretty_assertions", "regex", "serde", - "serde_cbor", - "serde_json", - "serde_yaml", "termcolor", - "toml", "tree-sitter", ] @@ -1544,9 +1540,13 @@ dependencies = [ "globset", "num_cpus", "pretty_assertions", + "regex", "rust-code-analysis", "serde", + "serde_cbor", "serde_json", + "serde_yaml", + "toml", "walkdir", ] diff --git a/Cargo.toml b/Cargo.toml index c3f57a4d8..4edd8429a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,12 +25,8 @@ petgraph = "^0.5" phf = { version = "^0.8", features = ["macros"] } regex = "^1.1" serde = { version = "^1.0", features = ["derive"] } -serde_cbor = "^0.11" -serde_json = "^1.0" termcolor = "^1.0" tree-sitter = "^0.16" -serde_yaml = "^0.8" -toml = "^0.5" [dev-dependencies] pretty_assertions = "^0.6" diff --git a/rust-code-analysis-cli/Cargo.toml b/rust-code-analysis-cli/Cargo.toml index b3c0d7baf..40892bcb4 100644 --- a/rust-code-analysis-cli/Cargo.toml +++ b/rust-code-analysis-cli/Cargo.toml @@ -16,9 +16,13 @@ crossbeam = "^0.7" futures = "^0.3" globset = "^0.4" num_cpus = "^1.13" +regex = "^1.1" rust-code-analysis = { path = "..", version = "0.0"} serde = "^1.0" +serde_cbor = "^0.11" serde_json = "^1.0" +serde_yaml = "^0.8" +toml = "^0.5" walkdir = "^2.2" [dev-dependencies] diff --git a/rust-code-analysis-cli/src/formats.rs b/rust-code-analysis-cli/src/formats.rs new file mode 100644 index 000000000..6a8bcf7f3 --- /dev/null +++ b/rust-code-analysis-cli/src/formats.rs @@ -0,0 +1,122 @@ +use regex::Regex; +use std::fs::File; +use std::io::Write; +use std::io::{Error, ErrorKind}; +use std::path::PathBuf; +use std::str::FromStr; + +use rust_code_analysis::FuncSpace; + +#[derive(Debug, Clone)] +pub enum Format { + Cbor, + Json, + Toml, + Yaml, +} + +impl Format { + pub fn all() -> &'static [&'static str] { + &["cbor", "json", "toml", "yaml"] + } + + pub fn dump_formats( + &self, + space: &FuncSpace, + path: &PathBuf, + output_path: &Option, + pretty: bool, + ) -> std::io::Result<()> { + if output_path.is_none() { + let stdout = std::io::stdout(); + let mut stdout = stdout.lock(); + + 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() + }; + write!(stdout, "{}", json_data) + } + Format::Toml => { + let toml_data = if pretty { + toml::to_string_pretty(&space).unwrap() + } else { + toml::to_string(&space).unwrap() + }; + write!(stdout, "{}", toml_data) + } + Format::Yaml => write!(stdout, "{}", serde_yaml::to_string(&space).unwrap()), + } + } else { + let format_ext = match self { + Format::Cbor => ".cbor", + Format::Json => ".json", + Format::Toml => ".toml", + Format::Yaml => ".yml", + }; + + let output_path = output_path.as_ref().unwrap(); + + let mut file = path.as_path().file_name().unwrap().to_os_string(); + file.push(format_ext); + + let mut format_path = output_path.clone(); + format_path.push(file); + + if format_path.as_path().exists() { + let mut new_filename = path.to_str().unwrap().to_string(); + let re = Regex::new(r"[\\:/]").unwrap(); + new_filename = re.replace_all(&new_filename, "_").to_string(); + new_filename.push_str(format_ext); + format_path.pop(); + format_path.push(new_filename); + } + + 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())), + } + } + } +} + +impl FromStr for Format { + type Err = String; + + fn from_str(format: &str) -> Result { + match format { + "cbor" => Ok(Format::Cbor), + "json" => Ok(Format::Json), + "toml" => Ok(Format::Toml), + "yaml" => Ok(Format::Yaml), + format => Err(format!("{:?} is not a supported format", format)), + } + } +} diff --git a/rust-code-analysis-cli/src/main.rs b/rust-code-analysis-cli/src/main.rs index c7b93c762..fb1c2d5a0 100644 --- a/rust-code-analysis-cli/src/main.rs +++ b/rust-code-analysis-cli/src/main.rs @@ -4,9 +4,13 @@ extern crate crossbeam; extern crate num_cpus; #[macro_use] extern crate serde; +extern crate serde_cbor; #[cfg_attr(test, macro_use)] extern crate serde_json; +extern crate serde_yaml; +extern crate toml; +mod formats; mod web; use clap::{App, Arg}; @@ -21,6 +25,7 @@ use std::sync::{Arc, Mutex}; use std::{process, thread}; use walkdir::{DirEntry, WalkDir}; +use formats::Format; use rust_code_analysis::*; use web::server; @@ -34,7 +39,7 @@ struct Config { function: bool, metrics: bool, output_format: Option, - output: String, + output: Option, pretty: bool, line_start: Option, line_end: Option, @@ -91,17 +96,13 @@ fn act_on_file(language: Option, path: PathBuf, cfg: &Config) -> std::io:: }; action::(&language, source, &path, pr, cfg) } else if cfg.metrics { - let cfg = MetricsCfg { - path, - output_format: cfg.output_format.clone(), - pretty: cfg.pretty, - output_path: if cfg.output.is_empty() { - None - } else { - Some(PathBuf::from(cfg.output.clone())) - }, - }; - action::(&language, source, &cfg.path.clone(), pr, cfg) + if let Some(output_format) = &cfg.output_format { + let space = get_function_spaces(&language, source, &path, pr).unwrap(); + output_format.dump_formats(&space, &path, &cfg.output, cfg.pretty) + } else { + let cfg = MetricsCfg { path }; + action::(&language, source, &cfg.path.clone(), pr, cfg) + } } else if cfg.comments { let cfg = CommentRmCfg { in_place: cfg.in_place, @@ -474,9 +475,9 @@ fn main() { .value_of("output_format") .map(parse_or_exit::); let pretty = matches.is_present("pretty"); - let output = matches.value_of("output").unwrap().to_string(); - let output_is_dir = PathBuf::from(output.clone()).is_dir(); - if metrics && !output.is_empty() && !output_is_dir { + let output = matches.value_of("output").map(|s| PathBuf::from(s)); + let output_is_dir = output.as_ref().map(|p| p.is_dir()).unwrap_or(false); + if metrics && output.is_some() && !output_is_dir { eprintln!("Error: The output parameter must be a directory"); process::exit(1); } @@ -576,11 +577,10 @@ fn main() { fix_includes(&mut data.files, &all_files); let data = serde_json::to_string(&data).unwrap(); - if output.is_empty() { - println!("{}", data); + if let Some(output_path) = output { + write_file(&output_path, data.as_bytes()).unwrap(); } else { - let output = PathBuf::from(output); - write_file(&output, data.as_bytes()).unwrap(); + println!("{}", data); } } } diff --git a/src/lib.rs b/src/lib.rs index 84326802e..d7f37f319 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -48,10 +48,6 @@ extern crate lazy_static; #[macro_use] extern crate serde; -extern crate serde_cbor; -extern crate serde_json; -extern crate serde_yaml; -extern crate toml; #[macro_use] mod asttools; diff --git a/src/macros.rs b/src/macros.rs index bff6b49fa..917f57ab9 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -106,9 +106,6 @@ macro_rules! mk_action { /// // Configuration options used by the function which computes the metrics /// let cfg = MetricsCfg { /// path, - /// output_format: None, - /// pretty: false, - /// output_path: None, /// }; /// /// action::(&language, source_as_vec, &cfg.path.clone(), None, cfg); @@ -127,6 +124,38 @@ macro_rules! mk_action { )* } } + + /// Returns all function spaces data of a code. + /// + /// # Examples + /// + /// ``` + /// use std::path::PathBuf; + /// + /// use rust_code_analysis::{get_function_spaces, LANG}; + /// + /// # fn main() { + /// let source_code = "int a = 42;"; + /// let language = LANG::Cpp; + /// + /// // The path to a dummy file used to contain the source code + /// let path = PathBuf::from("foo.c"); + /// let source_as_vec = source_code.as_bytes().to_vec(); + /// + /// get_function_spaces(&language, source_as_vec, &path, None).unwrap(); + /// # } + /// ``` + #[inline(always)] + pub fn get_function_spaces(lang: &LANG, source: Vec, path: &PathBuf, pr: Option>) -> Option { + match lang { + $( + LANG::$camel => { + let parser = $parser::new(source, &path, pr); + metrics(&parser, &path) + }, + )* + } + } }; } diff --git a/src/output/dump_formats.rs b/src/output/dump_formats.rs deleted file mode 100644 index bce5b96e1..000000000 --- a/src/output/dump_formats.rs +++ /dev/null @@ -1,140 +0,0 @@ -use regex::Regex; -use std::fs::File; -use std::io::Write; -use std::io::{Error, ErrorKind}; -use std::path::PathBuf; -use std::str::FromStr; - -use crate::spaces::FuncSpace; - -#[derive(Debug, Clone)] -/// The list of implemented output formats. -pub enum Format { - /// The `CBOR` format - Cbor, - /// The `JSON` format - Json, - /// The `TOML` format - Toml, - /// The `YAML` format - Yaml, -} - -impl Format { - /// Returns the list of implemented output formats as a slice of `&str`. - /// - /// # Examples - /// - /// ``` - /// use rust_code_analysis::Format; - /// - /// # fn main() { - /// for format in Format::all().iter(){ - /// println!("{}", format); - /// } - /// # } - /// ``` - pub fn all() -> &'static [&'static str] { - &["cbor", "json", "toml", "yaml"] - } -} - -impl FromStr for Format { - type Err = String; - - fn from_str(format: &str) -> Result { - match format { - "cbor" => Ok(Format::Cbor), - "json" => Ok(Format::Json), - "toml" => Ok(Format::Toml), - "yaml" => Ok(Format::Yaml), - format => Err(format!("{:?} is not a supported format", format)), - } - } -} - -pub(crate) fn dump_formats( - space: &FuncSpace, - path: &PathBuf, - output_path: &Option, - output_format: Format, - pretty: bool, -) -> std::io::Result<()> { - if output_path.is_none() { - let stdout = std::io::stdout(); - let mut stdout = stdout.lock(); - - match output_format { - 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() - }; - write!(stdout, "{}", json_data) - } - Format::Toml => { - let toml_data = if pretty { - toml::to_string_pretty(&space).unwrap() - } else { - toml::to_string(&space).unwrap() - }; - write!(stdout, "{}", toml_data) - } - Format::Yaml => write!(stdout, "{}", serde_yaml::to_string(&space).unwrap()), - } - } else { - let format_ext = match output_format { - Format::Cbor => ".cbor", - Format::Json => ".json", - Format::Toml => ".toml", - Format::Yaml => ".yml", - }; - - let output_path = output_path.as_ref().unwrap(); - - let mut file = path.as_path().file_name().unwrap().to_os_string(); - file.push(format_ext); - - let mut format_path = output_path.clone(); - format_path.push(file); - - if format_path.as_path().exists() { - let mut new_filename = path.to_str().unwrap().to_string(); - let re = Regex::new(r"[\\:/]").unwrap(); - new_filename = re.replace_all(&new_filename, "_").to_string(); - new_filename.push_str(format_ext); - format_path.pop(); - format_path.push(new_filename); - } - - let mut format_file = File::create(format_path)?; - match output_format { - 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())), - } - } -} diff --git a/src/output/mod.rs b/src/output/mod.rs index 5b92f23aa..b6109b04e 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -1,8 +1,5 @@ pub(crate) mod dump; pub use dump::*; -pub(crate) mod dump_formats; -pub use dump_formats::*; - pub(crate) mod dump_metrics; pub use dump_metrics::*; diff --git a/src/spaces.rs b/src/spaces.rs index 3efdde1cc..fb93fc672 100644 --- a/src/spaces.rs +++ b/src/spaces.rs @@ -14,7 +14,6 @@ use crate::loc::{self, Loc}; use crate::mi::{self, Mi}; use crate::nom::{self, Nom}; -use crate::dump_formats::*; use crate::dump_metrics::*; use crate::traits::*; @@ -202,7 +201,8 @@ struct State<'a> { halstead_maps: HalsteadMaps<'a>, } -/// Returns function space data of the code in a file. +/// Returns all function spaces data of a code. This function needs a parser to +/// be created a priori in order to work. /// /// # Examples /// @@ -221,7 +221,7 @@ struct State<'a> { /// // The parser of the code, in this case a CPP parser /// let parser = CppParser::new(source_as_vec, &path, None); /// -/// // Gets the function space data of the code contained in foo.c +/// // Gets all function spaces data of the code contained in foo.c /// metrics(&parser, &path).unwrap(); /// # } /// ``` @@ -296,15 +296,6 @@ pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a PathBuf) -> Option, - /// If `true`, the `CBOR` and `JSON` output formats are - /// pretty-printed - pub pretty: bool, - /// Path to the output file containing the metrics - /// - /// If `None`, the metrics are dumped on shell - pub output_path: Option, } pub struct Metrics { @@ -317,17 +308,7 @@ impl Callback for Metrics { fn call(cfg: Self::Cfg, parser: &T) -> Self::Res { if let Some(space) = metrics(parser, &cfg.path) { - if let Some(output_format) = cfg.output_format { - dump_formats( - &space, - &cfg.path, - &cfg.output_path, - output_format, - cfg.pretty, - ) - } else { - dump_root(&space) - } + dump_root(&space) } else { Ok(()) }