Skip to content

Commit

Permalink
feature: remove '$' requirement to start anchor (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
solidiquis authored Jan 20, 2025
1 parent 77a88c9 commit 953994d
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 93 deletions.
31 changes: 18 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ The following example demonstrates how to apply `grits` to `tcpdump` to extract
sudo tcpdump -nn | grits -p '^(?<ts>[^ ]+)' \
-p 'IP\w? (?<src>[^ ]+)' \
-p '> (?<dst>[^ ]+):' \
-t '[${(cyan|bold):ts}] ${(green|underlined):"src"}=${src} ${(yellow|underlined):"dst"}=${dst}'
-t '[{(cyan|bold):ts}] {(green|underlined):"src"}={src} {(yellow|underlined):"dst"}={dst}'
```

![demo image](images/demo.png)
Expand Down Expand Up @@ -85,17 +85,17 @@ Check the [releases page](https://github.com/solidiquis/grits/releases) for preb
## Templating language

`grits` uses a simple templating language to transform text, where templates consist of anchors.
Anchors are placeholders enclosed within `${...}` that correspond to named capture groups from
Anchors are placeholders enclosed within `{...}` that correspond to named capture groups from
the regular expression applied to the input. Once a match is found, the value from the
capture group is inserted into the anchor’s position in the template string.

Here's an example:
```bash
echo 'level=info msg=foobar path=/baz' | grit -p 'msg=(?<log>[^ ]+)' -o 'transformed=${log}'
echo 'level=info msg=foobar path=/baz' | grit -p 'msg=(?<log>[^ ]+)' -t 'transformed={log}'
```

In this command, we use a regular expression to capture the value associated with the msg field.
The capture group is named `log`. The template string `transformed=${log}` will replace `${log}` with
The capture group is named `log`. The template string `transformed={log}` will replace `{log}` with
the value captured from the input. The output will then be:

```
Expand All @@ -104,7 +104,7 @@ transformed=foobar

To summarize:
- The regular expression `msg=(?<log>[^ ]+)` captures the value `foobar` into the `log` capture group.
- The template `transformed=${log}` uses the value of `log` to generate the output.
- The template `transformed={log}` uses the value of `log` to generate the output.

The following are additional features of `grits` templating system:

Expand All @@ -116,7 +116,7 @@ immediately after the anchor name. For example, to access the second match of t
capture group, you would use:

```
${log[1]}
{log[1]}
```

### Default values
Expand All @@ -125,7 +125,7 @@ If a particular anchor doesn't have an associated match, default values can be c
operator like so:

```
${log || foo || bar[1] || "NO MATCH"}
{log || foo || bar[1] || "NO MATCH"}
```

The first default value that doesn't produce a blank string will be used. Default values can be
Expand All @@ -136,14 +136,14 @@ other anchors or a string literal.
Attributes offer additional means to transform text. Attributes are applied to anchors like so:

```
${(red|bold):ipaddr_v4}
{(red|bold):ipaddr_v4}
```

Here is an example using attributes with default values:


```
${(red|bold):ipaddr_v4 || ipaddr_v6 || "NOMATCH"}
{(red|bold):ipaddr_v4 || ipaddr_v6 || "NOMATCH"}
```

In the above example, `red` and `bold` will be applied the entire anchor.
Expand Down Expand Up @@ -177,13 +177,13 @@ The following attributes are currently available:
1. Multi-file processing:

```bash
grits -p 'sysctl=(?<sysctl>.*)'` -p 'sysctl output: ${sysctl}' file1 file2
grits -p 'sysctl=(?<sysctl>.*)'` -t 'sysctl output: {sysctl}' file1 file2
```

2. Piping:

```bash
docker logs -f 93670ea0964c | grits -p 'log_level=info(?<log>.*)' -o 'INFO LOG: ${log}'
docker logs -f 93670ea0964c | grits -p 'log_level=info(?<log>.*)' -t 'INFO LOG: {log}'
```

3. Attributes, default values, and multiple regular expressions:
Expand All @@ -192,10 +192,10 @@ docker logs -f 93670ea0964c | grits -p 'log_level=info(?<log>.*)' -o 'INFO LOG:
kubectl logs -f -n foo -l app=bar | grits \
-p '^kernel:(?<kern>.*)' \
-p '^sysctl:(?<sys>.*)' \
-o kernel=${(cyan):kern || \"NONE\"} sysctl=${(magenta):sys || \"NONE\"}
-t 'kernel={(cyan):kern || \"NONE\"} sysctl={(magenta):sys || \"NONE\"}'
```

### Completions
## Completions

Completions for supported shells can be generated using `grits --completions <SHELl>`. Consult your shell's documentation
for how to setup completions. For `zsh`, completions are bootstrapped like so:
Expand All @@ -204,6 +204,11 @@ for how to setup completions. For `zsh`, completions are bootstrapped like so:
grits --completions zsh > ~/.oh-my-zsh/completions/_grits
```
## Colorization
`grits` follows the informal [NO_COLOR](https://no-color.org/) standard. Setting `NO_COLOR` to a non-blank value will disable output colorization.
If stdout is not a terminal, colorization is automatically disabled.
## Contributing
All well-intentioned forms of contributions are welcome.
Expand Down
30 changes: 15 additions & 15 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ memory:
The remaining memory is stored in a capture called 'mem'. Now to transform the input line into
our desired output, we'll use the following template string:
Remaining memory: ${mem}
Remaining memory: {mem}
Putting everything together the following command:
$ echo $LINE | grits -p 'Memory: (?<mem>[^ ]+)' -o 'Remaining memory: ${mem}'
$ echo $LINE | grits -p 'Memory: (?<mem>[^ ]+)' -o 'Remaining memory: {mem}'
Gets us the following output:
Expand All @@ -58,7 +58,7 @@ Colorization is disabled under two circumstances:
An example of how to disable color:
$ NO_COLOR=1 grits -p 'Memory: (?<mem>[^ ]+)' -o 'Remaining memory: ${mem}' file
$ NO_COLOR=1 grits -p 'Memory: (?<mem>[^ ]+)' -o 'Remaining memory: {mem}' file
------------------------
----OUTPUT TEMPLATE-----
Expand All @@ -67,14 +67,14 @@ The output template which is specified using `-o, --output-template` is a format
that defines how to transform a line input and ultimately produce an output. The output
template contains constructs called anchors which looks like the following:
${log}
{log}
This anchor, which begins with '$' and ends with '}', is a reference to a named capture
This anchor, which begins with '{' and ends with '}', is a reference to a named capture
from a regular expression that a user provides. If, for a given input line, there is a
match for the `log` capture, then the value associated with the `log` capture will be used
to interpolate the output string at the `log` anchor. For example:
$ echo 'level=info msg=foobar path=/baz' | grit -p 'msg=(?<log>[^ ]+)' -o 'transformed=${log}'
$ echo 'level=info msg=foobar path=/baz' | grit -p 'msg=(?<log>[^ ]+)' -o 'transformed={log}'
Will get you the following output:
Expand All @@ -89,27 +89,27 @@ Anchors come with a handful of other features as well:
2. Chain default values, which can be another anchor or a string literal. The first non-blank
value will be used in the output (ultimately falls back to an empty string):
${log || foo || bar[1] || \"NO MATCH\"}
{log || foo || bar[1] || \"NO MATCH\"}
3. Apply to an anchor like so:
${(red|bold):foo}
{(red|bold):foo}
4. Apply ANSI-escape sequences to literals:
${(cyan|underlined):\"foo\"}
{(cyan|underlined):\"foo\"}
5. Escape characters with special meaning such as '$' with '\\':
5. Escape characters with special meaning such as '{' with '\\':
USD: \\$${amount}
USD: \\{foo\\} {amount}
----------------
---ATTRIBUTES---
----------------
As mentioned in the section prior, attributes are available to stylize the result of processing
the anchors. Multiple attributes may be used together like so:
${(red|bold|underlined):foo}
{(red|bold|underlined):foo}
The following attributes are currently available:
Expand Down Expand Up @@ -139,18 +139,18 @@ The following attributes are currently available:
----------------
1. Multi-file processing:
$ grits -p 'sysctl=(?<sysctl>.*)'` -p 'sysctl output: ${sysctl}' file1 file2
$ grits -p 'sysctl=(?<sysctl>.*)'` -p 'sysctl output: {sysctl}' file1 file2
2. Piping:
$ docker logs -f 93670ea0964c | grits -p 'log_level=info(?<log>.*)' -o 'INFO LOG: ${log}'
$ docker logs -f 93670ea0964c | grits -p 'log_level=info(?<log>.*)' -o 'INFO LOG: {log}'
3. Attributes, default values, and multiple regular expressions:
$ kubectl logs -f -n foo -l app=bar | grits \\
-p '^kernel:(?<kern>.*)' \\
-p '^sysctl:(?<sys>.*)' \\
-o kernel=${(cyan):kern || \"NONE\"} sysctl=${(magenta):sys || \"NONE\"}
-o kernel=${(cyan):kern || \"NONE\"} sysctl={(magenta):sys || \"NONE\"}
"
)]
pub struct Cli {
Expand Down
10 changes: 1 addition & 9 deletions src/template/error.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::{
parse::rules::VALID_ANCHOR_CHARSET,
token::{ANCHOR, ANCHOR_CLOSE, ANCHOR_OPEN, ATTRIBUTE_CLOSE, ATTRIBUTE_END, ESCAPE},
token::{ANCHOR_CLOSE, ATTRIBUTE_CLOSE, ATTRIBUTE_END, ESCAPE},
};
use indoc::{formatdoc, indoc};
use std::fmt::{self, Display};
Expand Down Expand Up @@ -48,14 +48,6 @@ impl ParseError {
}
}

pub fn invalid_anchor_start(char_index: usize, chars: &[char]) -> Self {
ParseError {
char_index,
partial_template: chars.iter().collect(),
message: format!("Expected '{ANCHOR_OPEN}' to immediately follow '{ANCHOR}'."),
}
}

pub fn invalid_anchor_name(char_index: usize, chars: &[char]) -> Self {
ParseError {
char_index,
Expand Down
13 changes: 4 additions & 9 deletions src/template/parse/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use super::{
error::ParseError,
token::{
ANCHOR, ANCHOR_CLOSE, ANCHOR_OPEN, ATTRIBUTE_CLOSE, ATTRIBUTE_DELIMETER, ATTRIBUTE_END, ATTRIBUTE_OPEN,
DEFAULT_PIPE, ESCAPE, INDEX_CLOSE, INDEX_OPEN, LITERAL_DOUBLE_QUOTE, LITERAL_SINGLE_QUOTE,
ANCHOR_CLOSE, ANCHOR_OPEN, ATTRIBUTE_CLOSE, ATTRIBUTE_DELIMETER, ATTRIBUTE_END, ATTRIBUTE_OPEN, DEFAULT_PIPE,
ESCAPE, INDEX_CLOSE, INDEX_OPEN, LITERAL_DOUBLE_QUOTE, LITERAL_SINGLE_QUOTE,
},
};
use anyhow::{format_err, Result};
Expand Down Expand Up @@ -63,7 +63,7 @@ enum ParseStateMode {
/// Encountered an escape character which will cause the next token to be treated as a
/// non-special character.
Escaping,
/// Encountered `$` which begins the anchor.
/// Encountered `{` which begins the anchor.
AnchorBegin,
/// Parse anchor name
AnchorParseBase,
Expand Down Expand Up @@ -121,7 +121,7 @@ fn parse_impl(mode: &mut ParseState, anchors: &mut Vec<Anchor>, rules: &Rules) -
if ch == ESCAPE {
mode.mode = ParseStateMode::Escaping;
return parse_impl(mode, anchors, rules);
} else if ch == ANCHOR {
} else if ch == ANCHOR_OPEN {
mode.mode = ParseStateMode::AnchorBegin;
mode.bound_anchor = Some(Anchor {
start: mode.cursor,
Expand All @@ -145,11 +145,6 @@ fn parse_impl(mode: &mut ParseState, anchors: &mut Vec<Anchor>, rules: &Rules) -

ParseStateMode::AnchorBegin => {
mode.cursor += 1;
if mode.tokens.get(mode.cursor).is_none_or(|ch| *ch != ANCHOR_OPEN) {
return Err(ParseError::invalid_anchor_start(mode.cursor - 1, &mode.tokens).into());
}
mode.cursor += 1;

for i in mode.cursor..mode.tokens.len() {
if mode.tokens.get(mode.cursor).is_some_and(|ch| ch.is_ascii_whitespace()) {
mode.cursor = i;
Expand Down
Loading

0 comments on commit 953994d

Please sign in to comment.