Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compare command #282

Merged
merged 2 commits into from Dec 25, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
mstruebing marked this conversation as resolved.
Show resolved Hide resolved
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();
mstruebing marked this conversation as resolved.
Show resolved Hide resolved

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)
mgrachev marked this conversation as resolved.
Show resolved Hide resolved
.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,
)
}
mstruebing marked this conversation as resolved.
Show resolved Hide resolved

#[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;