1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
#!/usr/bin/perl
#
# (c) 2019-2020 Vitaly Parnas <vp330@parnas.me>
# 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] <search terms and/or tags> <source(s)>
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 (<INI>) {
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);
}
|