File Coverage

blib/lib/Sieve/Generator/Sugar.pm
Criterion Covered Total %
statement 136 136 100.0
branch 14 14 100.0
condition n/a
subroutine 34 34 100.0
pod 21 21 100.0
total 205 205 100.0


line stmt bran cond sub pod time code
1 1     1   12160 use v5.36.0;
  1         5  
2              
3             package Sieve::Generator::Sugar 0.001;
4             # ABSTRACT: constructor functions for building Sieve generator objects
5              
6 1     1   1061 use JSON::MaybeXS ();
  1         14201  
  1         44  
7              
8 1         9 use Sub::Exporter -setup => [ qw(
9             blank
10             block
11             command
12             comment
13             set
14             sieve
15             heredoc
16             ifelse
17              
18             allof
19             anyof
20             noneof
21              
22             bool
23             fourpart
24             hasflag
25             header_exists
26             not_header_exists
27             not_string_test
28             qstr
29             size
30             string_test
31             terms
32 1     1   734 ) ];
  1         11237  
33              
34 1     1   907 use Sieve::Generator::Lines::Block;
  1         3  
  1         31  
35 1     1   501 use Sieve::Generator::Lines::Command;
  1         3  
  1         31  
36 1     1   397 use Sieve::Generator::Lines::Comment;
  1         3  
  1         30  
37 1     1   394 use Sieve::Generator::Lines::Document;
  1         3  
  1         31  
38 1     1   408 use Sieve::Generator::Lines::Heredoc;
  1         3  
  1         30  
39 1     1   425 use Sieve::Generator::Lines::IfElse;
  1         3  
  1         30  
40 1     1   422 use Sieve::Generator::Lines::Junction;
  1         3  
  1         29  
41 1     1   401 use Sieve::Generator::Text::Qstr;
  1         3  
  1         29  
42 1     1   430 use Sieve::Generator::Text::QstrList;
  1         3  
  1         30  
43 1     1   426 use Sieve::Generator::Text::Terms;
  1         3  
  1         777  
44              
45             #pod =head1 SYNOPSIS
46             #pod
47             #pod use Sieve::Generator::Sugar '-all';
48             #pod
49             #pod my $script = sieve(
50             #pod command('require', qstr([ qw(fileinto imap4flags) ])),
51             #pod blank(),
52             #pod ifelse(
53             #pod header_exists('X-Spam'),
54             #pod block(
55             #pod command('addflag', qstr('$Junk')),
56             #pod command('fileinto', qstr('Spam')),
57             #pod ),
58             #pod ),
59             #pod );
60             #pod
61             #pod print $script->as_sieve;
62             #pod
63             #pod =head1 DESCRIPTION
64             #pod
65             #pod This module exports constructor functions for building
66             #pod L object trees. All functions can be imported at once
67             #pod with the C<-all> tag.
68             #pod
69             #pod Because many of the function names (C, C, C, and so on)
70             #pod are common words that may clash with existing code, L allows
71             #pod all imported symbols to be given a prefix:
72             #pod
73             #pod use Sieve::Generator::Sugar -all => { -prefix => 'sv_' };
74             #pod
75             #pod With that import, each function is available under its prefixed name, e.g.
76             #pod C, C, C, and so on.
77             #pod
78             #pod =func comment
79             #pod
80             #pod my $comment = comment($text);
81             #pod my $comment = comment($text, { hashes => 2 });
82             #pod
83             #pod This function creates a L with the given
84             #pod content. The content may be a plain string or an object doing
85             #pod L. The optional second argument is a hashref; its
86             #pod C key controls how many C<#> characters prefix each line, defaulting
87             #pod to one.
88             #pod
89             #pod =cut
90              
91 3     3 1 998 sub comment ($content, $arg = undef) {
  3         7  
  3         7  
  3         6  
92 3 100       87 return Sieve::Generator::Lines::Comment->new({
93             ($arg ? %$arg : ()),
94             content => $content,
95             });
96             }
97              
98             #pod =func command
99             #pod
100             #pod my $cmd = command($identifier, @args);
101             #pod
102             #pod This function creates a L with the given
103             #pod identifier and arguments. Arguments may be plain strings or objects doing
104             #pod L. The command renders as a semicolon-terminated
105             #pod Sieve statement.
106             #pod
107             #pod =cut
108              
109 23     23 1 7133 sub command ($identifier, @args) {
  23         48  
  23         42  
  23         35  
110 23         639 return Sieve::Generator::Lines::Command->new({
111             identifier => $identifier,
112             args => \@args,
113             });
114             }
115              
116             #pod =func set
117             #pod
118             #pod my $cmd = set($variable, $value);
119             #pod
120             #pod This function creates a L for the Sieve
121             #pod C command (RFC 5229). Both C<$variable> and C<$value> are automatically
122             #pod quoted as Sieve strings.
123             #pod
124             #pod =cut
125              
126 2     2 1 923 sub set ($var, $val) {
  2         5  
  2         3  
  2         4  
127 2         89 return Sieve::Generator::Lines::Command->new({
128             identifier => 'set',
129             args => [
130             Sieve::Generator::Text::Qstr->new({ str => $var }),
131             Sieve::Generator::Text::Qstr->new({ str => $val }),
132             ],
133             });
134             }
135              
136             #pod =func ifelse
137             #pod
138             #pod my $if = ifelse($condition, $block);
139             #pod my $if = ifelse($cond, $if_block, [ $condN, $elsif_blockN ] ..., $else_block);
140             #pod
141             #pod This function creates a L. The first two
142             #pod arguments are the condition and the block to execute when it is true.
143             #pod Additional condition/block pairs render as C clauses. If the total
144             #pod number of trailing arguments is odd, the final argument is used as the plain
145             #pod C block.
146             #pod
147             #pod =cut
148              
149 23     23 1 486 sub ifelse ($cond, $if_true, @rest) {
  23         45  
  23         38  
  23         32  
  23         28  
150 23 100       65 my $else = @rest % 2 ? (pop @rest) : undef;
151              
152 23 100       598 return Sieve::Generator::Lines::IfElse->new({
153             cond => $cond,
154             true => $if_true,
155             elsifs => \@rest,
156             ($else ? (else => $else) : ()),
157             });
158             }
159              
160             #pod =func blank
161             #pod
162             #pod my $blank = blank();
163             #pod
164             #pod This function creates an empty L. It is
165             #pod typically used to insert a blank line between sections of a Sieve script.
166             #pod
167             #pod =cut
168              
169 1     1 1 1482 sub blank () {
  1         3  
170 1         13 return Sieve::Generator::Lines::Document->new({ things => [] });
171             }
172              
173             #pod =func sieve
174             #pod
175             #pod my $doc = sieve(@things);
176             #pod
177             #pod This function creates a L from the given
178             #pod C<@things>. The document is the top-level container for a Sieve script; its
179             #pod C method renders the full script as a string.
180             #pod
181             #pod =cut
182              
183 7     7 1 2839 sub sieve (@things) {
  7         17  
  7         13  
184 7         202 return Sieve::Generator::Lines::Document->new({ things => \@things });
185             }
186              
187             #pod =func block
188             #pod
189             #pod my $block = block(@things);
190             #pod
191             #pod This function creates a L containing the
192             #pod given C<@things>. A block renders as a brace-delimited, indented sequence of
193             #pod statements, as used in Sieve C/C/C constructs.
194             #pod
195             #pod =cut
196              
197 26     26 1 4509 sub block (@things) {
  26         61  
  26         34  
198 26         655 return Sieve::Generator::Lines::Block->new({ things => \@things });
199             }
200              
201             #pod =func allof
202             #pod
203             #pod my $test = allof(@tests);
204             #pod
205             #pod This function creates a L that renders as
206             #pod a Sieve C test, which is true only when all of the given tests
207             #pod are true.
208             #pod
209             #pod =cut
210              
211 1     1 1 23 sub allof (@things) {
  1         4  
  1         1  
212 1         54 return Sieve::Generator::Lines::Junction->new({
213             type => 'allof',
214             things => \@things,
215             });
216             }
217              
218             #pod =func anyof
219             #pod
220             #pod my $test = anyof(@tests);
221             #pod
222             #pod This function creates a L that renders as
223             #pod a Sieve C test, which is true when any of the given tests is
224             #pod true.
225             #pod
226             #pod =cut
227              
228 3     3 1 176 sub anyof (@things) {
  3         9  
  3         6  
229 3         67 return Sieve::Generator::Lines::Junction->new({
230             type => 'anyof',
231             things => \@things,
232             });
233             }
234              
235             #pod =func noneof
236             #pod
237             #pod my $test = noneof(@tests);
238             #pod
239             #pod This function creates a L that renders as
240             #pod a Sieve C test, which is true only when none of the given
241             #pod tests are true.
242             #pod
243             #pod =cut
244              
245 1     1 1 21 sub noneof (@things) {
  1         4  
  1         2  
246 1         31 return Sieve::Generator::Lines::Junction->new({
247             type => 'noneof',
248             things => \@things,
249             });
250             }
251              
252             #pod =func terms
253             #pod
254             #pod my $terms = terms(@terms);
255             #pod
256             #pod This function creates a L from the given
257             #pod C<@terms>. Each term may be a plain string or an object doing
258             #pod L; all terms are joined with single spaces when
259             #pod rendered. This is the general-purpose constructor for Sieve test expressions
260             #pod and argument sequences.
261             #pod
262             #pod =cut
263              
264 14     14 1 8230 sub terms (@terms) {
  14         38  
  14         20  
265 14         358 return Sieve::Generator::Text::Terms->new({ terms => \@terms });
266             }
267              
268             #pod =func heredoc
269             #pod
270             #pod my $hd = heredoc($text);
271             #pod
272             #pod This function creates a L containing the
273             #pod given C<$text>. The text renders using the Sieve C/C<.> multiline
274             #pod string syntax. Any line beginning with C<.> is automatically escaped to
275             #pod C<..>.
276             #pod
277             #pod =cut
278              
279 3     3 1 2800 sub heredoc ($text) {
  3         8  
  3         4  
280 3         90 return Sieve::Generator::Lines::Heredoc->new({ text => $text });
281             }
282              
283             #pod =func fourpart
284             #pod
285             #pod my $test = fourpart($identifier, $tag, $arg1, $arg2);
286             #pod
287             #pod This function creates a L representing a
288             #pod four-part Sieve test of the form C. C<$identifier>
289             #pod and C<$tag> are used as-is (with C<:> prepended to C<$tag>); C<$arg1> and
290             #pod C<$arg2> are each quoted automatically, with array references becoming Sieve
291             #pod string lists and plain scalars becoming quoted strings.
292             #pod
293             #pod =cut
294              
295 3     3 1 1710 sub fourpart ($identifier, $tag, $arg1, $arg2) {
  3         7  
  3         6  
  3         6  
  3         7  
  3         6  
296 3 100       95 return Sieve::Generator::Text::Terms->new({
    100          
297             terms => [
298             $identifier,
299             ":$tag",
300             (ref $arg1 ? Sieve::Generator::Text::QstrList->new({ strs => $arg1 })
301             : Sieve::Generator::Text::Qstr->new({ str => $arg1 })),
302             (ref $arg2 ? Sieve::Generator::Text::QstrList->new({ strs => $arg2 })
303             : Sieve::Generator::Text::Qstr->new({ str => $arg2 })),
304             ],
305             });
306             }
307              
308             #pod =func qstr
309             #pod
310             #pod my $q = qstr($string);
311             #pod my @qs = qstr(@strings);
312             #pod my $list = qstr(\@strings);
313             #pod
314             #pod This function creates Sieve string objects. A plain scalar produces a
315             #pod L that renders as a quoted Sieve string. An
316             #pod array reference produces a L that renders
317             #pod as a bracketed Sieve string list. When given a list of arguments, it maps
318             #pod over each and returns a corresponding list of objects.
319             #pod
320             #pod =cut
321              
322 23     23 1 346503 sub qstr (@inputs) {
  23         52  
  23         51  
323 23         48 return map {;
324 23 100       620 ref ? Sieve::Generator::Text::QstrList->new({ strs => $_ })
325             : Sieve::Generator::Text::Qstr->new({ str => $_ })
326             } @inputs;
327             }
328              
329             #pod =func header_exists
330             #pod
331             #pod my $test = header_exists($header);
332             #pod
333             #pod This function creates an RFC 5228 C test that is true if the named
334             #pod header field is present in the message. The C<$header> is automatically
335             #pod quoted as a Sieve string.
336             #pod
337             #pod =cut
338              
339 1     1 1 963 sub header_exists ($header) {
  1         4  
  1         3  
340 1         42 return Sieve::Generator::Text::Terms->new({
341             terms => [ 'exists', Sieve::Generator::Text::Qstr->new({ str => $header }) ],
342             });
343             }
344              
345             #pod =func not_header_exists
346             #pod
347             #pod my $test = not_header_exists($header);
348             #pod
349             #pod This function creates a C test that is true if the named header
350             #pod field is absent from the message. The C<$header> is automatically quoted as
351             #pod a Sieve string.
352             #pod
353             #pod =cut
354              
355 1     1 1 929 sub not_header_exists ($header) {
  1         3  
  1         3  
356 1         41 return Sieve::Generator::Text::Terms->new({
357             terms => [ 'not exists', Sieve::Generator::Text::Qstr->new({ str => $header }) ],
358             });
359             }
360              
361             #pod =func hasflag
362             #pod
363             #pod my $test = hasflag($flag);
364             #pod
365             #pod This function creates an RFC 5232 C test that is true if the message
366             #pod has the given flag set. The C<$flag> is automatically quoted as a Sieve
367             #pod string.
368             #pod
369             #pod =cut
370              
371 1     1 1 935 sub hasflag ($flag) {
  1         3  
  1         3  
372 1         39 return Sieve::Generator::Text::Terms->new({
373             terms => [ 'hasflag', Sieve::Generator::Text::Qstr->new({ str => $flag }) ],
374             });
375             }
376              
377             #pod =func string_test
378             #pod
379             #pod my $test = string_test($comparator, $key, $value);
380             #pod
381             #pod This function creates an RFC 5229 C test using the given comparator
382             #pod tag (e.g. C, C, C). The C<$key> and C<$value> should
383             #pod be objects doing L, typically produced by L.
384             #pod
385             #pod =cut
386              
387 1     1 1 21 sub string_test ($comparator, $key, $value) {
  1         3  
  1         2  
  1         3  
  1         2  
388 1         24 return Sieve::Generator::Text::Terms->new({
389             terms => [ "string :$comparator", $key, $value ],
390             });
391             }
392              
393             #pod =func not_string_test
394             #pod
395             #pod my $test = not_string_test($comparator, $key, $value);
396             #pod
397             #pod This function creates the negation of an RFC 5229 C test. It accepts
398             #pod the same arguments as L.
399             #pod
400             #pod =cut
401              
402 1     1 1 25 sub not_string_test ($comparator, $key, $value) {
  1         3  
  1         2  
  1         2  
  1         3  
403 1         26 return Sieve::Generator::Text::Terms->new({
404             terms => [ "not string :$comparator", $key, $value ],
405             });
406             }
407              
408             #pod =func size
409             #pod
410             #pod my $test = size($comparator, $value);
411             #pod
412             #pod This function creates an RFC 5228 C test using the given comparator
413             #pod (C or C) and size value (e.g. C<100K>). The value is not quoted
414             #pod and is passed through as-is.
415             #pod
416             #pod =cut
417              
418 1     1 1 949 sub size ($comparator, $value) {
  1         3  
  1         3  
  1         2  
419 1         40 return Sieve::Generator::Text::Terms->new({
420             terms => [ "size :$comparator", $value ],
421             });
422             }
423              
424             #pod =func bool
425             #pod
426             #pod my $test = bool($value);
427             #pod
428             #pod This function returns a Terms representing a literal C or C
429             #pod depending on the truthiness of C<$value>.
430             #pod
431             #pod =cut
432              
433 2     2 1 1857 sub bool ($value) {
  2         5  
  2         4  
434 2 100       81 return Sieve::Generator::Text::Terms->new({
435             terms => [ $value ? 'true' : 'false' ],
436             });
437             }
438              
439             1;
440              
441             __END__