From 13939c93b959ed860abad3e8c1dd89739b37c941 Mon Sep 17 00:00:00 2001 From: Jan Pikl Date: Fri, 29 Mar 2024 02:04:56 +0100 Subject: [PATCH] Support x quote flag --- TODO.txt | 3 +-- docs/reference/rew-x.md | 54 +++++++++++++++++++++++++++++++++++++++++ src/commands/x.rs | 24 +++++++++++++++++- src/pattern.rs | 33 +++++++++++++++++++++++-- tests/commands/x.rs | 2 ++ 5 files changed, 111 insertions(+), 5 deletions(-) diff --git a/TODO.txt b/TODO.txt index 8828a94..889a90c 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,8 +1,7 @@ 0.4 -- Better shell specific examples for `rew x`. -- Add `rew x --quote` with examples. - Fix `--examples` order in generated docs. - Make `rew x` escape sequences forward compatible (errors for unkown escape sequences). +- Better shell specific examples for `rew x` + cleanup and reorder all `rew x` examples. - Add `REW_PREFIX` env var to enforce stderr prefix over detecting it from argv[0]? - Refactor and split `x.rs` into multiple files. - Add missing unit test to increase code coverage. diff --git a/docs/reference/rew-x.md b/docs/reference/rew-x.md index 4b9025c..73fe460 100644 --- a/docs/reference/rew-x.md +++ b/docs/reference/rew-x.md @@ -44,6 +44,14 @@ Default value: `cmd` on Windows, `sh` everywhere else. Can be also set using `SHELL` environment variable. +
-q, --quote
+
+ +Wrap output of every pattern expression in quotes + +Use the flag once for single quotes `''` or twice for double quotes `""`. +
+
--examples
@@ -410,6 +418,52 @@ rew x '{seq}:\n\t{}' +You can enable automatic expression quoting using `-q, --quote` flag. + +```sh +rew x -q 'mv {} {lower | tr " " "_"}' +``` + +
+
+stdin: +
    +
  • IMG 1.jpg
  • +
  • IMG 2.jpg
  • +
+
+
+stdout: +
    +
  • mv 'IMG 1.jpg' 'img_1.jpg'
  • +
  • mv 'IMG 2.jpg' 'img_2.jpg'
  • +
+
+
+ +Double the `-q, --quote` to use double quotes instead of single quotes. + +```sh +rew x -qq 'mv {} {lower | tr " " "_"}' +``` + +
+
+stdin: +
    +
  • IMG 1.jpg
  • +
  • IMG 2.jpg
  • +
+
+
+stdout: +
    +
  • mv "IMG 1.jpg" "img_1.jpg"
  • +
  • mv "IMG 2.jpg" "img_2.jpg"
  • +
+
+
+ All global options `-0, --null`, `--buf-size` and `--buf-mode` are propagated to rew subcommands. Do not forget configure NUL separator manually for any external commands. ```sh diff --git a/src/commands/x.rs b/src/commands/x.rs index c36c4a5..843faf0 100644 --- a/src/commands/x.rs +++ b/src/commands/x.rs @@ -28,6 +28,7 @@ use anyhow::Error; use anyhow::Result; use bstr::ByteVec; use clap::crate_name; +use clap::ArgAction; use std::env; use std::io; use std::io::Write; @@ -127,6 +128,16 @@ pub const META: Meta = command_meta! { input: &["first", "second", "third"], output: &["1:", "\tfirst", "2:", "\tsecond", "3:", "\tthird"], }, + "You can enable automatic expression quoting using `-q, --quote` flag.": { + args: &["-q", "mv {} {lower | tr ' ' '_'}"], + input: &["IMG 1.jpg", "IMG 2.jpg"], + output: &["mv 'IMG 1.jpg' 'img_1.jpg'", "mv 'IMG 2.jpg' 'img_2.jpg'"], + }, + "Double the `-q, --quote` to use double quotes instead of single quotes.": { + args: &["-qq", "mv {} {lower | tr ' ' '_'}"], + input: &["IMG 1.jpg", "IMG 2.jpg"], + output: &["mv \"IMG 1.jpg\" \"img_1.jpg\"", "mv \"IMG 2.jpg\" \"img_2.jpg\""], + }, "All global options `-0, --null`, `--buf-size` and `--buf-mode` are propagated to rew subcommands. \ Do not forget configure NUL separator manually for any external commands.": { args: &["--null", "{upper | sed --null-data 's/^.//g'}"], @@ -157,11 +168,22 @@ struct Args { /// Default value: `cmd` on Windows, `sh` everywhere else. #[arg(short, long, env = "SHELL")] shell: Option, + + /// Wrap output of every pattern expression in quotes + /// + /// Use the flag once for single quotes `''` or twice for double quotes `""`. + #[clap(short, long, action = ArgAction::Count)] + pub quote: u8, } fn run(context: &Context, args: &Args) -> Result<()> { let raw_pattern = args.pattern.join(" "); - let pattern = Pattern::parse(&raw_pattern, args.escape)?; + let mut pattern = Pattern::parse(&raw_pattern, args.escape)?; + + if args.quote > 0 { + let quote = if args.quote > 1 { '"' } else { '\'' }; + pattern = pattern.quote_expressions(quote); + } if let Some(pattern) = pattern.try_simplify() { return eval_simple_pattern(context, &pattern); diff --git a/src/pattern.rs b/src/pattern.rs index 8c389d5..6e400a5 100644 --- a/src/pattern.rs +++ b/src/pattern.rs @@ -2,6 +2,7 @@ use crate::colors::BOLD_RED; use crate::colors::RESET; use crate::colors::YELLOW; use derive_more::Display; +use derive_more::IsVariant; use std::fmt; use std::fmt::Debug; use std::fmt::Formatter; @@ -74,7 +75,7 @@ pub struct Pattern(Vec); pub struct SimplePattern(Vec); -#[derive(Debug, Display, Clone, PartialEq)] +#[derive(Debug, Display, Clone, PartialEq, IsVariant)] pub enum Item { Constant(String), Expression(Expression), @@ -106,7 +107,7 @@ pub struct Command { } impl Pattern { - pub fn parse(input: &str, escape: char) -> Result { + pub fn parse(input: &str, escape: char) -> Result { Parser::new(input, escape).parse().map(Self) } @@ -114,6 +115,23 @@ impl Pattern { &self.0 } + #[must_use] + pub fn quote_expressions(self, quote: char) -> Self { + let mut items = Vec::new(); + + for item in self.0 { + if item.is_expression() { + items.push(Item::Constant(quote.to_string())); + items.push(item); + items.push(Item::Constant(quote.to_string())); + } else { + items.push(item); + } + } + + Self(items) + } + pub fn try_simplify(&self) -> Option { let mut simple_items = Vec::new(); @@ -632,6 +650,17 @@ mod tests { } } + #[rstest] + #[case("", "")] + #[case("{}", "'{}'")] + #[case("{}{}", "'{}''{}'")] + #[case(" {} {} ", " '{}' '{}' ")] + #[case(" {n1} {n2 a21 | n3 a31 a32} ", " '{`n1`}' '{`n2` `a21`|`n3` `a31` `a32`}' ")] + fn quote(#[case] input: &str, #[case] normalized: &str) { + let pattern = assert_ok!(Pattern::parse(input, '%')); + assert_eq!(pattern.quote_expressions('\'').to_string(), normalized); + } + #[test] fn err_display() { let error = Error { diff --git a/tests/commands/x.rs b/tests/commands/x.rs index 3f3f9cf..018196f 100644 --- a/tests/commands/x.rs +++ b/tests/commands/x.rs @@ -37,4 +37,6 @@ command_test!("x", { // Should not get stuck by pipeline command not reading its stdin cat_and_generator_many: [ sh "seq 1 100000 | %cmd% {} {seq 1..100000} | cksum" assert "" => "774411998 1177790\n" ], shell_and_generator_many: [ sh "seq 1 100000 | %cmd% {} {:# seq 1 100000} | cksum" assert "" => "774411998 1177790\n" ], + single_quote: [ cmd "-q" "mv {} {lower | tr ' ' '_'}" assert "My Notes.txt" => "mv 'My Notes.txt' 'my_notes.txt'\n" ], + double_quote: [ cmd "-qq" "mv {} {lower | tr ' ' '_'}" assert "My Notes.txt" => "mv \"My Notes.txt\" \"my_notes.txt\"\n" ], });