Repository: o0Ignition0o/cargo-scout (language: rust)

Issue:
Split scout into a lib and a bin ?
Having a lib.rs and a main.rs would be a nice first steps towards integration tests, as explained in #54 

Splitting this would also allow us to reason about what we want to expose, and add documentation to exposed structures and traits. And would be a nice step towards having a Contributing.md file or a wiki.


Discussion / hints:

Fix patch:
diff --git a/src/main.rs b/src/bin/cargo-scout.rs
similarity index 63%
rename from src/main.rs
rename to src/bin/cargo-scout.rs
index ddbc789..ee762a3 100644
--- a/src/main.rs
+++ b/src/bin/cargo-scout.rs
@@ -1,13 +1,5 @@
-use crate::linter::clippy::Clippy;
-use crate::linter::Lint;
-use std::env;
-use structopt::StructOpt;
-
-mod error;
-mod git;
-mod linter;
-mod project;
-mod scout;
+use cargo_scout::{run, Error, Lint, ScoutOptions};
+pub use structopt::StructOpt;
 
 #[derive(Debug, StructOpt)]
 #[structopt(
@@ -45,47 +37,27 @@ struct Options {
     preview: bool,
 }
 
-fn display_warnings(warnings: &[Lint]) {
-    for w in warnings {
-        if let Some(m) = &w.message {
-            for l in m.rendered.split('\n') {
-                println!("{}", l);
-            }
+impl From<Options> for ScoutOptions {
+    fn from(o: Options) -> Self {
+        Self {
+            verbose: o.verbose,
+            no_default_features: o.no_default_features,
+            all_features: o.all_features,
+            branch: o.branch,
+            cargo_toml: o.cargo_toml,
+            preview: o.preview,
         }
     }
-    println!("Clippy::pedantic found {} warnings", warnings.len());
 }
 
-fn main() -> Result<(), error::Error> {
+fn main() -> Result<(), Error> {
     let opts = Options::from_args();
-
-    println!("Getting diff against target {}", opts.branch);
-    let diff_sections = git::get_sections(env::current_dir()?, &opts.branch)?;
-
-    println!("Checking Cargo manifest");
-    let mut project = project::cargo::Project::from_manifest_path(opts.cargo_toml)?;
-    project.set_all_features(opts.all_features);
-    project.set_no_default_features(opts.no_default_features);
-
-    let mut clippy_linter = Clippy::new();
-    clippy_linter
-        .set_verbose(opts.verbose)
-        .set_no_default_features(opts.no_default_features)
-        .set_all_features(opts.all_features)
-        .set_preview(opts.preview);
-
-    let mut scout_builder = scout::Builder::new();
-    scout_builder
-        .set_project_config(project)
-        .set_linter(clippy_linter);
-    println!("Running clippy");
-    let scout = scout_builder.build()?;
-
-    let relevant_lints = scout.run_for_diff(&diff_sections)?;
-    return_warnings(&relevant_lints, opts.without_error)
+    let fail_if_errors = opts.without_error;
+    let relevant_lints = run(opts.into())?;
+    return_warnings(&relevant_lints, fail_if_errors)
 }
 
-fn return_warnings(lints: &[linter::Lint], without_error: bool) -> Result<(), error::Error> {
+fn return_warnings(lints: &[Lint], without_error: bool) -> Result<(), Error> {
     if lints.is_empty() {
         println!("No warnings raised by clippy::pedantic in your diff, you're good to go!");
         Ok(())
@@ -94,15 +66,25 @@ fn return_warnings(lints: &[linter::Lint], without_error: bool) -> Result<(), er
         if without_error {
             Ok(())
         } else {
-            Err(error::Error::NotClean)
+            Err(Error::NotClean)
+        }
+    }
+}
+
+fn display_warnings(warnings: &[Lint]) {
+    for w in warnings {
+        if let Some(m) = &w.message {
+            for l in m.rendered.split('\n') {
+                println!("{}", l);
+            }
         }
     }
+    println!("Clippy::pedantic found {} warnings", warnings.len());
 }
 
 #[cfg(test)]
 mod tests {
-    use crate::linter::Lint;
-    use crate::return_warnings;
+    use super::*;
     #[test]
     fn test_return_status_with_lints() {
         let lints = vec![Lint {
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..32da0ad
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,54 @@
+pub use error::Error;
+pub use linter::{Lint, Linter};
+pub use project::Config;
+
+use linter::clippy::Clippy;
+use project::cargo::Config as CargoConfig;
+use scout::Builder;
+use std::env;
+
+mod error;
+mod git;
+mod linter;
+mod project;
+mod scout;
+
+pub struct ScoutOptions {
+    /// Set the verbosity level
+    pub verbose: bool,
+    /// Pass the no default features flag to clippy
+    pub no_default_features: bool,
+    /// Pass the all features flag to clippy
+    pub all_features: bool,
+    /// Set the target branch
+    pub branch: String,
+    /// Pass the path of the `Cargo.toml` file
+    pub cargo_toml: String,
+    /// Enable nightly features (e.g. get lints even after the build has already been done.)
+    pub preview: bool,
+}
+
+pub fn run(opts: ScoutOptions) -> Result<Vec<Lint>, Error> {
+    println!("Getting diff against target {}", opts.branch);
+    let diff_sections = git::get_sections(env::current_dir()?, &opts.branch)?;
+
+    println!("Checking Cargo manifest");
+    let mut project = CargoConfig::from_manifest_path(opts.cargo_toml)?;
+    project.set_all_features(opts.all_features);
+    project.set_no_default_features(opts.no_default_features);
+
+    let mut clippy_linter = Clippy::new();
+    clippy_linter
+        .set_verbose(opts.verbose)
+        .set_no_default_features(opts.no_default_features)
+        .set_all_features(opts.all_features)
+        .set_preview(opts.preview);
+
+    let mut scout_builder = Builder::new();
+    scout_builder
+        .set_project_config(project)
+        .set_linter(clippy_linter);
+    println!("Running clippy");
+    let scout = scout_builder.build()?;
+    scout.run_for_diff(&diff_sections)
+}
diff --git a/src/project/cargo.rs b/src/project/cargo.rs
index 0f7587e..f7c21ba 100644
--- a/src/project/cargo.rs
+++ b/src/project/cargo.rs
@@ -1,14 +1,14 @@
-use crate::project::Config;
+use crate::project::Config as ProjectConfig;
 use serde::Deserialize;
 
 #[derive(Deserialize, PartialEq, Debug, Clone)]
-pub struct Project {
+pub struct Config {
     no_default_features: bool,
     all_features: bool,
     members: Vec<String>,
 }
 
-impl Config for Project {
+impl ProjectConfig for Config {
     fn linter_must_iterate(&self) -> bool {
         !self.members.is_empty() && (self.no_default_features || self.all_features)
     }
@@ -17,7 +17,7 @@ impl Config for Project {
     }
 }
 
-impl Project {
+impl Config {
     pub fn from_manifest_path(p: impl AsRef<std::path::Path>) -> Result<Self, crate::error::Error> {
         Ok(Self::from_manifest(cargo_toml::Manifest::from_path(p)?))
     }
@@ -51,19 +51,20 @@ impl Project {
 
 #[cfg(test)]
 mod tests {
+    use crate::project::cargo::Config;
+    use crate::project::Config as ProjectConfig;
+
     #[test]
     fn test_not_workspace_manifest() {
-        use crate::project::cargo::Project;
-        use crate::project::Config;
         let no_members: Vec<String> = Vec::new();
         let manifest = cargo_toml::Manifest::from_path("Cargo.toml").unwrap();
         // Make sure we actually parsed the manifest
         assert_eq!("cargo-scout", manifest.clone().package.unwrap().name);
-        let mut project = Project::from_manifest(manifest);
+        let mut project = Config::from_manifest(manifest);
         assert!(!project.linter_must_iterate());
         assert_eq!(no_members, project.get_members());
 
-        // Project must not iterate if not running in a workspace,
+        // Config must not iterate if not running in a workspace,
         // regardless of the passed flags
         project.set_all_features(true);
         assert!(!project.linter_must_iterate());
@@ -74,14 +75,12 @@ mod tests {
     }
     #[test]
     fn test_not_workspace_path() {
-        use crate::project::cargo::Project;
-        use crate::project::Config;
         let no_members: Vec<String> = Vec::new();
-        let mut project = Project::from_manifest_path("Cargo.toml").unwrap();
+        let mut project = Config::from_manifest_path("Cargo.toml").unwrap();
         assert!(!project.linter_must_iterate());
         assert_eq!(no_members, project.get_members());
 
-        // Project must not iterate if not running in a workspace,
+        // Config must not iterate if not running in a workspace,
         // regardless of the passed flags
         project.set_all_features(true);
         assert!(!project.linter_must_iterate());
@@ -92,8 +91,6 @@ mod tests {
     }
     #[test]
     fn test_neqo_members_manifest() {
-        use crate::project::cargo::Project;
-        use crate::project::Config;
         let neqo_toml = r#"[workspace]
         members = [
           "neqo-client",
@@ -110,7 +107,7 @@ mod tests {
 
         let manifest = cargo_toml::Manifest::from_slice(neqo_toml.as_bytes()).unwrap();
 
-        let mut project = Project::from_manifest(manifest);
+        let mut project = Config::from_manifest(manifest);
 
         assert!(!project.linter_must_iterate());
         assert_eq!(
@@ -128,7 +125,7 @@ mod tests {
             ],
             project.get_members()
         );
-        // Project must iterate if running in a workspace
+        // Config must iterate if running in a workspace
         // With all features or no default features is enabled
         project.set_all_features(true);
         assert!(project.linter_must_iterate());


Test patch: