File Coverage

blib/lib/RT/Client/REST/Forms.pm
Criterion Covered Total %
statement 75 133 56.3
branch 27 68 39.7
condition 14 39 35.9
subroutine 7 9 77.7
pod 5 5 100.0
total 128 254 50.3


line stmt bran cond sub pod time code
1             #!perl
2             # PODNAME: RT::Client::REST::Forms
3             # ABSTRACT: This package provides functions from RT::Interface::REST, because we don't want to depend on rt being installed. Derived from rt 3.4.5.
4              
5 22     22   105992 use strict;
  22         57  
  22         666  
6 22     22   130 use warnings;
  22         62  
  22         1035  
7              
8             package RT::Client::REST::Forms;
9             $RT::Client::REST::Forms::VERSION = '0.72';
10 22     22   135 use Exporter;
  22         49  
  22         949  
11              
12 22     22   154 use vars qw(@EXPORT @ISA);
  22         69  
  22         37926  
13              
14             @ISA = qw(Exporter);
15             @EXPORT = qw(expand_list form_parse form_compose vpush vsplit);
16              
17             my $CF_name = q%[#\s\w:()?/-]+%;
18             my $field = qr/[a-z][\w-]*|C(?:ustom)?F(?:ield)?-$CF_name|CF\.\{$CF_name}/i;
19             # always 9 https://rt-wiki.bestpractical.com/wiki/REST#Ticket_Attachments
20             my $spaces = ' ' x 9;
21              
22              
23             sub expand_list {
24 0     0 1 0 my ($list) = @_;
25 0         0 my (@elts, %elts);
26              
27 0         0 for my $elt (split /,/, $list) {
28 0 0       0 if ($elt =~ m/^(\d+)-(\d+)$/) { push @elts, ($1..$2) }
  0         0  
29 0         0 else { push @elts, $elt }
30             }
31              
32 0         0 @elts{@elts}=();
33 0         0 my @return = sort {$a<=>$b} keys %elts;
  0         0  
34             return @return
35 0         0 }
36              
37              
38             sub form_parse {
39 9     9 1 9762 my @lines = split /(?<=\n)/, shift;
40 9         40 my $state = 0;
41 9         26 my @forms = ();
42 9         73 my ($c, $o, $k, $e) = ('', [], {}, '');
43              
44             LINE:
45 9         43 while (@lines) {
46 121         199 my $line = shift @lines;
47              
48 121 100       258 next LINE if $line eq "\n";
49              
50 119 50       227 if ($line eq "--\n") {
51             # We reached the end of one form. We'll ignore it if it was
52             # empty, and store it otherwise, errors and all.
53 0 0 0     0 if ($e || $c || @$o) {
      0        
54 0         0 push @forms, [ $c, $o, $k, $e ];
55 0         0 $c = ''; $o = []; $k = {}; $e = '';
  0         0  
  0         0  
  0         0  
56             }
57 0         0 $state = 0;
58             next LINE
59 0         0 }
60              
61 119 50       213 if ($state != -1) {
62              
63 119 50 66     324 if ($state == 0 && $line =~ m/^#/) {
64             # Read an optional block of comments (only) at the start
65             # of the form.
66 0         0 $state = 1;
67 0         0 $c = $line;
68 0   0     0 while (@lines && $lines[0] =~ m/^#/) {
69 0         0 $c .= shift @lines;
70             }
71             next LINE
72 0         0 }
73              
74 119 100 66     1802 if ($state <= 1 && $line =~ m/^($field:) ?$/s) {
75             # Empty field
76 15         39 my $f = $1;
77 15         94 $f =~ s/:?$//;
78              
79 15 50       64 push(@$o, $f) unless exists $k->{$f};
80 15         46 vpush($k, $f, undef);
81              
82 15         27 $state = 1;
83              
84             next LINE
85 15         45 }
86              
87 104 50 33     1388 if ($state <= 1 && $line =~ m/^($field:) (.*)?$/s) {
88             # Read a field: value specification.
89 104         258 my $f = $1;
90 104         267 my $value = $2;
91 104         378 $f =~ s/:?$//;
92              
93             # Read continuation lines, if any.
94 104   100     586 while (@lines && ($lines[0] eq "\n" || $lines[0] =~ m/^ +/)) {
      100        
95 216         384 my $l = shift @lines;
96 216         852 $l =~ s/^$spaces//;
97 216         1237 $value .= $l
98             }
99              
100             # `Content` is always supposed to be followed by three new lines
101             # ... but this doesnt behave as documented
102             # https://rt-wiki.bestpractical.com/wiki/REST#Ticket_Attachments
103 104 100       209 if ($f eq 'Content') {
104 8         208 $value =~ s/\n\n\n?$//g
105             }
106             # Chomp everything else
107             else {
108 96         178 chomp $value
109             }
110              
111 104 50       311 push(@$o, $f) unless exists $k->{$f};
112 104         307 vpush($k, $f, $value);
113              
114 104         145 $state = 1;
115              
116             next LINE
117 104         277 }
118              
119 0 0       0 if ($line !~ m/^#/) {
120             # We've found a syntax error, so we'll reconstruct the
121             # form parsed thus far, and add an error marker. (>>)
122 0         0 $state = -1;
123 0         0 $e = form_compose([[ '', $o, $k, '' ]]);
124 0 0       0 $e.= $line =~ m/^>>/ ? "$line\n" : ">> $line\n";
125             next LINE
126 0         0 }
127              
128             # line will be ignored
129             }
130             else {
131             # We saw a syntax error earlier, so we'll accumulate the
132             # contents of this form until the end.
133 0         0 $e .= "$line\n";
134             }
135             }
136 9 50 33     159 push(@forms, [ $c, $o, $k, $e ]) if ($e || $c || @$o);
      33        
137              
138 9         56 for my $l (keys %$k) {
139 119 50       243 $k->{$l} = vsplit($k->{$l}) if (ref $k->{$l} eq 'ARRAY');
140             }
141              
142 9         46 return \@forms;
143             }
144              
145              
146             sub form_compose {
147 6     6 1 15 my ($forms) = @_;
148 6         16 my @text;
149              
150 6         16 for my $form (@$forms) {
151 6         19 my ($c, $o, $k, $e) = @$form;
152 6         11 my $text = '';
153              
154 6 50       14 if ($c) {
155 0         0 $c =~ s/\n*$/\n/;
156 0         0 $text = "$c\n";
157             }
158 6 50       18 if ($e) {
    50          
159 0         0 $text .= $e;
160             }
161             elsif ($o) {
162 6         12 my @lines;
163              
164 6         13 for my $key (@$o) {
165 10         15 my ($line, $sp);
166             my @values = (ref $k->{$key} eq 'ARRAY') ?
167 0         0 @{ $k->{$key} } :
168 10 50       36 $k->{$key};
169              
170 10         30 $sp = " "x(length("$key: "));
171 10 50       23 $sp = " "x4 if length($sp) > 16;
172              
173 10         16 for my $v (@values) {
174 10 50 33     49 if ($v =~ /\n/) {
    50          
175 0         0 $v =~ s/^/$sp/gm;
176 0         0 $v =~ s/^$sp//;
177              
178 0 0 0     0 if ($line) {
    0          
179 0         0 push @lines, "$line\n\n";
180 0         0 $line = '';
181             }
182             elsif (@lines && $lines[-1] !~ m/\n\n$/) {
183 0         0 $lines[-1] .= "\n";
184             }
185 0         0 push @lines, "$key: $v\n\n";
186             }
187             elsif ($line &&
188             length($line)+length($v)-rindex($line, "\n") >= 70)
189             {
190 0         0 $line .= ",\n$sp$v";
191             }
192             else {
193 10 50       34 $line = $line ? "$line, $v" : "$key: $v";
194             }
195             }
196              
197 10 50       23 $line = "$key:" unless @values;
198 10 50       21 if ($line) {
199 10 50       24 if ($line =~ m/\n/) {
200 0 0 0     0 if (@lines && $lines[-1] !~ m/\n\n$/) {
201 0         0 $lines[-1] .= "\n";
202             }
203 0         0 $line .= "\n";
204             }
205 10         35 push @lines, "$line\n";
206             }
207             }
208              
209 6         23 $text .= join '', @lines;
210             }
211             else {
212 0         0 chomp $text;
213             }
214 6         15 push @text, $text;
215             }
216              
217 6         27 return join "\n--\n\n", @text;
218             }
219              
220              
221             sub vpush {
222 119     119 1 330 my ($hash, $key, $val) = @_;
223 119 50       280 my @val = ref $val eq 'ARRAY' ? @$val : $val;
224              
225 119 50       225 if (exists $hash->{$key}) {
226 0 0       0 unless (ref $hash->{$key} eq 'ARRAY') {
227 0 0       0 my @v = $hash->{$key} ne '' ? $hash->{$key} : ();
228 0         0 $hash->{$key} = \@v;
229             }
230 0         0 push @{ $hash->{$key} }, @val;
  0         0  
231             }
232             else {
233 119         368 $hash->{$key} = $val;
234             }
235             }
236              
237              
238             sub vsplit {
239 0     0 1   my ($val) = @_;
240 0           my (@words);
241              
242 0 0         for my $line (map {split /\n/} (ref $val eq 'ARRAY') ? @$val : $val)
  0            
243             {
244             # XXX: This should become a real parser, à la Text::ParseWords.
245 0           $line =~ s/^\s+//;
246 0           $line =~ s/\s+$//;
247 0           push @words, split /\s*,\s*/, $line;
248             }
249              
250 0           return \@words;
251             }
252              
253             __END__
254              
255             =pod
256              
257             =encoding UTF-8
258              
259             =head1 NAME
260              
261             RT::Client::REST::Forms - This package provides functions from RT::Interface::REST, because we don't want to depend on rt being installed. Derived from rt 3.4.5.
262              
263             =head1 VERSION
264              
265             version 0.72
266              
267             =head2 METHODS
268              
269             =over 4
270              
271             =item expand_list
272              
273             Expands a list, splitting on commas and stuff.
274              
275             =item form_parse
276              
277             Returns a reference to an array of parsed forms.
278              
279             =item form_compose
280              
281             Returns text representing a set of forms.
282              
283             =for stopwords vpush vsplit
284              
285             =item vpush
286              
287             Add a value to a (possibly multi-valued) hash key.
288              
289             =item vsplit
290              
291             "Normalize" a hash key that's known to be multi-valued.
292              
293             =back
294              
295             1;
296              
297             =head1 AUTHOR
298              
299             Dean Hamstead <dean@fragfest.com.au>
300              
301             =head1 COPYRIGHT AND LICENSE
302              
303             This software is copyright (c) 2023, 2020 by Dmitri Tikhonov.
304              
305             This is free software; you can redistribute it and/or modify it under
306             the same terms as the Perl 5 programming language system itself.
307              
308             =cut