Skip to content

Commit

Permalink
Use + extend search mode
Browse files Browse the repository at this point in the history
  • Loading branch information
alexpovel committed Nov 3, 2024
1 parent cb76d0b commit 2e5320e
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 80 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ ignore = "0.4.23"
itertools = "0.13.0"
log = "0.4.22"
pathdiff = "0.2.1"
similar = "2.6.0"
tempfile = "3.13.0"
titlecase = "3.3.0"
tree-sitter = "0.23.0"
Expand Down
34 changes: 34 additions & 0 deletions src/iterext.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/// Extension trait that adds parallel zipping functionality to iterators over iterators.
pub trait ParallelZipExt: Iterator {
/// Zips multiple iterators in parallel, such that the nth invocation yields a
/// [`Vec`] of all nth items of the subiterators.
fn parallel_zip(self) -> ParallelZip<Self::Item>
where
Self: Sized,
Self::Item: Iterator;
}

/// An iterator similar to [`std::iter::zip`], but instead it zips over *multiple
/// iterators* in parallel, such that the nth invocation yields a [`Vec`] of all
/// nth items of its subiterators.
#[derive(Debug)]
pub struct ParallelZip<I>(Vec<I>);

impl<I: Iterator> Iterator for ParallelZip<I> {
type Item = Vec<I::Item>;

fn next(&mut self) -> Option<Self::Item> {
self.0.iter_mut().map(Iterator::next).collect()
}
}

// Implement the extension trait for any iterator whose items are themselves iterators
impl<T> ParallelZipExt for T
where
T: Iterator,
T::Item: Iterator,
{
fn parallel_zip(self) -> ParallelZip<T::Item> {
ParallelZip(self.collect())
}
}
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,6 @@ pub const GLOBAL_SCOPE: &str = r".*";
/// The type of regular expression used throughout the crate. Abstracts away the
/// underlying implementation.
pub use fancy_regex::Regex as RegexPattern;

/// Custom iterator extensions.
pub mod iterext;
91 changes: 42 additions & 49 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ use ignore::{WalkBuilder, WalkState};
use itertools::Itertools;
use log::{debug, error, info, trace, LevelFilter};
use pathdiff::diff_paths;
use similar::{ChangeTag, TextDiff};
#[cfg(feature = "german")]
use srgn::actions::German;
use srgn::actions::{
Action, ActionError, Deletion, Lower, Normalization, Replacement, Style, Titlecase, Upper,
};
#[cfg(feature = "symbols")]
use srgn::actions::{Symbols, SymbolsInversion};
use srgn::iterext::ParallelZipExt;
use srgn::scoping::langs::LanguageScoper;
use srgn::scoping::literal::{Literal, LiteralError};
use srgn::scoping::regex::{Regex, RegexError};
Expand Down Expand Up @@ -168,7 +168,11 @@ fn main() -> Result<()> {
info!("Will use search mode."); // Modelled after ripgrep!

let style = Style {
fg: Some(Color::Red),
fg: if options.dry_run {
Some(Color::Green) // "Would change to this", like git diff
} else {
Some(Color::Red) // "Found!", like ripgrep
},
styles: vec![Styles::Bold],
..Default::default()
};
Expand All @@ -187,13 +191,13 @@ fn main() -> Result<()> {
}

let pipeline = if options.dry_run {
let action = Style {
fg: Some(Color::Green),
let action: Box<dyn Action> = Box::new(Style {
fg: Some(Color::Red),
styles: vec![Styles::Bold],
..Default::default()
};
let action: Box<dyn Action> = Box::new(action);
vec![actions, vec![action]]
});
let color_only = vec![action];
vec![color_only, actions]
} else {
vec![actions]
};
Expand Down Expand Up @@ -579,7 +583,7 @@ fn process_path(

debug!("Processing path: {:?}", path);

let (new_contents, old_contents, filesize, changed) = {
let (new_contents, filesize, changed) = {
let mut file = File::open(&path)?;

let filesize = file.metadata().map_or(0, |m| m.len());
Expand All @@ -599,7 +603,7 @@ fn process_path(
pipeline,
)?;

(destination, source, filesize, changed)
(destination, filesize, changed)
};

// Hold the lock so results aren't intertwined
Expand Down Expand Up @@ -629,37 +633,17 @@ fn process_path(
}

if changed {
trace!(
"File contents changed, will process file: {}",
path.display()
debug!("Got new file contents, writing to file: {:?}", path);
assert!(
!global_options.dry_run,
// Dry run leverages search mode, so should never get here. Assert for
// extra safety.
"Dry running, but attempted to write file!"
);
fs::write(&path, new_contents.as_bytes())?;

if global_options.dry_run {
debug!(
"Dry-running: will not overwrite file '{}' with new contents: {}",
path.display(),
new_contents.escape_debug()
);
writeln!(stdout, "{}", format!("{}:", path.display()).magenta())?;
unreachable!("will never get here");

let diff = TextDiff::from_lines(&old_contents, &new_contents);
for change in diff.iter_all_changes() {
let (sign, color) = match change.tag() {
ChangeTag::Delete => ('-', Color::Red),
ChangeTag::Insert => ('+', Color::Green),
ChangeTag::Equal => continue,
};

write!(stdout, "{}", format!("{sign} {change}").color(color))?;
}
} else {
debug!("Got new file contents, writing to file: {:?}", path);
fs::write(&path, new_contents.as_bytes())?;

// Confirm after successful processing.
writeln!(stdout, "{}", path.display())?;
}
// Confirm after successful processing.
writeln!(stdout, "{}", path.display())?;
} else {
debug!(
"Skipping writing file anew (nothing changed): {}",
Expand Down Expand Up @@ -721,7 +705,10 @@ fn apply(
view.squeeze();
}

for actions in pipeline {
// Give each pipeline its own fresh view
let mut views = vec![view; pipeline.len()];

for (actions, view) in pipeline.iter().zip_eq(&mut views) {
for action in *actions {
view.map_with_context(action)?;
}
Expand All @@ -730,21 +717,27 @@ fn apply(
debug!("Writing to destination.");
let line_based = global_options.only_matching || global_options.line_numbers;
if line_based {
for (i, line) in view.lines().into_iter().enumerate() {
let line_views = views.iter().map(|v| v.lines().into_iter()).collect_vec();

for (i, lines) in line_views.into_iter().parallel_zip().enumerate() {
let i = i + 1;
if !global_options.only_matching || line.has_any_in_scope() {
if global_options.line_numbers {
// `ColoredString` needs to be 'evaluated' to do anything; make sure
// to not forget even if this is moved outside of `format!`.
#[allow(clippy::to_string_in_format_args)]
destination.push_str(&format!("{}:", i.to_string().green().to_string()));
}
for line in lines {
if !global_options.only_matching || line.has_any_in_scope() {
if global_options.line_numbers {
// `ColoredString` needs to be 'evaluated' to do anything; make sure
// to not forget even if this is moved outside of `format!`.
#[allow(clippy::to_string_in_format_args)]
destination.push_str(&format!("{}:", i.to_string().green().to_string()));
}

destination.push_str(&line.to_string());
destination.push_str(&line.to_string());
}
}
}
} else {
destination.push_str(&view.to_string());
for view in views {
destination.push_str(&view.to_string());
}
};
debug!("Done writing to destination.");

Expand Down
25 changes: 14 additions & 11 deletions tests/snapshots/cli__tests__files-inplace-python-dry-run-macos.snap
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ args:
- baz
stdin: ~
stdout:
- "1.py:\n"
- "- # This string is found and touched: foo\n"
- "+ # This string is found and touched: baz\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "subdir/2.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "subdir/subdir/3.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "1.py\n"
- "1:# This string is found and touched: foo\n"
- "1:# This string is found and touched: baz\n"
- "4:def foo(bar: int) -> int:\n"
- "4:def baz(bar: int) -> int:\n"
- "\n"
- "subdir/2.py\n"
- "1:def foo(bar: int) -> int:\n"
- "1:def baz(bar: int) -> int:\n"
- "\n"
- "subdir/subdir/3.py\n"
- "1:def foo(bar: int) -> int:\n"
- "1:def baz(bar: int) -> int:\n"
- "\n"
exit_code: 0
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ args:
- baz
stdin: ~
stdout:
- "subdir/2.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "subdir/subdir/3.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "subdir/2.py\n"
- "1:def foo(bar: int) -> int:\n"
- "1:def baz(bar: int) -> int:\n"
- "\n"
- "subdir/subdir/3.py\n"
- "1:def foo(bar: int) -> int:\n"
- "1:def baz(bar: int) -> int:\n"
- "\n"
exit_code: 0
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ args:
- baz
stdin: ~
stdout:
- "1-shebanged:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "1.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "subdir/2.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "subdir/subdir/3.py:\n"
- "- def foo(bar: int) -> int:\n"
- "+ def baz(bar: int) -> int:\n"
- "1-shebanged\n"
- "9:def foo(bar: int) -> int:\n"
- "9:def baz(bar: int) -> int:\n"
- "\n"
- "1.py\n"
- "4:def foo(bar: int) -> int:\n"
- "4:def baz(bar: int) -> int:\n"
- "\n"
- "subdir/2.py\n"
- "1:def foo(bar: int) -> int:\n"
- "1:def baz(bar: int) -> int:\n"
- "\n"
- "subdir/subdir/3.py\n"
- "1:def foo(bar: int) -> int:\n"
- "1:def baz(bar: int) -> int:\n"
- "\n"
exit_code: 0

0 comments on commit 2e5320e

Please sign in to comment.