File Coverage

blib/lib/Sieve/Generator/Sugar.pm
Criterion Covered Total %
statement 148 153 96.7
branch 25 32 78.1
condition 8 8 100.0
subroutine 37 38 97.3
pod 20 20 100.0
total 238 251 94.8


line stmt bran cond sub pod time code
1 1     1   5674 use v5.36.0;
  1         5  
2              
3             package Sieve::Generator::Sugar 0.003;
4             # ABSTRACT: constructor functions for building Sieve generator objects
5              
6 1     1   470 use JSON::MaybeXS ();
  1         11767  
  1         27  
7 1     1   520 use Params::Util qw(_ARRAY0 _HASH0 _SCALAR0);
  1         5894  
  1         72  
8              
9 1     1   5 use experimental 'builtin', 'for_list';
  1         1  
  1         4  
10 1     1   516 use builtin 'blessed';
  1         106  
  1         49  
11              
12 1         13 use Sub::Exporter -setup => [ qw(
13             blank
14             block
15             command
16             test
17             comment
18             set
19             sieve
20             heredoc
21             ifelse
22              
23             allof
24             anyof
25             noneof
26              
27             bool
28             hasflag
29             negate
30             number
31             qstr
32             terms
33             var_eq
34             var_ne
35 1     1   693 ) ];
  1         6787  
36              
37 1     1   932 use Sieve::Generator::Element::Block;
  1         4  
  1         36  
38 1     1   451 use Sieve::Generator::Element::Command;
  1         2  
  1         41  
39 1     1   467 use Sieve::Generator::Element::Comment;
  1         3  
  1         29  
40 1     1   399 use Sieve::Generator::Element::Document;
  1         3  
  1         28  
41 1     1   402 use Sieve::Generator::Element::Heredoc;
  1         2  
  1         30  
42 1     1   465 use Sieve::Generator::Element::IfElse;
  1         3  
  1         30  
43 1     1   390 use Sieve::Generator::Element::Junction;
  1         3  
  1         30  
44 1     1   382 use Sieve::Generator::Element::Num;
  1         2  
  1         31  
45 1     1   417 use Sieve::Generator::Element::Qstr;
  1         3  
  1         44  
46 1     1   402 use Sieve::Generator::Element::QstrList;
  1         3  
  1         30  
47 1     1   399 use Sieve::Generator::Element::Terms;
  1         3  
  1         1024  
48              
49             #pod =head1 SYNOPSIS
50             #pod
51             #pod use Sieve::Generator::Sugar '-all';
52             #pod
53             #pod my $script = sieve(
54             #pod command('require', [ qw(fileinto imap4flags) ]),
55             #pod blank(),
56             #pod ifelse(
57             #pod header_exists('X-Spam'),
58             #pod block(
59             #pod command('addflag', '$Junk'),
60             #pod command('fileinto', 'Spam'),
61             #pod ),
62             #pod ),
63             #pod );
64             #pod
65             #pod print $script->as_sieve;
66             #pod
67             #pod =head1 DESCRIPTION
68             #pod
69             #pod This module exports constructor functions for building
70             #pod L object trees. All functions can be imported at once
71             #pod with the C<-all> tag.
72             #pod
73             #pod Because many of the function names (C, C, C, and so on)
74             #pod are common words that may clash with existing code, L allows
75             #pod all imported symbols to be given a prefix:
76             #pod
77             #pod use Sieve::Generator::Sugar -all => { -prefix => 'sv_' };
78             #pod
79             #pod With that import, each function is available under its prefixed name, e.g.
80             #pod C, C, C, and so on.
81             #pod
82             #pod =func comment
83             #pod
84             #pod my $comment = comment($text);
85             #pod my $comment = comment($text, { hashes => 2 });
86             #pod
87             #pod This function creates a L with the given
88             #pod content. The content may be a plain string or an object doing
89             #pod L. The optional second argument is a hashref; its
90             #pod C key controls how many C<#> characters prefix each line, defaulting
91             #pod to one.
92             #pod
93             #pod =cut
94              
95 3     3 1 582 sub comment ($content, $arg = undef) {
  3         7  
  3         4  
  3         3  
96 3 100       50 return Sieve::Generator::Element::Comment->new({
97             ($arg ? %$arg : ()),
98             content => $content,
99             });
100             }
101              
102             #pod =func command
103             #pod
104             #pod my $cmd = command($identifier, (\%tagged?), @args);
105             #pod
106             #pod This function creates a L with the given
107             #pod identifier and arguments. Arguments may be plain strings or objects doing
108             #pod L. The command renders as a semicolon-terminated
109             #pod Sieve statement.
110             #pod
111             #pod =cut
112              
113 52     52   50 my sub _command ($identifier, $meta_arg, @args) {
  52         50  
  52         56  
  52         55  
  52         50  
114 52         52 my $tagged_args;
115              
116 52 100 100     170 if (@args && _HASH0($args[0]) && !blessed($args[0])) {
      100        
117 10         13 my $tagged_input = shift @args;
118              
119 10         23 for my ($k, $v) (%$tagged_input) {
120             # The underlying data structure is designed so we can represent this:
121             #
122             # :arg v1 v2 v3
123             #
124             # ...but there's currently
125 14 0       69 $tagged_args->{$k} = !defined $v ? []
    50          
    50          
    100          
    100          
126             : blessed($v) ? [ $v ]
127             : !ref $v ? [ Sieve::Generator::Element::Qstr->new({ str => $v }) ]
128             : _ARRAY0($v) ? [ Sieve::Generator::Element::QstrList->new({ strs => $v }) ]
129             : _SCALAR0($v) ? [ Sieve::Generator::Element::Terms->new({ terms => [$$v] }) ]
130             : Carp::confess("unknown reference type $v passed in Sieve command sugar's tagged args");
131             }
132             }
133              
134 52         85 my @autoquoted_args = map {;
135 21 0       316 blessed($_) ? $_
    50          
    100          
    100          
136             : !ref $_ ? Sieve::Generator::Element::Qstr->new({ str => $_ })
137             : _ARRAY0($_) ? Sieve::Generator::Element::QstrList->new({ strs => $_ })
138             : _SCALAR0($_) ? Sieve::Generator::Element::Terms->new({ terms => [$$_] })
139             : Carp::confess("unknown reference type $_ passed in Sieve command sugar's positional args");
140             } @args;
141              
142 52   100     1115 return Sieve::Generator::Element::Command->new({
143             %$meta_arg,
144              
145             identifier => $identifier,
146             tagged_args => $tagged_args // {},
147             positional_args => \@autoquoted_args,
148             });
149             }
150              
151 34     34 1 5859 sub command ($identifier, @args) {
  34         52  
  34         46  
  34         30  
152 34         60 _command($identifier, {}, @args);
153             }
154              
155             #pod =func test
156             #pod
157             #pod my $test = test($identifier, (\%tagged?), @args);
158             #pod
159             #pod This function creates a L with the given
160             #pod identifier and arguments -- and semicolon-at-the-end turned off. In the
161             #pod future, this might produce a distinct object, but for now, and really in Sieve,
162             #pod commands and tests are I the same thing.
163             #pod
164             #pod =cut
165              
166 18     18 1 7532 sub test ($identifier, @args) {
  18         24  
  18         24  
  18         19  
167 18         57 _command($identifier, { autowrap => 0, semicolon => 0 }, @args);
168             }
169              
170             #pod =func set
171             #pod
172             #pod my $cmd = set($variable, $value);
173             #pod
174             #pod This function creates a L for the Sieve
175             #pod C command (RFC 5229). Both C<$variable> and C<$value> are automatically
176             #pod quoted as Sieve strings.
177             #pod
178             #pod =cut
179              
180 2     2 1 36 sub set ($var, $val) {
  2         2  
  2         3  
  2         4  
181 2         25 return Sieve::Generator::Element::Command->new({
182             identifier => 'set',
183             positional_args => [
184             Sieve::Generator::Element::Qstr->new({ str => $var }),
185             Sieve::Generator::Element::Qstr->new({ str => $val }),
186             ],
187             });
188             }
189              
190             #pod =func ifelse
191             #pod
192             #pod my $if = ifelse($condition, $block);
193             #pod my $if = ifelse($cond, $if_block, [ $condN, $elsif_blockN ] ..., $else_block);
194             #pod
195             #pod This function creates a L. The first two
196             #pod arguments are the condition and the block to execute when it is true.
197             #pod Additional condition/block pairs render as C clauses. If the total
198             #pod number of trailing arguments is odd, the final argument is used as the plain
199             #pod C block.
200             #pod
201             #pod =cut
202              
203 25     25 1 329 sub ifelse ($cond, $if_true, @rest) {
  25         28  
  25         25  
  25         26  
  25         21  
204 25 100       50 my $else = @rest % 2 ? (pop @rest) : undef;
205              
206 25 100       301 return Sieve::Generator::Element::IfElse->new({
207             cond => $cond,
208             true => $if_true,
209             elsifs => \@rest,
210             ($else ? (else => $else) : ()),
211             });
212             }
213              
214             #pod =func blank
215             #pod
216             #pod my $blank = blank();
217             #pod
218             #pod This function creates an empty L. It is
219             #pod typically used to insert a blank line between sections of a Sieve script.
220             #pod
221             #pod =cut
222              
223 2     2 1 1358 sub blank () {
  2         2  
224 2         22 return Sieve::Generator::Element::Document->new({ things => [] });
225             }
226              
227             #pod =func sieve
228             #pod
229             #pod my $doc = sieve(@things);
230             #pod
231             #pod This function creates a L from the given
232             #pod C<@things>. The document is the top-level container for a Sieve script; its
233             #pod C method renders the full script as a string.
234             #pod
235             #pod =cut
236              
237 9     9 1 1250 sub sieve (@things) {
  9         14  
  9         9  
238 9         138 return Sieve::Generator::Element::Document->new({ things => \@things });
239             }
240              
241             #pod =func block
242             #pod
243             #pod my $block = block(@things);
244             #pod
245             #pod This function creates a L containing the
246             #pod given C<@things>. A block renders as a brace-delimited, indented sequence of
247             #pod statements, as used in Sieve C/C/C constructs.
248             #pod
249             #pod =cut
250              
251 30     30 1 1434 sub block (@things) {
  30         44  
  30         30  
252 30         362 return Sieve::Generator::Element::Block->new({ things => \@things });
253             }
254              
255             #pod =func allof
256             #pod
257             #pod my $test = allof(@tests);
258             #pod
259             #pod This function creates a L that renders as
260             #pod a Sieve C test, which is true only when all of the given tests
261             #pod are true.
262             #pod
263             #pod =cut
264              
265 1     1 1 14 sub allof (@things) {
  1         1  
  1         2  
266 1         12 return Sieve::Generator::Element::Junction->new({
267             type => 'allof',
268             things => \@things,
269             });
270             }
271              
272             #pod =func anyof
273             #pod
274             #pod my $test = anyof(@tests);
275             #pod
276             #pod This function creates a L that renders as
277             #pod a Sieve C test, which is true when any of the given tests is
278             #pod true.
279             #pod
280             #pod =cut
281              
282 3     3 1 45 sub anyof (@things) {
  3         7  
  3         4  
283 3         38 return Sieve::Generator::Element::Junction->new({
284             type => 'anyof',
285             things => \@things,
286             });
287             }
288              
289             #pod =func noneof
290             #pod
291             #pod my $test = noneof(@tests);
292             #pod
293             #pod This function creates a L that renders as
294             #pod a Sieve C test, which is true only when none of the given
295             #pod tests are true.
296             #pod
297             #pod =cut
298              
299 1     1 1 13 sub noneof (@things) {
  1         2  
  1         1  
300 1         16 return Sieve::Generator::Element::Junction->new({
301             type => 'noneof',
302             things => \@things,
303             });
304             }
305              
306             #pod =func terms
307             #pod
308             #pod my $terms = terms(@terms);
309             #pod
310             #pod This function creates a L from the given
311             #pod C<@terms>. Each term may be a plain string or an object doing
312             #pod L; all terms are joined with single spaces when
313             #pod rendered. This is the general-purpose constructor for Sieve test expressions
314             #pod and argument sequences.
315             #pod
316             #pod =cut
317              
318 14     14 1 4679 sub terms (@terms) {
  14         25  
  14         18  
319 14         193 return Sieve::Generator::Element::Terms->new({ terms => \@terms });
320             }
321              
322             #pod =func heredoc
323             #pod
324             #pod my $hd = heredoc($text);
325             #pod
326             #pod This function creates a L containing the
327             #pod given C<$text>. The text renders using the Sieve C/C<.> multiline
328             #pod string syntax. Any line beginning with C<.> is automatically escaped to
329             #pod C<..>.
330             #pod
331             #pod =cut
332              
333 6     6 1 2341 sub heredoc ($text) {
  6         8  
  6         7  
334 6         99 return Sieve::Generator::Element::Heredoc->new({ text => $text });
335             }
336              
337             #pod =func number
338             #pod
339             #pod my $num = number($value);
340             #pod my $num = number($value, $suffix);
341             #pod
342             #pod This function creates a L that renders as a
343             #pod Sieve numeric literal (RFC 5228 section 2.4.1). The C<$value> must be a
344             #pod non-negative integer. The optional C<$suffix> is a size quantifier: C,
345             #pod C, or C (case insensitive).
346             #pod
347             #pod =cut
348              
349 3     3 1 1700 sub number ($value, $suffix = undef) {
  3         5  
  3         4  
  3         4  
350 3 100       59 return Sieve::Generator::Element::Num->new({
351             value => $value,
352             (defined $suffix ? (suffix => $suffix) : ()),
353             });
354             }
355              
356             #pod =func qstr
357             #pod
358             #pod my $q = qstr($string);
359             #pod my @qs = qstr(@strings);
360             #pod my $list = qstr(\@strings);
361             #pod
362             #pod This function creates Sieve string objects. A plain scalar produces a
363             #pod L that renders as a quoted Sieve string. An
364             #pod array reference produces a L that renders
365             #pod as a bracketed Sieve string list. When given a list of arguments, it maps
366             #pod over each and returns a corresponding list of objects.
367             #pod
368             #pod =cut
369              
370 19     19 1 326188 sub qstr (@inputs) {
  19         27  
  19         19  
371 19         20 return map {;
372 19 100       242 ref ? Sieve::Generator::Element::QstrList->new({ strs => $_ })
373             : Sieve::Generator::Element::Qstr->new({ str => $_ })
374             } @inputs;
375             }
376              
377             #pod =func hasflag
378             #pod
379             #pod my $test = hasflag($flag);
380             #pod
381             #pod This function creates an RFC 5232 C test that is true if the message
382             #pod has the given flag set. The C<$flag> is automatically quoted as a Sieve
383             #pod string.
384             #pod
385             #pod =cut
386              
387 2     2 1 1128 sub hasflag ($flag) {
  2         2  
  2         3  
388 2         48 return Sieve::Generator::Element::Command->new({
389             autowrap => 0,
390             semicolon => 0,
391              
392             identifier => 'hasflag',
393             positional_args => [ Sieve::Generator::Element::Qstr->new({ str => $flag }) ],
394             });
395             }
396              
397             #pod =func negate
398             #pod
399             #pod my $test = negate($inner_test);
400             #pod
401             #pod This function wraps C<$inner_test> in a Sieve C test, producing output
402             #pod like C.
403             #pod
404             #pod =cut
405              
406 2     2 1 26 sub negate ($test) {
  2         3  
  2         3  
407 2         28 return Sieve::Generator::Element::Command->new({
408             autowrap => 0,
409             semicolon => 0,
410              
411             identifier => 'not',
412             positional_args => [ $test ],
413             });
414             }
415              
416             #pod =func bool
417             #pod
418             #pod my $test = bool($value);
419             #pod
420             #pod This function returns a test representing a literal C or C
421             #pod depending on the truthiness of C<$value>.
422             #pod
423             #pod =cut
424              
425 2     2 1 1074 sub bool ($value) {
  2         4  
  2         3  
426 2 100       51 return Sieve::Generator::Element::Command->new({
427             autowrap => 0,
428             semicolon => 0,
429             identifier => $value ? 'true' : 'false',
430             });
431             }
432              
433             #pod =func var_eq
434             #pod
435             #pod my $test = var_eq($var, $value);
436             #pod
437             #pod This produces a string "is" test checking that the given variable name equals
438             #pod the given value, by producing something like C.
439             #pod
440             #pod =cut
441              
442 1     1 1 582 sub var_eq ($var, $value) {
  1         3  
  1         2  
  1         1  
443 1         28 return Sieve::Generator::Element::Command->new({
444             autowrap => 0,
445             semicolon => 0,
446              
447             identifier => 'string',
448             tagged_args => { is => [] },
449             positional_args => [
450             Sieve::Generator::Element::Qstr->new({ str => "\${$var}" }),
451             qstr($value),
452             ],
453             });
454             }
455              
456             #pod =func var_ne
457             #pod
458             #pod my $test = var_ne($var, $value);
459             #pod
460             #pod This produces an inverted string "is" test checking that the given variable
461             #pod name equals the given value, by producing something like C
462             #pod "${$var}" "$value">.
463             #pod
464             #pod =cut
465              
466 0     0 1   sub var_ne ($var, $value) {
  0            
  0            
  0            
467 0           return Sieve::Generator::Element::Command->new({
468             autowrap => 0,
469             semicolon => 0,
470              
471             identifier => 'not string',
472             tagged_args => { is => [] },
473             positional_args => [
474             Sieve::Generator::Element::Qstr->new({ str => "\${$var}" }),
475             qstr($value),
476             ],
477             });
478             }
479              
480             1;
481              
482             __END__