File Coverage

blib/lib/Sieve/Generator/Element/Command.pm
Criterion Covered Total %
statement 78 81 96.3
branch 18 24 75.0
condition 9 13 69.2
subroutine 10 10 100.0
pod 2 4 50.0
total 117 132 88.6


line stmt bran cond sub pod time code
1 1     1   11 use v5.36.0;
  1         2  
2             package Sieve::Generator::Element::Command 0.003;
3             # ABSTRACT: a single Sieve command statement
4              
5 1     1   4 use Moo;
  1         1  
  1         9  
6             with 'Sieve::Generator::Element';
7              
8 1     1   308 use Params::Util qw(_ARRAY0);
  1         1  
  1         799  
9              
10             #pod =head1 DESCRIPTION
11             #pod
12             #pod A command is a single semicolon-terminated Sieve statement, such as C,
13             #pod C, or C. It consists of an identifier followed by
14             #pod zero or more arguments.
15             #pod
16             #pod =attr identifier
17             #pod
18             #pod This attribute holds the name of the Sieve command, such as C,
19             #pod C, or C.
20             #pod
21             #pod =cut
22              
23             has identifier => (is => 'ro', required => 1);
24              
25             #pod =attr semicolon
26             #pod
27             #pod This attribute can be set to false during construction to suppress its trailing
28             #pod semicolon. This is useful for making tests, which are just commands without
29             #pod semicolons or blocks after them.
30             #pod
31             #pod =cut
32              
33             has semicolon => (
34             is => 'ro',
35             default => 1,
36             );
37              
38             #pod =attr autowrap
39             #pod
40             #pod This attribute can be set false during construction to suppress automatic
41             #pod multiline formatting if this command runs lone.
42             #pod
43             #pod =cut
44              
45             has autowrap => (
46             is => 'ro',
47             default => 1,
48             );
49              
50             #pod =attr block
51             #pod
52             #pod This attribute holds an optional L to render
53             #pod after the arguments instead of a semicolon. This models the Sieve grammar rule
54             #pod C for commands like
55             #pod C that take a block body. When set, the C attribute
56             #pod is ignored.
57             #pod
58             #pod =cut
59              
60             has block => (is => 'ro');
61              
62             #pod =attr tagged_args
63             #pod
64             #pod This attribute holds the list of tagged arguments to the command, given as a
65             #pod hashref. The values in the hashref will be array references of objects doing
66             #pod L, which will follow the tag name.
67             #pod
68             #pod The accessor will return a list of pairs.
69             #pod
70             #pod =cut
71              
72             has _tagged_args => (
73             is => 'ro',
74             default => sub { {} },
75             init_arg => 'tagged_args'
76             );
77              
78 82     82 1 79 sub tagged_args ($self) {
  82         77  
  82         81  
79 82         119 my $tagged_args = $self->_tagged_args;
80 82         187 return $tagged_args->%{ sort keys %$tagged_args };
81             }
82              
83             #pod =attr positional_args
84             #pod
85             #pod This attribute holds the list of positional arguments to the command. Each
86             #pod argument should be an object doing L.
87             #pod
88             #pod =cut
89              
90             has _positional_args => (
91             is => 'ro',
92             default => sub { [] },
93             init_arg => 'positional_args'
94             );
95              
96 82     82 1 144 sub positional_args { $_[0]->_positional_args->@* }
97              
98 20     20 0 21 sub children ($self) {
  20         21  
  20         16  
99 20         19 my @children;
100              
101 20         23 my @tagged_pairs = $self->tagged_args;
102 20         41 while (my ($name, $values) = splice @tagged_pairs, 0, 2) {
103 0         0 push @children, @$values;
104             }
105              
106 20         23 push @children, $self->positional_args;
107 20 50       30 push @children, $self->block if $self->block;
108              
109 20         44 return @children;
110             }
111              
112 59     59 0 602 sub as_sieve ($self, $i = undef) {
  59         108  
  59         64  
  59         63  
113 59   100     104 $i //= 0;
114              
115 59         103 my $oneline = $self->_as_sieve_oneline($i);
116              
117 59 100 100     196 if (!$self->autowrap || length $oneline < 72) {
118 57         112 return $oneline;
119             }
120              
121 2         5 return $self->_as_sieve_multiline($i);
122             }
123              
124 60     60   639 sub _as_sieve_oneline ($self, $i = undef) {
  60         96  
  60         58  
  60         82  
125 60   100     117 my $indent = q{ } x ($i // 0);
126              
127 60         102 my $str = $indent . $self->identifier;
128              
129 60         78 my @tagged_pairs = $self->tagged_args;
130 60         142 while (my ($name, $values) = splice @tagged_pairs, 0, 2) {
131 26         81 $str .= " :$name";
132 26         42 for my $v (@$values) {
133 17         31 my $rendered = $v->as_sieve(0);
134 17 50       56 $str .= $str =~ /\n\z/ ? $rendered : " $rendered";
135             }
136             }
137              
138 60         84 for my $arg ($self->positional_args) {
139 27         74 my $rendered = $arg->as_sieve(0);
140 27 100       77 $str .= $str =~ /\n\z/ ? $rendered : " $rendered";
141             }
142              
143 60 100       155 if ($self->block) {
    100          
144 2   50     9 my $blk = $self->block->as_sieve($i // 0);
145 2 50       7 $str .= $str =~ /\n\z/ ? $blk : " $blk";
146             } elsif ($self->semicolon) {
147 34         49 $str .= ";";
148             }
149              
150 60         99 return $str;
151             }
152              
153 2     2   3 sub _as_sieve_multiline ($self, $i = undef) {
  2         2  
  2         3  
  2         2  
154 2   50     18 my $indent = q{ } x ($i // 0);
155 2         6 my $indent2 = q{ } x (1 + length $self->identifier);
156              
157 2         4 my $str = $indent . $self->identifier;
158 2         3 my $n = 0;
159              
160 2         3 my @pair_queue = $self->tagged_args;
161 2         6 for my $i (grep {; $_ % 2 == 0 } keys @pair_queue) {
  20         27  
162 10         16 $pair_queue[$i] = ":$pair_queue[$i]";
163             }
164              
165 2         4 push @pair_queue, map {; $_->as_sieve(0), [] } $self->positional_args;
  0         0  
166              
167 2         7 while (my ($name, $values) = splice @pair_queue, 0, 2) {
168 10 100       17 $str .= $n++ ? "$indent$indent2" : q{ };
169 10         12 $str .= "$name";
170              
171 10 50       14 if (@$values) {
172 10         11 $str .= " " . join(q{ }, map {; $_->as_sieve(0) } @$values);
  10         15  
173             }
174              
175 10 100       22 if (@pair_queue) {
    50          
    50          
176 8         13 $str .= "\n";
177             } elsif ($self->block) {
178 0   0     0 $str .= " " . $self->block->as_sieve($i // 0);
179             } elsif ($self->semicolon) {
180 2         5 $str .= ";";
181             }
182             }
183              
184 2         7 return $str;
185             }
186              
187 1     1   6 no Moo;
  1         1  
  1         5  
188             1;
189              
190             __END__