Skip to content

Commit

Permalink
feat: allowing to read stdin when -
Browse files Browse the repository at this point in the history
  • Loading branch information
benfdking committed Nov 1, 2024
1 parent 186d26a commit 22653be
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 90 deletions.
4 changes: 2 additions & 2 deletions crates/cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,15 @@ pub(crate) enum Commands {

#[derive(Debug, Parser)]
pub(crate) struct LintArgs {
/// Files or directories to fix.
/// Files or directories to fix. Use `-` to read from stdin.
pub paths: Vec<PathBuf>,
#[arg(default_value = "human", short, long)]
pub format: Format,
}

#[derive(Debug, Parser)]
pub(crate) struct FixArgs {
/// Files or directories to fix.
/// Files or directories to fix. Use `-` to read from stdin.
pub paths: Vec<PathBuf>,
/// Skip the confirmation prompt and go straight to applying fixes.
#[arg(long)]
Expand Down
78 changes: 78 additions & 0 deletions crates/cli/src/commands_fix.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::check_user_input;
use crate::commands::FixArgs;
use crate::commands::Format;
use crate::linter;
use sqruff_lib::core::config::FluffConfig;
use std::path::Path;

pub(crate) fn run_fix(
args: FixArgs,
config: FluffConfig,
ignorer: impl Fn(&Path) -> bool + Send + Sync,
) -> i32 {
let FixArgs {
paths,
force,
format,
} = args;
let mut linter = linter(config, format);
let result = linter.lint_paths(paths, true, &ignorer);

if result
.paths
.iter()
.map(|path| path.files.iter().all(|file| file.violations.is_empty()))
.all(|v| v)
{
let count_files = result
.paths
.iter()
.map(|path| path.files.len())
.sum::<usize>();
println!("{} files processed, nothing to fix.", count_files);
0
} else {
if !force {
match check_user_input() {
Some(true) => {
eprintln!("Attempting fixes...");
}
Some(false) => return 0,
None => {
eprintln!("Invalid input, please enter 'Y' or 'N'");
eprintln!("Aborting...");
return 0;
}
}
}

for linted_dir in result.paths {
for mut file in linted_dir.files {
let path = std::mem::take(&mut file.path);
let write_buff = file.fix_string();
std::fs::write(path, write_buff).unwrap();
}
}

linter.formatter_mut().unwrap().completion_message();
0
}
}

pub(crate) fn run_fix_stdin(config: FluffConfig, format: Format) -> i32 {
let read_in = crate::stdin::read_std_in().unwrap();

let linter = linter(config, format);
let result = linter.lint_string(&read_in, None, true);

// print fixed to std out
let violations = result.get_violations(Some(false));
println!("{}", result.fix_string());

// if all fixable violations are fixable, return 0 else return 1
if violations.is_empty() {
0
} else {
1
}
}
62 changes: 62 additions & 0 deletions crates/cli/src/commands_lint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use crate::commands::{Format, LintArgs};
use crate::linter;
use sqruff_lib::core::config::FluffConfig;
use std::path::Path;

pub(crate) fn run_lint(
args: LintArgs,
config: FluffConfig,
ignorer: impl Fn(&Path) -> bool + Send + Sync,
) -> i32 {
let LintArgs { paths, format } = args;
let mut linter = linter(config, format);
let result = linter.lint_paths(paths, false, &ignorer);
let count: usize = result.paths.iter().map(|path| path.files.len()).sum();

// TODO this should be cleaned up better
if matches!(format, Format::GithubAnnotationNative) {
for path in result.paths {
for file in path.files {
for violation in file.violations {
let line = format!(
"::error title=sqruff,file={},line={},col={}::{}: {}",
file.path,
violation.line_no,
violation.line_pos,
violation.rule.as_ref().unwrap().code,
violation.description
);
eprintln!("{line}");
}
}
}
}

eprintln!("The linter processed {count} file(s).");
linter.formatter_mut().unwrap().completion_message();
if linter
.formatter()
.unwrap()
.has_fail
.load(std::sync::atomic::Ordering::SeqCst)
{
1
} else {
0
}
}

pub(crate) fn run_lint_stdin(config: FluffConfig, format: Format) -> i32 {
let read_in = crate::stdin::read_std_in().unwrap();

let mut linter = linter(config, format);
let result = linter.lint_string(&read_in, None, false);

linter.formatter_mut().unwrap().completion_message();

if result.get_violations(None).is_empty() {
0
} else {
1
}
}
105 changes: 19 additions & 86 deletions crates/cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use clap::Parser as _;
use commands::{FixArgs, Format, LintArgs};
use commands::Format;
use sqruff_lib::cli::formatters::OutputStreamFormatter;
use sqruff_lib::core::config::FluffConfig;
use sqruff_lib::core::linter::core::Linter;
use std::path::Path;
use std::sync::Arc;
use stdin::is_std_in_flag_input;

use crate::commands::{Cli, Commands};
#[cfg(feature = "codegen-docs")]
use crate::docs::codegen_docs;

mod commands;
mod commands_fix;
mod commands_lint;
#[cfg(feature = "codegen-docs")]
mod docs;
mod github_action;
mod ignore;
mod stdin;

#[cfg(all(
not(target_os = "windows"),
Expand All @@ -41,12 +45,10 @@ fn main() {

let mut extra_config_path = None;
let mut ignore_local_config = false;

if cli.config.is_some() {
extra_config_path = cli.config;
ignore_local_config = true;
}

let config = FluffConfig::from_root(extra_config_path, ignore_local_config, None).unwrap();
let current_path = std::env::current_dir().unwrap();
let ignore_file = ignore::IgnoreFile::new_from_root(&current_path).unwrap();
Expand All @@ -57,91 +59,22 @@ fn main() {
};

let status_code = match cli.command {
Commands::Lint(LintArgs { paths, format }) => {
let mut linter = linter(config, format);
let result = linter.lint_paths(paths, false, &ignorer);
let count: usize = result.paths.iter().map(|path| path.files.len()).sum();

// TODO this should be cleaned up better
if matches!(format, Format::GithubAnnotationNative) {
for path in result.paths {
for file in path.files {
for violation in file.violations {
let line = format!(
"::error title=sqruff,file={},line={},col={}::{}: {}",
file.path,
violation.line_no,
violation.line_pos,
violation.rule.as_ref().unwrap().code,
violation.description
);
eprintln!("{line}");
}
}
}
}

eprintln!("The linter processed {count} file(s).");
linter.formatter_mut().unwrap().completion_message();
if linter
.formatter()
.unwrap()
.has_fail
.load(std::sync::atomic::Ordering::SeqCst)
{
Commands::Lint(args) => match is_std_in_flag_input(&args.paths) {
Err(e) => {
eprintln!("{e}");
1
} else {
0
}
}
Commands::Fix(FixArgs {
paths,
force,
format,
}) => {
let mut linter = linter(config, format);
let result = linter.lint_paths(paths, true, &ignorer);

if result
.paths
.iter()
.map(|path| path.files.iter().all(|file| file.violations.is_empty()))
.all(|v| v)
{
let count_files = result
.paths
.iter()
.map(|path| path.files.len())
.sum::<usize>();
println!("{} files processed, nothing to fix.", count_files);
0
} else {
if !force {
match check_user_input() {
Some(true) => {
eprintln!("Attempting fixes...");
}
Some(false) => return,
None => {
eprintln!("Invalid input, please enter 'Y' or 'N'");
eprintln!("Aborting...");
return;
}
}
}

for linted_dir in result.paths {
for mut file in linted_dir.files {
let path = std::mem::take(&mut file.path);
let write_buff = file.fix_string();
std::fs::write(path, write_buff).unwrap();
}
}

linter.formatter_mut().unwrap().completion_message();
0
Ok(false) => commands_lint::run_lint(args, config, ignorer),
Ok(true) => commands_lint::run_lint_stdin(config, args.format),
},
Commands::Fix(args) => match is_std_in_flag_input(&args.paths) {
Err(e) => {
eprintln!("{e}");
1
}
}
Ok(false) => commands_fix::run_fix(args, config, ignorer),
Ok(true) => commands_fix::run_fix_stdin(config, args.format),
},
Commands::Lsp => {
sqruff_lsp::run();
0
Expand All @@ -151,7 +84,7 @@ fn main() {
std::process::exit(status_code);
}

fn linter(config: FluffConfig, format: Format) -> Linter {
pub(crate) fn linter(config: FluffConfig, format: Format) -> Linter {
let output_stream: Box<dyn std::io::Write + Send + Sync> = match format {
Format::Human => Box::new(std::io::stderr()),
Format::GithubAnnotationNative => Box::new(std::io::sink()),
Expand Down
47 changes: 47 additions & 0 deletions crates/cli/src/stdin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use std::io::Read;
use std::path::PathBuf;

/// Check if the given input is the flag to use stdin as input.
///
/// If the input is a single path and that path is `-`, then the input is the flag to use stdin as
/// input. Else, the input is not the flag to use stdin as input.
///
/// The error message is returned if any of the inputs are `-` and there are other inputs.
pub(crate) fn is_std_in_flag_input(inputs: &[PathBuf]) -> Result<bool, String> {
if inputs.len() == 1 && inputs[0] == PathBuf::from("-") {
Ok(true)
} else if inputs.iter().any(|input| *input == PathBuf::from("-")) {
Err("Cannot mix stdin flag with other inputs".to_string())
} else {
Ok(false)
}
}

/// Read the contents of stdin.
pub(crate) fn read_std_in() -> Result<String, String> {
let mut buffer = String::new();
std::io::stdin()
.read_to_string(&mut buffer)
.map_err(|e| e.to_string())?;
Ok(buffer)
}

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

#[test]
fn test_is_std_in_flag_input() {
let inputs = vec![PathBuf::from("-")];
assert_eq!(is_std_in_flag_input(&inputs), Ok(true));

let inputs = vec![PathBuf::from("file1"), PathBuf::from("-")];
assert_eq!(
is_std_in_flag_input(&inputs),
Err("Cannot mix stdin flag with other inputs".to_string())
);

let inputs = vec![PathBuf::from("file1")];
assert_eq!(is_std_in_flag_input(&inputs), Ok(false));
}
}
1 change: 1 addition & 0 deletions crates/cli/tests/stdin/stdin.exitcode
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1
4 changes: 4 additions & 0 deletions crates/cli/tests/stdin/stdin.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
== [<string>] FAIL
L: 1 | P: 20 | LT12 | Files must end with a single trailing newline.
| [layout.end_of_file]
All Finished
Loading

0 comments on commit 22653be

Please sign in to comment.