Skip to content

Commit

Permalink
Support x quote flag
Browse files Browse the repository at this point in the history
  • Loading branch information
jpikl committed Mar 29, 2024
1 parent b2ca270 commit 13939c9
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 5 deletions.
3 changes: 1 addition & 2 deletions TODO.txt
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
54 changes: 54 additions & 0 deletions docs/reference/rew-x.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ Default value: `cmd` on Windows, `sh` everywhere else.
Can be also set using `SHELL` environment variable.
</dd>

<dt><code>-q, --quote</code></dt>
<dd>

Wrap output of every pattern expression in quotes

Use the flag once for single quotes `''` or twice for double quotes `""`.
</dd>

<dt><code>--examples</code></dt>
<dd>

Expand Down Expand Up @@ -410,6 +418,52 @@ rew x '{seq}:\n\t{}'
</div>
</div>

You can enable automatic expression quoting using `-q, --quote` flag.

```sh
rew x -q 'mv {} {lower | tr " " "_"}'
```

<div class="example-io">
<div class="example-io-stream">
<small><b>stdin:</b></small>
<ul>
<li><code>IMG 1.jpg</code></li>
<li><code>IMG 2.jpg</code></li>
</ul>
</div>
<div class="example-io-stream">
<small><b>stdout:</b></small>
<ul>
<li><code>mv 'IMG 1.jpg' 'img_1.jpg'</code></li>
<li><code>mv 'IMG 2.jpg' 'img_2.jpg'</code></li>
</ul>
</div>
</div>

Double the `-q, --quote` to use double quotes instead of single quotes.

```sh
rew x -qq 'mv {} {lower | tr " " "_"}'
```

<div class="example-io">
<div class="example-io-stream">
<small><b>stdin:</b></small>
<ul>
<li><code>IMG 1.jpg</code></li>
<li><code>IMG 2.jpg</code></li>
</ul>
</div>
<div class="example-io-stream">
<small><b>stdout:</b></small>
<ul>
<li><code>mv "IMG 1.jpg" "img_1.jpg"</code></li>
<li><code>mv "IMG 2.jpg" "img_2.jpg"</code></li>
</ul>
</div>
</div>

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
Expand Down
24 changes: 23 additions & 1 deletion src/commands/x.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'}"],
Expand Down Expand Up @@ -157,11 +168,22 @@ struct Args {
/// Default value: `cmd` on Windows, `sh` everywhere else.
#[arg(short, long, env = "SHELL")]
shell: Option<String>,

/// 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);
Expand Down
33 changes: 31 additions & 2 deletions src/pattern.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -74,7 +75,7 @@ pub struct Pattern(Vec<Item>);

pub struct SimplePattern(Vec<SimpleItem>);

#[derive(Debug, Display, Clone, PartialEq)]
#[derive(Debug, Display, Clone, PartialEq, IsVariant)]
pub enum Item {
Constant(String),
Expression(Expression),
Expand Down Expand Up @@ -106,14 +107,31 @@ pub struct Command {
}

impl Pattern {
pub fn parse(input: &str, escape: char) -> Result<Pattern> {
pub fn parse(input: &str, escape: char) -> Result<Self> {
Parser::new(input, escape).parse().map(Self)
}

pub fn items(&self) -> &Vec<Item> {
&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<SimplePattern> {
let mut simple_items = Vec::new();

Expand Down Expand Up @@ -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 {
Expand Down
2 changes: 2 additions & 0 deletions tests/commands/x.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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" ],
});

0 comments on commit 13939c9

Please sign in to comment.