Skip to content

Commit

Permalink
feat: implement tag printing and filtering (#171)
Browse files Browse the repository at this point in the history
This commit adds the `-g` and `-t` arguments to the scan command. The `-g` arguments prints the tags associated to the matching rules, while the `-t` arguments allows filtering the results by tag name.

---------

Co-authored-by: Victor M. Alvarez <[email protected]>
  • Loading branch information
wxsBSD and plusvic authored Aug 5, 2024
1 parent 7332a7b commit 0ee4100
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 0 deletions.
49 changes: 49 additions & 0 deletions cli/src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,20 @@ pub fn scan() -> Command {
arg!(-m --"print-meta")
.help("Print rule metadata")
)
.arg(
arg!(-g --"print-tags")
.help("Print rule tags")
)
.arg(
arg!(--"disable-console-logs")
.help("Disable printing console log messages")
)
.arg(
arg!(-t --"tag" <TAG>)
.help("Print only rules tagged as TAG")
.required(false)
.value_parser(value_parser!(String))
)
.arg(
arg!(-n --"negate")
.help("Print non-satisfied rules only")
Expand Down Expand Up @@ -355,6 +365,8 @@ fn print_rules_as_json(
output: &Sender<Message>,
) {
let print_namespace = args.get_flag("print-namespace");
let only_tag = args.get_one::<String>("tag");
let print_tags = args.get_flag("print-tags");
let print_meta = args.get_flag("print-meta");
let print_strings = args.get_flag("print-strings");
let print_strings_limit = args.get_one::<usize>("print-strings-limit");
Expand All @@ -369,6 +381,14 @@ fn print_rules_as_json(
// `the `by_ref` method cannot be invoked on a trait object`
#[allow(clippy::while_let_on_iterator)]
while let Some(matching_rule) = rules.next() {
if only_tag.is_some()
&& !matching_rule
.tags()
.any(|t| t.identifier() == only_tag.unwrap())
{
return;
}

let mut json_rule = if print_namespace {
serde_json::json!({
"namespace": matching_rule.namespace(),
Expand All @@ -384,6 +404,12 @@ fn print_rules_as_json(
json_rule["meta"] = matching_rule.metadata().into_json();
}

if print_tags {
let tags: Vec<&str> =
matching_rule.tags().map(|t| t.identifier()).collect();
json_rule["tags"] = serde_json::json!(tags);
}

if print_strings || print_strings_limit.is_some() {
let limit = print_strings_limit.unwrap_or(&STRINGS_LIMIT);
let mut match_vec: Vec<serde_json::Value> = Vec::new();
Expand Down Expand Up @@ -447,6 +473,8 @@ fn print_rules_as_text(
output: &Sender<Message>,
) {
let print_namespace = args.get_flag("print-namespace");
let only_tag = args.get_one::<String>("tag");
let print_tags = args.get_flag("print-tags");
let print_meta = args.get_flag("print-meta");
let print_strings = args.get_flag("print-strings");
let print_strings_limit = args.get_one::<usize>("print-strings-limit");
Expand All @@ -456,6 +484,14 @@ fn print_rules_as_text(
// `the `by_ref` method cannot be invoked on a trait object`
#[allow(clippy::while_let_on_iterator)]
while let Some(matching_rule) = rules.next() {
if only_tag.is_some()
&& !matching_rule
.tags()
.any(|t| t.identifier() == only_tag.unwrap())
{
return;
}

let mut line = if print_namespace {
format!(
"{}:{}",
Expand All @@ -466,6 +502,19 @@ fn print_rules_as_text(
format!("{}", matching_rule.identifier().paint(Cyan).bold())
};

let tags = matching_rule.tags();

if print_tags && !tags.is_empty() {
line.push_str(" [");
for (pos, tag) in tags.with_position() {
line.push_str(tag.identifier());
if !matches!(pos, itertools::Position::Last) {
line.push(',');
}
}
line.push(']');
}

let metadata = matching_rule.metadata();

if print_meta && !metadata.is_empty() {
Expand Down
8 changes: 8 additions & 0 deletions lib/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,13 @@ impl<'a> Compiler<'a> {
self.check_for_duplicate_tags(tags.as_slice())?;
}

let tags: Vec<IdentId> = rule
.tags
.iter()
.flatten()
.map(|t| self.ident_pool.get_or_intern(t.name))
.collect();

// Take snapshot of the current compiler state. In case of error
// compiling the current rule this snapshot allows restoring the
// compiler to the state it had before starting compiling the rule.
Expand Down Expand Up @@ -966,6 +973,7 @@ impl<'a> Compiler<'a> {
self.report_builder.current_source_id(),
rule.identifier.span(),
),
tags,
patterns: vec![],
is_global: rule.flags.contains(RuleFlag::Global),
is_private: rule.flags.contains(RuleFlag::Private),
Expand Down
2 changes: 2 additions & 0 deletions lib/src/compiler/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,8 @@ pub(crate) struct RuleInfo {
pub(crate) namespace_ident_id: IdentId,
/// The ID of the rule identifier in the identifiers pool.
pub(crate) ident_id: IdentId,
/// Tags associated to the rule.
pub(crate) tags: Vec<IdentId>,
/// Reference to the rule identifier in the source code. This field is
/// ignored while serializing and deserializing compiles rules, as it
/// is used only during the compilation phase, but not during the scan
Expand Down
53 changes: 53 additions & 0 deletions lib/src/scanner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,15 @@ impl<'a, 'r> Rule<'a, 'r> {
}
}

/// Returns the tags associated to this rule.
pub fn tags(&self) -> Tags<'a, 'r> {
Tags {
ctx: self.ctx,
iterator: self.rule_info.tags.iter(),
len: self.rule_info.tags.len(),
}
}

/// Returns the patterns defined by this rule.
pub fn patterns(&self) -> Patterns<'a, 'r> {
Patterns {
Expand Down Expand Up @@ -1091,6 +1100,50 @@ impl<'a, 'r> ExactSizeIterator for Metadata<'a, 'r> {
}
}

/// An iterator that returns the tags defined by a rule.
pub struct Tags<'a, 'r> {
ctx: &'a ScanContext<'r>,
iterator: Iter<'a, IdentId>,
len: usize,
}

impl<'a, 'r> Tags<'a, 'r> {
/// Returns `true` if the rule doesn't have any tags.
#[inline]
pub fn is_empty(&self) -> bool {
self.iterator.len() == 0
}
}

impl<'a, 'r> Iterator for Tags<'a, 'r> {
type Item = Tag<'a, 'r>;

fn next(&mut self) -> Option<Self::Item> {
let ident_id = self.iterator.next()?;
Some(Tag { ctx: self.ctx, ident_id: *ident_id })
}
}

impl<'a, 'r> ExactSizeIterator for Tags<'a, 'r> {
#[inline]
fn len(&self) -> usize {
self.len
}
}

/// Represents a tag defined by a rule.
pub struct Tag<'a, 'r> {
ctx: &'a ScanContext<'r>,
ident_id: IdentId,
}

impl<'a, 'r> Tag<'a, 'r> {
/// Returns the tag's identifier.
pub fn identifier(&self) -> &'r str {
self.ctx.compiled_rules.ident_pool().get(self.ident_id).unwrap()
}
}

/// An iterator that returns the patterns defined by a rule.
pub struct Patterns<'a, 'r> {
ctx: &'a ScanContext<'r>,
Expand Down

0 comments on commit 0ee4100

Please sign in to comment.