Skip to content

Commit

Permalink
feat: add compare-command
Browse files Browse the repository at this point in the history
  • Loading branch information
mstruebing authored and Max Str眉bing committed Dec 24, 2020
1 parent 20a7a3b commit 0095068
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 4 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
### 馃殌 Added
- Add `compare`-command [#282](https://github.com/dotenv-linter/dotenv-linter/pull/282) ([@mstruebing](https://github.com/mstruebing))
- Add colored output feature and `--no-color` flag to disable colors [#307](https://github.com/dotenv-linter/dotenv-linter/pull/307) ([@Nikhil0487](https://github.com/Nikhil0487))
- Display linted files when run [#311](https://github.com/dotenv-linter/dotenv-linter/pull/311) ([@Anthuang](https://github.com/anthuang))
- Add export prefix support [#340](https://github.com/dotenv-linter/dotenv-linter/pull/340)([@skonik](https://github.com/skonik))
Expand Down
4 changes: 4 additions & 0 deletions src/common.rs
@@ -1,13 +1,17 @@
pub(crate) mod comment;
mod compare;
mod file_entry;
mod line_entry;
pub(crate) mod output;
mod warning;

use colored::*;
pub use compare::CompareFileType;
pub use compare::CompareWarning;
pub use file_entry::FileEntry;
pub use line_entry::LineEntry;
pub use output::check::CheckOutput;
pub use output::compare::CompareOutput;
pub use output::fix::FixOutput;
pub use warning::Warning;

Expand Down
31 changes: 31 additions & 0 deletions src/common/compare.rs
@@ -0,0 +1,31 @@
use std::fmt;
use std::path::PathBuf;

use crate::common::*;

// A structure used to compare environment files
pub struct CompareFileType {
pub path: PathBuf,
pub keys: Vec<String>,
pub missing: Vec<String>,
}

pub struct CompareWarning {
pub path: PathBuf,
pub missing_keys: Vec<String>,
}

impl fmt::Display for CompareWarning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
format!(
"{} is missing keys: {}",
self.path.display(),
self.missing_keys.join(", ")
)
.italic(),
)
}
}
24 changes: 24 additions & 0 deletions src/common/output/compare.rs
@@ -0,0 +1,24 @@
use crate::common::{CompareWarning, FileEntry};

pub struct CompareOutput {
// Quiet program output mode
is_quiet_mode: bool,
}

impl CompareOutput {
pub fn new(is_quiet_mode: bool) -> Self {
CompareOutput { is_quiet_mode }
}

/// Prints information about a file in process
pub fn print_processing_info(&self, file: &FileEntry) {
if !self.is_quiet_mode {
println!("Comparing {}", file);
}
}

/// Prints warnings without any additional information
pub fn print_warnings(&self, warnings: &[CompareWarning]) {
warnings.iter().for_each(|w| println!("{}", w))
}
}
1 change: 1 addition & 0 deletions src/common/output/mod.rs
@@ -1,2 +1,3 @@
pub mod check;
pub mod compare;
pub mod fix;
64 changes: 64 additions & 0 deletions src/lib.rs
@@ -1,5 +1,6 @@
use crate::common::*;
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::error::Error;
use std::path::PathBuf;

Expand All @@ -9,6 +10,7 @@ mod fixes;
mod fs_utils;

pub use checks::available_check_names;
use common::CompareWarning;

pub fn check(args: &clap::ArgMatches, current_dir: &PathBuf) -> Result<usize, Box<dyn Error>> {
let lines_map = get_lines(args, current_dir)?;
Expand Down Expand Up @@ -124,6 +126,68 @@ fn get_needed_file_paths(args: &clap::ArgMatches) -> Vec<PathBuf> {
file_paths
}

// Compares if different environment files contains the same variables and
// returns warnings if not
pub fn compare(
args: &clap::ArgMatches,
current_dir: &PathBuf,
) -> Result<Vec<CompareWarning>, Box<dyn Error>> {
let mut all_keys: HashSet<String> = HashSet::new();
let lines_map = get_lines(args, current_dir)?;
let output = CompareOutput::new(args.is_present("quiet"));

let mut warnings: Vec<CompareWarning> = Vec::new();
let mut files_to_compare: Vec<CompareFileType> = Vec::new();

// Nothing to check
if lines_map.is_empty() {
return Ok(warnings);
}

// // Create CompareFileType structures for each file
for (_, (fe, strings)) in lines_map.into_iter().enumerate() {
output.print_processing_info(&fe);
let lines = get_line_entries(&fe, strings);
let mut keys: Vec<String> = Vec::new();

for line in lines {
if let Some(key) = line.get_key() {
all_keys.insert(key.to_string());
keys.push(key.to_string());
}
}

let file_to_compare: CompareFileType = CompareFileType {
path: fe.path,
keys,
missing: Vec::new(),
};

files_to_compare.push(file_to_compare);
}

// Create warnings if any file misses any key
for file in files_to_compare {
let missing_keys: Vec<_> = all_keys
.iter()
.filter(|key| !file.keys.contains(key))
.map(|key| key.to_owned())
.collect();

if !missing_keys.is_empty() {
let warning = CompareWarning {
path: file.path,
missing_keys,
};

warnings.push(warning)
}
}

output.print_warnings(&warnings);
Ok(warnings)
}

fn get_file_paths(
dir_entries: Vec<PathBuf>,
excludes: &[PathBuf],
Expand Down
33 changes: 29 additions & 4 deletions src/main.rs
Expand Up @@ -3,6 +3,13 @@ use std::error::Error;
use std::ffi::OsStr;
use std::{env, process};

fn quiet_flag() -> clap::Arg<'static, 'static> {
Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("Doesn't display additional information")
}

fn main() -> Result<(), Box<dyn Error>> {
#[cfg(windows)]
colored::control::set_virtual_terminal(true).ok();
Expand Down Expand Up @@ -33,6 +40,12 @@ fn main() -> Result<(), Box<dyn Error>> {

process::exit(0);
}
("compare", Some(files)) => {
let warnings = dotenv_linter::compare(&files, &current_dir)?;
if warnings.is_empty() {
process::exit(0);
}
}
_ => {
eprintln!("unknown command");
}
Expand Down Expand Up @@ -71,6 +84,21 @@ fn get_args(current_dir: &OsStr) -> clap::ArgMatches {
.usage("dotenv-linter fix [FLAGS] [OPTIONS] <input>...")
.about("Automatically fixes warnings"),
)
.subcommand(
SubCommand::with_name("compare")
.setting(AppSettings::ColoredHelp)
.visible_alias("c")
.args(&vec![
Arg::with_name("input")
.help("Files to compare")
.multiple(true)
.min_values(2)
.required(true),
quiet_flag(),
])
.about("Compares if files have the same keys")
.usage("dotenv-linter compare <files>..."),
)
.get_matches()
}

Expand Down Expand Up @@ -103,9 +131,6 @@ fn common_args(current_dir: &OsStr) -> Vec<Arg> {
Arg::with_name("no-color")
.long("no-color")
.help("Turns off the colored output"),
Arg::with_name("quiet")
.short("q")
.long("quiet")
.help("Doesn't display additional information"),
quiet_flag(),
]
}
1 change: 1 addition & 0 deletions tests/cli.rs
@@ -1,6 +1,7 @@
mod args;
mod checks;
mod common;
mod compare;
mod fixes;
mod flags;
mod options;
Expand Down
63 changes: 63 additions & 0 deletions tests/compare/compare.rs
@@ -0,0 +1,63 @@
use crate::common::TestDir;

#[test]
fn files_with_same_environment_variables() {
let test_dir = TestDir::new();
let testfile_one = test_dir.create_testfile(".env1", "FOO=abc\nBAR=def");
let testfile_two = test_dir.create_testfile(".env2", "FOO=abc\nBAR=def");
let expected_output = format!("Comparing .env1\nComparing .env2\n");

test_dir.test_command_success_with_args(
&["compare", testfile_one.as_str(), testfile_two.as_str()],
expected_output,
);
}

#[test]
fn files_with_same_environment_variables_in_quiet_mode() {
let test_dir = TestDir::new();
let testfile_one = test_dir.create_testfile(".env1", "FOO=abc\nBAR=def");
let testfile_two = test_dir.create_testfile(".env2", "FOO=abc\nBAR=def");
let expected_output = format!("");

test_dir.test_command_success_with_args(
&[
"compare",
"--quiet",
testfile_one.as_str(),
testfile_two.as_str(),
],
expected_output,
);
}

#[test]
fn files_with_different_environment_variables() {
let test_dir = TestDir::new();
let testfile_one = test_dir.create_testfile(".env1", "FOO=abc");
let testfile_two = test_dir.create_testfile(".env2", "FOO=abc\nBAR=def");
let expected_output = format!("Comparing .env1\nComparing .env2\n.env1 is missing keys: BAR\n");

test_dir.test_command_fail_with_args(
&["compare", testfile_one.as_str(), testfile_two.as_str()],
expected_output,
)
}

#[test]
fn files_with_different_environment_variables_in_quiet_mode() {
let test_dir = TestDir::new();
let testfile_one = test_dir.create_testfile(".env1", "FOO=abc");
let testfile_two = test_dir.create_testfile(".env2", "FOO=abc\nBAR=def");
let expected_output = format!(".env1 is missing keys: BAR\n");

test_dir.test_command_fail_with_args(
&[
"compare",
"--quiet",
testfile_one.as_str(),
testfile_two.as_str(),
],
expected_output,
)
}
1 change: 1 addition & 0 deletions tests/compare/mod.rs
@@ -0,0 +1 @@
mod compare;

0 comments on commit 0095068

Please sign in to comment.