File Coverage

blib/lib/OPM/Validate.pm
Criterion Covered Total %
statement 131 182 71.9
branch 15 24 62.5
condition 13 15 86.6
subroutine 7 7 100.0
pod 1 1 100.0
total 167 229 72.9


line stmt bran cond sub pod time code
1             package OPM::Validate;
2              
3             # ABSTRACT: Validate .opm files
4              
5 2     2   992 use v5.20;
  2         11  
6              
7 2     2   8 use strict;
  2         4  
  2         43  
8 2     2   8 use warnings;
  2         4  
  2         1380  
9              
10             our $VERSION = '1.13'; # VERSION
11              
12             my %boundaries = (
13             # tag min max
14             CVS => [ 0, 1],
15             Name => [ 1, 1],
16             URL => [ 1, 1],
17             Vendor => [ 1, 1],
18             Version => [ 1, 1],
19             BuildDate => [ 0, 1],
20             BuildHost => [ 0, 1],
21             );
22              
23             sub validate {
24 21     21 1 18615 my ($class, $content, $is_sopm) = @_;
25              
26 21         585 $content =~ s{
27            
28             }{}xmsg;
29              
30 21         60 my ($check,$grammar) = _grammar( $content, $is_sopm );
31 21         530 my $match = $content =~ $grammar;
32 21 100       140 die 'Invalid .opm file' if !$match;
33              
34 17         58 for my $key ( keys %boundaries ) {
35 119         180 $check->( $key );
36             }
37             }
38              
39             sub _grammar {
40 21     21   39 my ($content, $is_sopm) = @_;
41              
42 21   100     65 $is_sopm //= 0;
43              
44 21         29 my %s;
45             my %pos;
46              
47             my $check = sub {
48 1104     1104   1979 my ($name, $max, $min) = @_;
49              
50 1104 100 66     1956 $max //= (exists $boundaries{$name} ? $boundaries{$name}->[-1] : 100_000);
51 1104 100 100     2252 $min //= (exists $boundaries{$name} ? $boundaries{$name}->[0] : 0);
52              
53 1104         2174 my ($first,$second) = split /\./, $name;
54              
55 1104   100     2181 my $check = $s{$name} // 0;
56 1104 100 100     2244 if ( $second && ref $s{$first} ) {
57 692   50     1044 $check = $s{$first}->{$second} // 0;
58             }
59              
60 1104         1126 my $context = '';
61 1104         1156 my $pos = $pos{$name};
62 1104 100       1405 if ( $pos ) {
63 88         109 my $start_length = 30;
64 88         103 my $start = $pos - $start_length;
65 88 50       127 if ( $start < 0 ) {
66 0         0 $start_length = $pos;
67 0         0 $start = 0;
68             }
69              
70 88         88 my $end_length = 30;
71 88 50       143 if ( $pos + $end_length > length $content ) {
72 0         0 $end_length = ( length $content ) - $pos;
73             }
74              
75 88         402 $context = sprintf "\n%s <-- --> %s",
76             ( substr $content, $start, $start_length ),
77             ( substr $content, $pos, $end_length );
78             }
79              
80 1104 50       10330 if ( $check > $max ) {
    50          
81 0         0 die sprintf 'Too many "%s" elements. Max %s element(s) allowed.%s', $name, $max, $context;
82             }
83             elsif ( $check < $min ) {
84 0         0 die sprintf 'Too few "%s" elements. Min %s element(s) required.%s', $name, $min, $context;
85             }
86 21         114 };
87              
88             my $grammar = qr{
89             \A(?&PACKAGE)\z
90              
91             (?(DEFINE)
92             (?
93             \s* <\?xml \s* version="1.0" \s* encoding="[^"]+?" \s* \?> \s+
94             \s* <(?otrs|otobo)_package \s+ version="[12].[0-9]+">
95             (?:\s*(?&PACKAGE_TAGS))+
96             \s* \s*
97             )
98              
99             (?
100 8         24 (?:.*?(?{$pos{CVS} = pos(); $s{CVS}++; $check->('CVS')})) |
  8         13  
  8         16  
101 21         56 (?:.*?(?{$pos{Name} = pos(); ++$s{Name}; $check->('Name')})) |
  21         33  
  21         34  
102 21         36 (?:.*?(?{++$s{Version}; $check->('Version')})) |
  21         30  
103 19         35 (?:.*?(?{++$s{Vendor}; $check->('Vendor')})) |
  19         23  
104 19         36 (?:.*?(?{++$s{URL}; $check->('URL')})) |
  19         27  
105 15         30 (?:.*?(?{++$s{License}; $check->('License')})) |
  15         20  
106 12         32 (?:.*?(?{$s{BuildDate}++; $check->('BuildDate')})) |
  12         16  
107 12         29 (?:.*?(?{$s{BuildHost}++; $check->('BuildHost')})) |
  12         18  
108             (?:.*?) |
109             (?:.*?) |
110             (?:.*?) |
111             (?&FRAMEWORK) |
112             (?&DESCRIPTION) |
113             (?&INTRO) |
114             (?&CODE) |
115             (?&PACKAGEMERGE) |
116             (?&FILELIST) |
117             (?&PREREQ) |
118             (?&DATABASE) |
119             (?&CHANGELOG)
120             )
121              
122             (?
123 2     2   859 <(?[A-Za-z0-9]+)[^>]*>.*?(?{ print $+{custom},"\n" })
  2         740  
  2         4345  
  0         0  
124             )
125              
126             (?
127 27         363 .*?
128             (*COMMIT)
129             )
130              
131             (?
132 4         8 (?:Maximum="[0-9\.]+"(?{++$s{'Framework.Max'}; $check->('Framework.Max',1)})) |
  4         7  
133 4         7 (?:Minimum="[0-9\.]+"(?{++$s{'Framework.Min'}; $check->('Framework.Min',1)}))
  4         7  
134             )
135              
136             (?
137             .*?
138             (*COMMIT)
139             )
140              
141             (?
142 42         63 (?:Lang="(?[a-zA-Z]+)"(?{++$s{'Description.Lang'}; $check->('Description.Lang',1)})) |
  42         53  
143 0         0 (?:Format="[a-zA-Z]+"(?{++$s{'Description.Format'}; $check->('Description.Format',1)})) |
  0         0  
144 0         0 (?:Translatable="[01]"(?{++$s{'Description.Translatable'}; $check->('Description.Translatable',1)}))
  0         0  
145             )
146              
147             (?
148 16         36
149             ( \s+ (?&CHANGELOG_ATTR))*>.*?
150 16         33 (?{$check->('ChangeLog.Date', 1, 0); $check->('ChangeLog.Version', 1, 0); })
  16         19  
151             (*COMMIT)
152             )
153              
154             (?
155 16         27 (?:Date="[^"]+"(?{++$s{'ChangeLog.Date'}; $check->('ChangeLog.Date',1,1)})) |
  16         22  
156 16         28 (?:Version="[0-9\.]+"(?{++$s{'ChangeLog.Version'}; $check->('ChangeLog.Version',1,1)}))
  16         24  
157             )
158              
159             (?
160             Install|Upgrade|Reinstall|Uninstall)(?{delete $s{Intro};})
161 7 50       87 (?:(?Pre|Post|)(?{++$s{Intro}->{Type} if $+{intro_pre_post}}))?
162             ( \s+ (?&INTRO_ATTR))+>.*?
163 7         13 (?{$check->('Intro.Type', 1, 1);})
164             (*COMMIT)
165             )
166              
167             (?
168 7         20 (?:Type="(?i:Post|Pre)"(?{++$s{Intro}->{Type}; $check->('Intro.Type',1,1)})) |
  7         15  
169 2         5 (?:Title="[^"]*"(?{++$s{Intro}->{Title}; $check->('Intro.Title',1)})) |
  2         4  
170 0         0 (?:Format="[^"]*"(?{++$s{Intro}->{Format}; $check->('Intro.Format',1)})) |
  0         0  
171 2         11 (?:Lang="[A-Za-z]+"(?{++$s{Intro}->{Lang}; $check->('Intro.Lang',1)})) |
  2         10  
172 0         0 (?:Translatable="[01]"(?{++$s{Intro}->{Translatable}; $check->('Intro.Translatable',1)})) |
  0         0  
173 0 0       0 (?:Version="[0-9\.]+"(?{++$s{Intro}->{Version}; $check->('Intro.Version',$+{intro_type} eq 'Upgrade' ? 1 : 0)}))
  0         0  
174             )
175            
176             (?
177             Install|Upgrade|Reinstall|Uninstall) (?{delete $s{Code};})
178             ( \s+ (?&CODE_ATTR))*>.*?
179 0         0 (?{$check->('Code.Type', 1);})
180             (*COMMIT)
181             )
182              
183             (?
184 0         0 (?:Type="(?i:Post|Pre)"(?{++$s{Code}->{Type}; $check->('Code.Type',1,1)})) |
  0         0  
185 0 0       0 (?:Version="[^"]+"(?{++$s{Code}->{Version}; $check->('Code.Version',$+{code_type} eq 'Upgrade' ? 1 : 0)}))
  0         0  
186             )
187              
188             (?
189             <(?Module|Package)Required (?{delete $s{Prereq};})
190             (\s+ (?&VERSION))?>.+?
191 10         25 (?{$check->('Prereq.Version', 1); delete $s{Prereq}})
192             (*COMMIT)
193             )
194              
195             (?
196 10         23 (?:Version="[^"]+"(?{++$s{Prereq}->{Version}; $check->('Prereq.Version', 1)}))
  10         17  
197             )
198              
199             (?
200            
201             (\s+ (?&PACKAGEMERGEATTR))+>.*?
202 2         5 (?{$check->('Merge.Name', 1, 1);})
203             (*COMMIT)
204             )
205              
206             (?
207 2         4 (?:TargetVersion="[0-9\.]+"(?{++$s{'Merge.Version'}; $check->('Merge.Version', 1)})) |
  2         5  
208 2         13 (?:Name="[^"]+"(?{++$s{'Merge.Name'};}))
209             )
210              
211              
212             (?
213             (\s*(?(?{$is_sopm})(?&FILESOPM)|(?&FILEOPM)))+\s*
214             (*COMMIT)
215             )
216              
217             (?
218            
219 25         58 ( \s+ (?&FILE_ATTR))+ \s* />(?{$check->('File.' . $_ , 1, 1) for qw/Location Permission/;})
220             (*COMMIT)
221             )
222              
223             (?
224            
225             ( \s+ (?&FILE_ATTR))+ \s* > \s* [A-Za-z0-9\+/\s]+ (*COMMIT) ={0,3} \s*?
226 70         201 (?{$check->('File.' . $_ , 1, 1) for qw/Location Permission Encode/;})
227             (*COMMIT)
228             )
229              
230             (?
231 95         592 (?:Location="[^"]+"(?{++$s{File}->{Location};})) |
232 70         806 (?:Encode="[^"]+"(?{++$s{File}->{Encode};})) |
233 95         643 (?:Permission="[0-7]+"(?{++$s{File}->{Permission};}))
234             )
235              
236             (?
237             Install|Upgrade|Reinstall|Uninstall) (?{delete $s{Database}})
238             ( \s+ (?&DATABASE_ATTR)){0,3}>
239             (\s* (?&DATABASE_TAGS) )* \s*
240            
241             (*COMMIT)
242             )
243              
244             (?
245 13         39 (?:Type="(?i:Post|Pre)"(?{++$s{Database}->{Type}; $check->('Database.Type',1)})) |
  13         21  
246 0         0 (?:Version="[^"]+"(?{++$s{Database}->{Version}; $check->('Database.Version',1)}))
  0         0  
247             )
248              
249             (?
250             (?&TABLE_CREATE) |
251             (?&TABLE_DROP) |
252             (?&TABLE_ALTER) |
253             (?&INSERT)
254             )
255              
256             (?
257            
258             (?: \s+ (?&TABLE_ATTR))+ \s*>
259             (?: \s* (?&TABLE_CREATE_TAGS) )+ \s*
260            
261             (*COMMIT)
262             )
263              
264             (?
265             (?&COLUMN) |
266             (?&FOREIGN_KEY) |
267             (?&INDEX) |
268             (?&UNIQUE)
269             )
270              
271             (?
272            
273             ( \s+ (?&TABLE_ALTER_ATTR))+>
274             (\s* (?&TABLE_ALTER_TAGS) )*? \s*
275            
276             (*COMMIT)
277             )
278              
279             (?
280             (?&TABLE_ATTR) |
281             (?: \s* NameNew="[^"]*?") |
282             (?: \s* NameOld="[^"]*?")
283             )
284              
285             (?
286             (?&COLUMN_ADD) |
287             (?&COLUMN_DROP) |
288             (?&COLUMN_CHANGE) |
289             (?&FOREIGN_KEY_CREATE) |
290             (?&FOREIGN_KEY_DROP) |
291             (?&INDEX_CREATE) |
292             (?&INDEX_DROP) |
293             (?&UNIQUE_CREATE) |
294             (?&UNIQUE_DROP)
295             )
296              
297             (?
298            
299             ( \s+ (?&TABLE_ATTR))+ \s* (?:/>|>\s*)
300             (*COMMIT)
301             )
302              
303             (?
304 36         79 (?:Name="([^"]+)"(?{$pos{'Table.Name'} = pos(); ++$s{Table}->{Name}; $check->('Table.Name',1,1)})) |
  36         77  
  36         84  
305 0         0 (?:Type="(?i:Post|Pre)"(?{++$s{Table}->{Type}; $check->('Table.Type',1)})) |
  0         0  
306 20         44 (?:Version="[^"]+"(?{++$s{Table}->{Version}; $check->('Table.Version', 1)}))
  20         31  
307             )
308              
309             (?
310            
311             ( \s+ (?&COLUMN_ATTR))+ \s* (?:/>|>\s*)
312             (*COMMIT)
313             )
314              
315             (?
316            
317             ( \s+ (?&COLUMN_ATTR))+ \s* (?:/>|>\s*)
318             (*COMMIT)
319             )
320              
321             (?
322 87         200 (?:Name="[^"]+"(?{++$s{Column}->{Name}; $check->('Column.Name',1,1);})) |
  87         123  
323 6         15 (?:AutoIncrement="(?i:true|false)"(?{++$s{Column}->{AutoIncrement}; $check->('Column.AutoIncrement',1)})) |
  6         11  
324 87         181 (?:Required="(?i:true|false|0|1)"(?{++$s{Column}->{Required}; $check->('Column.Required',1)})) |
  87         112  
325 6         15 (?:PrimaryKey="(?i:true|false)"(?{++$s{Column}->{PrimaryKey}; $check->('Column.PrimaryKey',1)})) |
  6         10  
326 87         184 (?:\bType="[A-Za-z]+"(?{++$s{Column}->{Type}; $check->('Column.Type',1)})) |
  87         109  
327 38         87 (?:Size="\d+(?:,\d+)?"(?{++$s{Column}->{Size}; $check->('Column.Size',1)})) |
  38         52  
328 0         0 (?:Default="[^"]+"(?{++$s{Column}->{Default}; $check->('Column.Default',1)}))
  0         0  
329             )
330              
331             (?
332            
333             (?: \s+ (?&COLUMN_CHANGE_ATTR))+ \s* (?:/>|>\s*)
334             (*COMMIT)
335             )
336              
337             (?
338 2         8 (?:NameOld="[^"]*?"(?{++$s{Column}->{NameOld}; $check->('Column.NameOld',1)})) |
  2         4  
339 2         7 (?:NameNew="[^"]*?"(?{++$s{Column}->{NameNew}; $check->('Column.NameNew',1)})) |
  2         5  
340 0         0 (?:AutoIncrement="(?:true|false)"(?{++$s{Column}->{AutoIncrement}; $check->('Column.AutoIncrement',1)})) |
  0         0  
341 2         9 (?:Required="(?:true|false|0|1)"(?{++$s{Column}->{Required}; $check->('Column.Required',1)})) |
  2         4  
342 0         0 (?:PrimaryKey="(?:true|false)"(?{++$s{Column}->{PrimaryKey}; $check->('Column.PrimaryKey',1)})) |
  0         0  
343 2         7 (?:Type=".*?"(?{++$s{Column}->{Type}; $check->('Column.Type',1)})) |
  2         3  
344 2         6 (?:Size="\d+(?:,\d+)?"(?{++$s{Column}->{Size}; $check->('Column.Size',1)})) |
  2         4  
345 0         0 (?:Default=".*?"(?{++$s{Column}->{Default}; $check->('Column.Default',1)}))
  0         0  
346             )
347              
348             (?
349            
350             ( \s+ (?&COLUMN_DROP_ATTR))+ \s* (?:/>|>\s*)
351             (*COMMIT)
352             )
353              
354             (?
355 4         13 (?:Name="[^"]+?"(?{++$s{Column}->{Name}; $check->('Column.Name',1,1)}))
  4         8  
356             )
357              
358             (?
359 0         0
  0         0  
360             ( \s+ (?&INSERT_ATTR))+>
361             (\s+ (?&INSERT_DATA) )+ \s*
362            
363             (*COMMIT)
364             )
365              
366             (?
367 0         0 (?:Table=".*?"(?{++$s{'Insert.Table'}; $check->('Insert.Table',1,1)})) |
  0         0  
368 0         0 (?:Type=".*?"(?{++$s{'Insert.Type'}; $check->('Insert.Type',1)})) |
  0         0  
369 0         0 (?:Version=".*?"(?{++$s{'Insert.Version'}; $check->('Insert.Version',1)}))
  0         0  
370             )
371              
372             (?
373 0         0
  0         0  
374             ( \s+ (?&INSERT_DATA_ATTR))+>
375             .*?
376            
377             (*COMMIT)
378             )
379              
380             (?
381 0         0 (?:Key=".*?"(?{++$s{'Data.Key'}; $check->('Data.Key',1,1)})) |
  0         0  
382 0         0 (?:Translatable="[01]"(?{++$s{'Data.Translatable'}; $check->('Data.Translatable',1)})) |
  0         0  
383 0         0 (?:Type=".*?"(?{++$s{'Data.Type'}; $check->('Data.Type',1)}))
  0         0  
384             )
385              
386             (?
387            
388             (\s+ (?&INDEX_COLUMN) )+ \s*
389            
390             (?{ delete $s{'Generic.Name'}; })
391             (*COMMIT)
392             )
393              
394             (?
395            
396             ( \s* (?&NAME_ATTR))? \s* (?:/>|>
397             (\s+ (?&INDEX_COLUMN) )* \s*
398             )
399             (?{ delete $s{'Generic.Name'}; })
400             (*COMMIT)
401             )
402              
403             (?
404            
405             ( \s* (?&NAME_OR_SIZE_ATTR))* \s* (?:/>|>\s*)
406             (?{ delete $s{'Generic.Name'}; })
407             (*COMMIT)
408             )
409              
410             (?
411             (?&NAME_ATTR) |
412             (?:Size="[^"]*")
413             )
414              
415             (?
416            
417             ( \s* (?&NAME_ATTR))? \s* (?:/>|>\s*)
418             (?{ delete $s{'Generic.Name'}; })
419             (*COMMIT)
420             )
421              
422             (?
423            
424             (\s+ (?&UNIQUE_COLUMN) )+ \s*
425            
426             (*COMMIT)
427             )
428              
429             (?
430            
431             ( \s+ (?&NAME_ATTR))? \s* (?:/>|>\s*)
432             (?{ delete $s{'Generic.Name'}; })
433             (*COMMIT)
434             )
435              
436             (?
437            
438             ( \s+ (?&NAME_ATTR))? \s* (?:/>|>\s*)
439             (?{ delete $s{'Generic.Name'}; })
440             (*COMMIT)
441             )
442              
443             (?
444             |>\s*)
445             (*COMMIT)
446             )
447              
448             (?
449 0         0 (?:Name="[^"]+"(?{++$s{'Generic.Name'}; $check->('Generic.Name',1,1)}))
  0         0  
450             )
451            
452             (?
453            
454             (\s+ (?&REFERENCE) )+ \s*
455            
456             (*COMMIT)
457             )
458              
459             (?
460            
461             (?: \s+ (?:(?&NAME_ATTR)|(?&FOREIGN_TABLE)))? \s* (?:/>|>\s*
462             (\s+ (?&REFERENCE) )* \s*
463             )
464             (*COMMIT)
465             )
466              
467             (?
468             |>\s*(?:(?&REFERENCE)\s*)*)
469             (*COMMIT)
470             )
471              
472             (?
473 10         19 (?:ForeignTable="[^"]+"(?{++$s{'ForeignTable'}; $check->('ForeignTable',1,1)}))
  10         16  
474             )
475              
476             (?
477 19         174
478             ( \s+ (?&REFERENCE_ATTR) )+ \s*
479             (?:\s*/>|>\s*)
480             (*COMMIT)
481             )
482              
483             (?
484 19         42 (?:Local="[^"]+"(?{++$s{'Reference.Local'}; $check->('Reference.Local',1,1)})) |
  19         26  
485 19         35 (?:Foreign="[^"]+"(?{++$s{'Reference.Foreign'}; $check->('Reference.Foreign',1,1)}))
  19         28  
486             )
487             )
488 21         187 }xms;
489              
490 21         57 return $check, $grammar;
491             }
492              
493             1;
494              
495             __END__