Skip to content

Commit

Permalink
Implement tag printing.
Browse files Browse the repository at this point in the history
This commit adds a -t argument to the scan command which will print the tags of
matching rules.

One thing I realized (and this is also true for metadata) is that if there is no
tags we are still including them in the JSON (just as an empty list). When we
print in text mode we have checks if the tags are empty and skipping them if so.

I'm open to changing the JSON output so that we do not include a "meta" and
"tags" key if they are empty.
  • Loading branch information
wxsBSD committed Aug 3, 2024
1 parent 7332a7b commit adac3e6
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 0 deletions.
25 changes: 25 additions & 0 deletions cli/src/commands/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ 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")
Expand Down Expand Up @@ -355,6 +359,7 @@ fn print_rules_as_json(
output: &Sender<Message>,
) {
let print_namespace = args.get_flag("print-namespace");
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 Down Expand Up @@ -384,6 +389,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 +458,7 @@ fn print_rules_as_text(
output: &Sender<Message>,
) {
let print_namespace = args.get_flag("print-namespace");
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 @@ -466,6 +478,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(&format!("{}", 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: 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 adac3e6

Please sign in to comment.