| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
=encoding utf8 |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
Query::Tags - Raku-inspired query language for attributes |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
use Query::Tags; |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
# Select all @books for which the 'title' field matches the regex /Perl/. |
|
12
|
|
|
|
|
|
|
my $q = Query::Tags->new(q[:title/Perl/]); |
|
13
|
|
|
|
|
|
|
my @shelf = $q->select(@books); |
|
14
|
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
=head2 VERSION |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
This document describes v0.0.3 of Query::Tags. |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
=cut |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
package Query::Tags v0.0.3; |
|
22
|
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
Query::Tags implements a simple query language for stringy object attributes. |
|
26
|
|
|
|
|
|
|
Its main features are: |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=over |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=item Attribute syntax |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
C<< :key(value) >> designates that an object should have a field or method |
|
33
|
|
|
|
|
|
|
named C<< key >> whose value should match C<< value >>. If C<< value >> is |
|
34
|
|
|
|
|
|
|
missing (is C), the key or field should exist. |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=item Regular expressions |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
Perl regular expressions are fully supported. |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
=item Junctions |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
Simple logic operations on queries is supported in the form of junctions |
|
43
|
|
|
|
|
|
|
(as in Raku). For example, C<< :title! >> matches |
|
44
|
|
|
|
|
|
|
all books whose C field matches neither C nor makes an |
|
45
|
|
|
|
|
|
|
C. |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
=item Pegex grammar |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
The language is specified using a L grammar which means that it can |
|
50
|
|
|
|
|
|
|
be easily changed and extended. You can also supply your own L |
|
51
|
|
|
|
|
|
|
to the Pegex parser engine, for instance to compile a Query::Tags query to SQL. |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=back |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
This feature set allows for reasonably flexible filtering of tagged, unstructured |
|
56
|
|
|
|
|
|
|
data (think of email headers). They also allow for a straightforward query syntax |
|
57
|
|
|
|
|
|
|
and quick parsing (discussed in detail below). |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
It does not support: |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=over |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
=item Nested data structures |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
There is no way to match values inside a list, for example. |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=item Types |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
There is no type information. All matching is string-based. There are no |
|
70
|
|
|
|
|
|
|
operators for comparing numbers, dates or ranges (but they could be added |
|
71
|
|
|
|
|
|
|
without too much work). |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=item Complex logic |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Junctions provide only a limited means for using logical connectives with |
|
76
|
|
|
|
|
|
|
query assertions. You I specify "all books whose title is X or Y" but |
|
77
|
|
|
|
|
|
|
you I specify "all books whose title is X or whose author is Y". |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=back |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=cut |
|
82
|
|
|
|
|
|
|
|
|
83
|
2
|
|
|
2
|
|
290274
|
use v5.16; |
|
|
2
|
|
|
|
|
8
|
|
|
84
|
2
|
|
|
2
|
|
16
|
use strict; |
|
|
2
|
|
|
|
|
31
|
|
|
|
2
|
|
|
|
|
111
|
|
|
85
|
2
|
|
|
2
|
|
13
|
use warnings; |
|
|
2
|
|
|
|
|
16
|
|
|
|
2
|
|
|
|
|
188
|
|
|
86
|
|
|
|
|
|
|
|
|
87
|
2
|
|
|
2
|
|
1321
|
use Pegex; |
|
|
2
|
|
|
|
|
36689
|
|
|
|
2
|
|
|
|
|
177
|
|
|
88
|
2
|
|
|
2
|
|
1187
|
use Query::Tags::Grammar; |
|
|
2
|
|
|
|
|
6
|
|
|
|
2
|
|
|
|
|
17
|
|
|
89
|
2
|
|
|
2
|
|
1055
|
use Query::Tags::To::AST; |
|
|
2
|
|
|
|
|
8
|
|
|
|
2
|
|
|
|
|
91
|
|
|
90
|
|
|
|
|
|
|
|
|
91
|
2
|
|
|
2
|
|
13
|
use Exporter qw(import); |
|
|
2
|
|
|
|
|
3
|
|
|
|
2
|
|
|
|
|
656
|
|
|
92
|
|
|
|
|
|
|
our @EXPORT_OK = qw(parse_query); |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
=head2 Methods |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
=head3 new |
|
97
|
|
|
|
|
|
|
|
|
98
|
|
|
|
|
|
|
my $q = Query::Tags->new($query_string, \%opts); |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
Parses the query string and creates a new query object. |
|
101
|
|
|
|
|
|
|
The query is internally represented by a syntax tree. |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
The optional argument C<\%opts> is a hashref containing |
|
104
|
|
|
|
|
|
|
options. Only one option is supported at the moment: |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
=over |
|
107
|
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=item B |
|
109
|
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Controls the matching of assertions in the query for |
|
111
|
|
|
|
|
|
|
pairs with an empty I part. If the given value is |
|
112
|
|
|
|
|
|
|
a CODEREF, it is invoked with each tested object together |
|
113
|
|
|
|
|
|
|
with the I part of an assertion (and the C<\%opts> |
|
114
|
|
|
|
|
|
|
hashref unaltered). It should return a truthy or falsy |
|
115
|
|
|
|
|
|
|
value depending on match. If the B value |
|
116
|
|
|
|
|
|
|
is not a CODEREF it is assumed to be a string and is |
|
117
|
|
|
|
|
|
|
used instead of any missing I in an assertion. |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
=back |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
=cut |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub new { |
|
124
|
11
|
|
|
11
|
1
|
168115
|
my ($class, $query, $opts) = @_; |
|
125
|
11
|
|
|
|
|
77
|
my $root = Pegex::Parser->new( |
|
126
|
|
|
|
|
|
|
grammar => Query::Tags::Grammar->new, |
|
127
|
|
|
|
|
|
|
receiver => Query::Tags::To::AST->new, |
|
128
|
|
|
|
|
|
|
)->parse($query); |
|
129
|
11
|
|
100
|
|
|
917
|
bless { query => $query, tree => $root, opts => $opts // +{ } }, $class |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head3 tree |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
my $root = $q->tree; |
|
135
|
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
Get the root of the underlying syntax tree representation. |
|
137
|
|
|
|
|
|
|
It is an object of class L. |
|
138
|
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
=cut |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
sub tree { |
|
142
|
|
|
|
|
|
|
$_[0]->{tree} |
|
143
|
1
|
|
|
1
|
1
|
10
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=head3 test |
|
146
|
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
$q->test($obj) ? 'PASS' : 'FAIL' |
|
148
|
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
Check if the given object passes all query assertions in C<$q>. |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=cut |
|
152
|
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub test { |
|
154
|
10
|
|
|
10
|
1
|
25
|
my ($self, $obj) = @_; |
|
155
|
|
|
|
|
|
|
$self->{tree}->test($obj, $self->{opts}) |
|
156
|
10
|
|
|
|
|
42
|
} |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
=head3 select |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
my @pass = $q->select(@objs); |
|
161
|
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
Return all objects which pass all query assertions in C<$q>. |
|
163
|
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=cut |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
sub select { |
|
167
|
0
|
|
|
0
|
1
|
0
|
my $self = shift; |
|
168
|
0
|
|
|
|
|
0
|
my $tree = $self->{tree}; |
|
169
|
0
|
|
|
|
|
0
|
grep { $tree->test($_, $self->{opts}) } @_ |
|
|
0
|
|
|
|
|
0
|
|
|
170
|
|
|
|
|
|
|
} |
|
171
|
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
=head2 Exports |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=head3 parse_query |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
my $q = parse_query($query_string); |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
Optional export which provides a more procedural interface |
|
179
|
|
|
|
|
|
|
to L of this package. |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=cut |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
sub parse_query { |
|
184
|
1
|
|
|
1
|
1
|
227209
|
__PACKAGE__->new(@_) |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=head2 Query syntax |
|
188
|
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
The query language is specified in a L grammar called C |
|
190
|
|
|
|
|
|
|
which is included in the distribution's C directory. See that file |
|
191
|
|
|
|
|
|
|
for detailed technical information. What follows is an overview of the |
|
192
|
|
|
|
|
|
|
language. |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
=over |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=item Query |
|
197
|
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
A C is represented by a L |
|
199
|
|
|
|
|
|
|
object. It contains a list of assertions in the form of C. |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
=item Pair |
|
202
|
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
A C consists of a I and a I. Keys are alphanumeric strings |
|
204
|
|
|
|
|
|
|
(beginning with an alphabetic character) also permitting the punctuation |
|
205
|
|
|
|
|
|
|
characters C<.>, C<-> and C<_>. The value can be a C, a C |
|
206
|
|
|
|
|
|
|
or a C. A pair starts with a colon C<:> and the value is written |
|
207
|
|
|
|
|
|
|
directly after the key. E.g., C<:key'value'> (for a string value), |
|
208
|
|
|
|
|
|
|
C<:key/value/> (for a regex value) or C<< :key& >> (for a |
|
209
|
|
|
|
|
|
|
junction value). The value is optional. |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
If instead of a value, the question mark character C> appears, the |
|
212
|
|
|
|
|
|
|
I field must exist and its value must be truthy. |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=item Quoted string |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
A C is a string delimited by single quotes C<'>. |
|
217
|
|
|
|
|
|
|
E.g., C<'Perl'> but B C or C<"Perl"> or C<< «Perl» >>. |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
=item Regex |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
A C is a standard Perl regex, as usual between a pair of slahes C> |
|
222
|
|
|
|
|
|
|
but not allowing modifiers. E.g., C or C(?i)Perl/> but B |
|
223
|
|
|
|
|
|
|
C. |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=item Junction |
|
226
|
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
A C is a superposition of several values together with a I. |
|
228
|
|
|
|
|
|
|
The mode can be C<&> (meaning the object should match I of the given |
|
229
|
|
|
|
|
|
|
values), C<|> (the object should match I of the given values) |
|
230
|
|
|
|
|
|
|
and C (the object should match I of the given values). The list |
|
231
|
|
|
|
|
|
|
of values is given in angular backets C<< < ... > >> and is whitespace-separated. |
|
232
|
|
|
|
|
|
|
It can contain (and freely mix) quoted strings, regexes, junctions and barewords. |
|
233
|
|
|
|
|
|
|
A junction can also be negated by prefixing its mode with a tilde C<~>. |
|
234
|
|
|
|
|
|
|
E.g., C<< & >> (both match) or C<< | >> |
|
235
|
|
|
|
|
|
|
(at least one matches) or C<< ~& >> (not all match). |
|
236
|
|
|
|
|
|
|
|
|
237
|
|
|
|
|
|
|
=item Bareword |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
All of the above constructs start with a non-alphabetical character: |
|
240
|
|
|
|
|
|
|
C<:> for pairs, C<'> for quoted strings, C> for regexes and C<&>, |
|
241
|
|
|
|
|
|
|
C<|>, C or C<~> for junctions. Hence, single word strings do not |
|
242
|
|
|
|
|
|
|
actually have to be written in quotes as they can be distinguished |
|
243
|
|
|
|
|
|
|
from non-strings. A C is a string of C<\w> characters, |
|
244
|
|
|
|
|
|
|
C<\d> numbers or the punctuation characters C<.>, C<-> or C<_>. |
|
245
|
|
|
|
|
|
|
It is internally converted to a string. Barewords can appear in |
|
246
|
|
|
|
|
|
|
junctions as well as the top-level query. At the top level, they are |
|
247
|
|
|
|
|
|
|
converted to pairs with B and with the bareword as a string |
|
248
|
|
|
|
|
|
|
value. It is up to the application to decide what to do with them. |
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=back |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=head1 EXAMPLES |
|
253
|
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
=head2 Searching a small database |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
Get all books (co-)authored by C: |
|
257
|
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
use Modern::Perl; |
|
259
|
|
|
|
|
|
|
use Query::Tags qw(parse_query); |
|
260
|
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
my @books = ( |
|
262
|
|
|
|
|
|
|
{ title => 'Programming Perl', authors => 'Tom Christiansen, brian d foy, Larry Wall, Jon Orwant' }, |
|
263
|
|
|
|
|
|
|
{ title => 'Learning Perl', authors => 'Randal L. Schwartz, Tom Phoenix, brian d foy' }, |
|
264
|
|
|
|
|
|
|
{ title => 'Intermediate Perl', authors => 'Randal L. Schwartz and brian d foy, with Tom Phoenix' }, |
|
265
|
|
|
|
|
|
|
{ title => 'Mastering Perl', authors => 'brian d foy' }, |
|
266
|
|
|
|
|
|
|
{ title => 'Perl Best Practices', authors => 'Damian Conway' }, |
|
267
|
|
|
|
|
|
|
{ title => 'Higher-Order Perl', authors => 'Mark-Jason Dominus' }, |
|
268
|
|
|
|
|
|
|
{ title => 'Object Oriented Perl', authors => 'Damian Conway' }, |
|
269
|
|
|
|
|
|
|
{ title => 'Modern Perl', authors => 'chromatic' } |
|
270
|
|
|
|
|
|
|
); |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
say $_->{title} for parse_query(q[:authors/foy/])->select(@books); |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=head2 Email headers |
|
275
|
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
Find all work emails from a mailing list that mention C or C: |
|
277
|
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
use v5.16; |
|
279
|
|
|
|
|
|
|
use Mail::Header; |
|
280
|
|
|
|
|
|
|
use Path::Tiny; |
|
281
|
|
|
|
|
|
|
use Query::Tags qw(parse_query); |
|
282
|
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
my @mail = map { Mail::Header->new([$_->lines]) } path('~/Mail/work/cur')->children; |
|
284
|
|
|
|
|
|
|
my @headers = map { my $mh = $_; +{ map { fc $_ => $mh->get($_) } $mh->tags } } @mail; |
|
285
|
|
|
|
|
|
|
say $_->{subject} for |
|
286
|
|
|
|
|
|
|
parse_query(q[:list-id :subject|(?i)seminar/ /(?i)talk/>])->select(@headers); |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=cut |
|
289
|
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=head1 AUTHOR |
|
291
|
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
Tobias Boege |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
295
|
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
This software is copyright (C) 2025 by Tobias Boege. |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
This is free software; you can redistribute it and/or |
|
299
|
|
|
|
|
|
|
modify it under the terms of the Artistic License 2.0. |
|
300
|
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=cut |
|
302
|
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
":wq" |