Skip to content

Commit

Permalink
Shows files that were linted (#311)
Browse files Browse the repository at this point in the history
* Show linted files

* Add tests for output

* Move tests, enum, and add backup tests

* Fix tests from rebase

* Move mode delcaration outside for loop

* Make mode copy

Co-authored-by: Grachev Mikhail <work@mgrachev.com>
  • Loading branch information
Anthuang and mgrachev committed Oct 26, 2020
1 parent 4e4d516 commit a5903f9
Show file tree
Hide file tree
Showing 30 changed files with 1,080 additions and 219 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
- Display linted files when run [#311](https://github.com/dotenv-linter/dotenv-linter/pull/311) ([@Anthuang](https://github.com/anthuang))

### 馃敡 Changed

Expand Down
2 changes: 2 additions & 0 deletions src/common.rs
@@ -1,10 +1,12 @@
pub(crate) mod comment;
mod file_entry;
mod line_entry;
pub(crate) mod output;
mod warning;

pub use file_entry::FileEntry;
pub use line_entry::LineEntry;
pub use output::Output;
pub use warning::Warning;

pub const LF: &str = "\n";
Expand Down
119 changes: 119 additions & 0 deletions src/common/output.rs
@@ -0,0 +1,119 @@
use crate::common::{FileEntry, Warning};
use std::ffi::OsString;
use std::fmt;

/// Mode in which the program is run.
#[derive(Clone, Copy)]
pub enum Mode {
Fix,
Check,
}

/// Prefix for the backup output.
const BACKUP_PREFIX: &str = "Original file was backed up to: ";

/// Wraps warnings to provide more information when printing.
pub struct Output {
/// Path of the file the warnings originated from.
path: FileEntry,

/// Path of the file's backup.
backup_path: Option<OsString>,

/// List of warnings.
pub warnings: Vec<Warning>,

/// Mode of the program.
mode: Mode,
}

impl Output {
pub fn new(
path: FileEntry,
backup_path: Option<OsString>,
warnings: Vec<Warning>,
mode: Mode,
) -> Self {
Self {
path,
backup_path,
warnings,
mode,
}
}

/// Prints warnings without any additional information.
pub fn print_warnings(&self) {
self.warnings.iter().for_each(|w| println!("{}", w));
}

/// Prints the backup file's path.
pub fn print_backup(&self) {
if let Some(p) = &self.backup_path {
println!("{}{:?}", BACKUP_PREFIX, p);
}
}
}

impl fmt::Display for Output {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.mode {
Mode::Fix => {
write!(f, "Fixing {}", self.path)?;
}
Mode::Check => {
write!(f, "Checking {}", self.path)?;
}
}
if let Some(p) = &self.backup_path {
writeln!(f, "\n{}{:?}", BACKUP_PREFIX, p)?;
}
if !self.warnings.is_empty() {
writeln!(f)?;
}
for w in self.warnings.iter() {
writeln!(f, "{}", w)?;
}
Ok(())
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::common::tests::*;

#[test]
fn output_fmt_test() {
let line = line_entry(1, 1, "FOO=BAR");
let warning = Warning::new(
line.clone(),
"DuplicatedKey",
String::from("The FOO key is duplicated"),
);
let output = Output::new(line.file, None, vec![warning], Mode::Check);

assert_eq!(
"Checking .env\n.env:1 DuplicatedKey: The FOO key is duplicated\n",
format!("{}", output)
);
}

#[test]
fn fix_output_fmt_test() {
let line = line_entry(1, 1, "FOO=BAR");
let warning = Warning::new(
line.clone(),
"DuplicatedKey",
String::from("The FOO key is duplicated"),
);

let backup_path = OsString::from(".env_1234");
let output = Output::new(line.file, Some(backup_path), vec![warning], Mode::Fix);

assert_eq!(
"Fixing .env\nOriginal file was backed up to: \".env_1234\"\n\n.env:1 DuplicatedKey: The FOO key is duplicated\n",
format!("{}", output)
);
}
}
30 changes: 24 additions & 6 deletions src/lib.rs
Expand Up @@ -11,7 +11,7 @@ mod fs_utils;
pub use checks::available_check_names;

#[allow(clippy::redundant_closure)]
pub fn run(args: &clap::ArgMatches, current_dir: &PathBuf) -> Result<Vec<Warning>, Box<dyn Error>> {
pub fn run(args: &clap::ArgMatches, current_dir: &PathBuf) -> Result<Vec<Output>, Box<dyn Error>> {
let mut file_paths: Vec<PathBuf> = Vec::new();
let mut skip_checks: Vec<&str> = Vec::new();
let mut excluded_paths: Vec<PathBuf> = Vec::new();
Expand All @@ -37,7 +37,12 @@ pub fn run(args: &clap::ArgMatches, current_dir: &PathBuf) -> Result<Vec<Warning
}

let is_fix = args.is_present("fix");
let mut warnings: Vec<Warning> = Vec::new();
let mut outputs: Vec<Output> = Vec::new();
let mode = if is_fix {
output::Mode::Fix
} else {
output::Mode::Check
};

for path in file_paths {
let relative_path = match fs_utils::get_relative_path(&path, &current_dir) {
Expand All @@ -51,25 +56,38 @@ pub fn run(args: &clap::ArgMatches, current_dir: &PathBuf) -> Result<Vec<Warning
};

let mut lines = get_line_entries(&fe, strs);
let mut backup_file = None;

// run fixers & write results to file
let mut result = checks::run(&lines, &skip_checks);
if is_fix && !result.is_empty() && fixes::run(&mut result, &mut lines, &skip_checks) > 0 {
// create backup copy unless user specifies not to
let should_backup = !args.is_present("no-backup");
if should_backup {
let backup_file = fs_utils::backup_file(&fe)?;
println!("Original file was backed up to: {:?}\n", backup_file);
backup_file = Some(fs_utils::backup_file(&fe)?.into_os_string());
}

// write corrected file
fs_utils::write_file(&fe.path, lines)?;
}

warnings.extend(result);
outputs.push(Output::new(fe, backup_file, result, mode));
}

Ok(warnings)
Ok(outputs)
}

/// Prints outputs with correct newlines.
///
/// Prints a newline after an output if it is not the last output or, for the
/// last output, it has no warnings.
pub fn print_outputs(outputs: Vec<Output>) {
for (i, output) in outputs.iter().enumerate() {
print!("{}", output);
if i != outputs.len() - 1 || (i == outputs.len() - 1 && output.warnings.is_empty()) {
println!();
}
}
}

fn get_file_paths(
Expand Down
53 changes: 35 additions & 18 deletions src/main.rs
Expand Up @@ -14,42 +14,59 @@ fn main() -> Result<(), Box<dyn Error>> {
process::exit(0);
}

let warnings = dotenv_linter::run(&args, &current_dir)?;
let outputs = dotenv_linter::run(&args, &current_dir)?;

if warnings.is_empty() {
if outputs.is_empty() {
process::exit(0);
}

let total = warnings.len();
let total = outputs.iter().map(|o| o.warnings.len()).sum();
let is_not_quiet = !args.is_present("quiet");

if args.is_present("fix") {
if is_not_quiet {
warnings.iter().for_each(|w| println!("{}", w));
println!();
dotenv_linter::print_outputs(outputs);
} else {
outputs.iter().for_each(|w| w.print_backup());
}

println!("All warnings are fixed. Total: {}", total);
print_fix_total(total);
process::exit(0);
} else {
warnings.iter().for_each(|w| println!("{}", w));
}

if is_not_quiet {
print_total(total);
}
if is_not_quiet {
dotenv_linter::print_outputs(outputs);
print_check_total(total);
} else {
outputs.iter().for_each(|w| w.print_warnings());
}

// Ensure the exit code is 0 if there were no warnings
if total == 0 {
process::exit(0);
}
process::exit(1);
}

fn print_total(total: usize) {
let mut problems = String::from("problem");

if total > 1 {
problems += "s";
fn print_fix_total(total: usize) {
if total != 0 {
println!("\nAll warnings are fixed. Total: {}", total);
} else {
println!("\nNo warnings found");
}
}

fn print_check_total(total: usize) {
if total != 0 {
let mut problems = String::from("problem");

println!("\n{}", format!("Found {} {}", total, problems));
if total != 1 {
problems += "s";
}

println!("\nFound {} {}", total, problems);
} else {
println!("\nNo problems found");
}
}

fn get_args(current_dir: &OsStr) -> clap::ArgMatches {
Expand Down
25 changes: 14 additions & 11 deletions tests/args/current_dir.rs
@@ -1,35 +1,38 @@
use crate::common::TestDir;
use crate::common::*;

#[test]
fn exits_with_0_on_no_warnings() {
let test_dir = TestDir::new();
test_dir.create_testfile(".env", "FOO=bar\n");
test_dir.test_command_success();
let expected_output = check_output(&[(".env", &[])]);
test_dir.test_command_success(expected_output);
}

#[test]
fn checks_current_dir() {
let testdir = TestDir::new();
let testfile = testdir.create_testfile(".env", "FOO\n");

testdir.test_command_fail(
format!(
"{}:1 KeyWithoutValue: The FOO key should be with a value or have an equal sign\n\nFound 1 problem\n",
testdir.test_command_fail(check_output(&[(
testfile.shortname_as_str(),
&[format!(
"{}:1 KeyWithoutValue: The FOO key should be with a value or have an equal sign",
testfile.shortname_as_str()
)
);
.as_str()],
)]));
}

#[test]
fn checks_current_dir_with_dot_arg() {
let testdir = TestDir::new();
let testfile = testdir.create_testfile("test.env", "foo=\n");
testdir.create_testfile("test.env", "foo=\n");

let args = &["."];
let expected_output = format!(
"{}:1 LowercaseKey: The foo key should be in uppercase\n\nFound 1 problem\n",
testfile.shortname_as_str(),
);
let expected_output = check_output(&[(
"test.env",
&["test.env:1 LowercaseKey: The foo key should be in uppercase"],
)]);

testdir.test_command_fail_with_args(args, expected_output);
}

0 comments on commit a5903f9

Please sign in to comment.