#!/usr/bin/perl # # (c) 2019-2020 Vitaly Parnas # See LICENSE for licensing information. use strict; use warnings; use 5.010; use File::Basename; use Data::Dumper; use Term::ANSIColor qw(:constants); my $DELIM = ';'; my $CONF_FILE = basename($0) . '.conf'; my $COLOR_FILE = YELLOW; #my $COLOR_FILE = BRIGHT_MAGENTA; my $COLOR_PATTERN = BRIGHT_CYAN; my $DEBUG = 0; sub usage () { say " Usage: " . basename($0) . " [options] Options: [-h|--help] - print this message. [-d|--debug] - debug mode, default: no. [-n|--no-act] - display search command, do not execute. [-c|--count] - display counts of search results of each file only. [-r|--recursive] - recurse into directories. [-s|--suppress-err] - ignore missing files. "; exit(1); } sub read_ini { my $ini = shift; my $config; my $section; open(INI, '<', $ini) or die "Could not open file '$ini' $!\n"; while () { chomp; next if /^(#|;|\s*$)/; # comments, blanks next if s/^\[(.*)\]/$section = $1/sie; # new config section /^([^#;]+)\s*=\s*([^#]+)\s*/ and $config->{$section}->{$1} = $2; } close INI; return $config; } sub parse_free_args { my ($conf, $free_args, $searches, $paths) = @_; foreach (@$free_args) { my $target; my $val = $conf->{"content"}->{$_}; if ($val) { $target = $paths; } else { $val = ($conf->{"terms"}->{$_} or $_); $target = $searches; } push (@$target, split(/$DELIM/, $val)); } say "Searches: @$searches" if $DEBUG; say "Paths @$paths" if $DEBUG; } sub exec_pretty_search { my ($cmd, $search_str) = @_; $_ = qx($cmd); s/^[^\s:]+:/sprintf($COLOR_FILE . "%s" . RESET, $&)/egm; s/$search_str/sprintf($COLOR_PATTERN . "%s" . RESET, $&)/eig; s#$ENV{HOME}#~#g; # strip verbose $HOME path from results say; } # MAIN { my $conf; my (@free_args, @searches, @paths, @grep_options); my ($no_act, $count_matches, $recurse, $min_count, $suppress_err) = (0,0,0,1,0); my ($search_str, $cmd); while ($_ = shift) { if (/^(-h|--help)/) { usage(); } elsif (/^(-d|--debug)/) { $DEBUG = 1; } elsif (/^(-n|--no-act)/) { $no_act = 1; } elsif (/^(-c|--count)/) { $count_matches = 1; } elsif (/^(-r|--recursive)/) { $recurse = 1;} elsif (/^(-s|--suppress-err)/) { $suppress_err = 1;} else { push (@free_args, $_); } } usage() unless (@free_args); push (@grep_options, $recurse ? "-r" : "--exclude-dir=/*"); push (@grep_options, "--no-messages") if $suppress_err; foreach (".$CONF_FILE", "$ENV{HOME}/.$CONF_FILE", "$ENV{HOME}/.config/$CONF_FILE") { if (-e) { say "Using conf $_"; $conf = read_ini($_); last; } } $conf or die "Could not find or open $CONF_FILE\n"; #print Dumper($conf) if $DEBUG; parse_free_args($conf, \@free_args, \@searches, \@paths); while (my ($filter_name, $val) = each %{$conf->{"filters"}}) { ($filter_name eq "min-count") and $min_count = $val and next; foreach (split(/$DELIM/, $val)) { push (@grep_options, "--$filter_name '$_'"); } } $count_matches and unshift (@grep_options, "-cH"); $search_str = join('|', @searches); $cmd = "grep -Ei @grep_options -e '$search_str' @paths"; if ($count_matches) { $cmd .= " | awk -F':' '{if (\$2 >= $min_count) " . "printf(\"%4d : %s\\n\", \$2, \$1)}' | sort -n"; } $no_act ? say($cmd) : exec_pretty_search($cmd, $search_str); exit(0); }