File Coverage

blib/lib/ExtUtils/ParseXS/Utilities.pm
Criterion Covered Total %
statement 129 138 93.4
branch 48 50 96.0
condition 1 5 20.0
subroutine 20 22 90.9
pod 17 17 100.0
total 215 232 92.6


line stmt bran cond sub pod time code
1             package ExtUtils::ParseXS::Utilities;
2 27     27   668490 use strict;
  27         145  
  27         817  
3 27     27   242 use warnings;
  27         53  
  27         697  
4 27     27   144 use Exporter;
  27         60  
  27         1071  
5 27     27   163 use File::Spec;
  27         50  
  27         756  
6 27     27   4087 use ExtUtils::ParseXS::Constants ();
  27         62  
  27         57899  
7              
8             our $VERSION = '3.43_02';
9              
10             our (@ISA, @EXPORT_OK);
11             @ISA = qw(Exporter);
12             @EXPORT_OK = qw(
13             standard_typemap_locations
14             trim_whitespace
15             C_string
16             valid_proto_string
17             process_typemaps
18             map_type
19             standard_XS_defs
20             assign_func_args
21             analyze_preprocessor_statements
22             set_cond
23             Warn
24             current_line_number
25             blurt
26             death
27             check_conditional_preprocessor_statements
28             escape_file_for_line_directive
29             report_typemap_failure
30             );
31              
32             =head1 NAME
33              
34             ExtUtils::ParseXS::Utilities - Subroutines used with ExtUtils::ParseXS
35              
36             =head1 SYNOPSIS
37              
38             use ExtUtils::ParseXS::Utilities qw(
39             standard_typemap_locations
40             trim_whitespace
41             C_string
42             valid_proto_string
43             process_typemaps
44             map_type
45             standard_XS_defs
46             assign_func_args
47             analyze_preprocessor_statements
48             set_cond
49             Warn
50             blurt
51             death
52             check_conditional_preprocessor_statements
53             escape_file_for_line_directive
54             report_typemap_failure
55             );
56              
57             =head1 SUBROUTINES
58              
59             The following functions are not considered to be part of the public interface.
60             They are documented here for the benefit of future maintainers of this module.
61              
62             =head2 C
63              
64             =over 4
65              
66             =item * Purpose
67              
68             Provide a list of filepaths where F files may be found. The
69             filepaths -- relative paths to files (not just directory paths) -- appear in this list in lowest-to-highest priority.
70              
71             The highest priority is to look in the current directory.
72              
73             'typemap'
74              
75             The second and third highest priorities are to look in the parent of the
76             current directory and a directory called F underneath the parent
77             directory.
78              
79             '../typemap',
80             '../lib/ExtUtils/typemap',
81              
82             The fourth through ninth highest priorities are to look in the corresponding
83             grandparent, great-grandparent and great-great-grandparent directories.
84              
85             '../../typemap',
86             '../../lib/ExtUtils/typemap',
87             '../../../typemap',
88             '../../../lib/ExtUtils/typemap',
89             '../../../../typemap',
90             '../../../../lib/ExtUtils/typemap',
91              
92             The tenth and subsequent priorities are to look in directories named
93             F which are subdirectories of directories found in C<@INC> --
94             I a file named F actually exists in such a directory.
95             Example:
96              
97             '/usr/local/lib/perl5/5.10.1/ExtUtils/typemap',
98              
99             However, these filepaths appear in the list returned by
100             C in reverse order, I, lowest-to-highest.
101              
102             '/usr/local/lib/perl5/5.10.1/ExtUtils/typemap',
103             '../../../../lib/ExtUtils/typemap',
104             '../../../../typemap',
105             '../../../lib/ExtUtils/typemap',
106             '../../../typemap',
107             '../../lib/ExtUtils/typemap',
108             '../../typemap',
109             '../lib/ExtUtils/typemap',
110             '../typemap',
111             'typemap'
112              
113             =item * Arguments
114              
115             my @stl = standard_typemap_locations( \@INC );
116              
117             Reference to C<@INC>.
118              
119             =item * Return Value
120              
121             Array holding list of directories to be searched for F files.
122              
123             =back
124              
125             =cut
126              
127             SCOPE: {
128             my @tm_template;
129              
130             sub standard_typemap_locations {
131 9     9 1 174 my $include_ref = shift;
132              
133 9 100       118 if (not @tm_template) {
134 5         253 @tm_template = qw(typemap);
135              
136 5         224 my $updir = File::Spec->updir();
137 5         342 foreach my $dir (
138             File::Spec->catdir(($updir) x 1),
139             File::Spec->catdir(($updir) x 2),
140             File::Spec->catdir(($updir) x 3),
141             File::Spec->catdir(($updir) x 4),
142             ) {
143 20         402 unshift @tm_template, File::Spec->catfile($dir, 'typemap');
144 20         263 unshift @tm_template, File::Spec->catfile($dir, lib => ExtUtils => 'typemap');
145             }
146             }
147              
148 9         314 my @tm = @tm_template;
149 9         55 foreach my $dir (@{ $include_ref}) {
  9         95  
150 100         1346 my $file = File::Spec->catfile($dir, ExtUtils => 'typemap');
151 100 100       2158 unshift @tm, $file if -e $file;
152             }
153 9         148 return @tm;
154             }
155             } # end SCOPE
156              
157             =head2 C
158              
159             =over 4
160              
161             =item * Purpose
162              
163             Perform an in-place trimming of leading and trailing whitespace from the
164             first argument provided to the function.
165              
166             =item * Argument
167              
168             trim_whitespace($arg);
169              
170             =item * Return Value
171              
172             None. Remember: this is an I modification of the argument.
173              
174             =back
175              
176             =cut
177              
178             sub trim_whitespace {
179 359     359 1 19631 $_[0] =~ s/^\s+|\s+$//go;
180             }
181              
182             =head2 C
183              
184             =over 4
185              
186             =item * Purpose
187              
188             Escape backslashes (C<\>) in prototype strings.
189              
190             =item * Arguments
191              
192             $ProtoThisXSUB = C_string($_);
193              
194             String needing escaping.
195              
196             =item * Return Value
197              
198             Properly escaped string.
199              
200             =back
201              
202             =cut
203              
204             sub C_string {
205 233     233 1 1310 my($string) = @_;
206              
207 233         451 $string =~ s[\\][\\\\]g;
208 233         862 $string;
209             }
210              
211             =head2 C
212              
213             =over 4
214              
215             =item * Purpose
216              
217             Validate prototype string.
218              
219             =item * Arguments
220              
221             String needing checking.
222              
223             =item * Return Value
224              
225             Upon success, returns the same string passed as argument.
226              
227             Upon failure, returns C<0>.
228              
229             =back
230              
231             =cut
232              
233             sub valid_proto_string {
234 7     7 1 3965 my ($string) = @_;
235              
236 7 100       109 if ( $string =~ /^$ExtUtils::ParseXS::Constants::PrototypeRegexp+$/ ) {
237 5         23 return $string;
238             }
239              
240 2         7 return 0;
241             }
242              
243             =head2 C
244              
245             =over 4
246              
247             =item * Purpose
248              
249             Process all typemap files.
250              
251             =item * Arguments
252              
253             my $typemaps_object = process_typemaps( $args{typemap}, $pwd );
254              
255             List of two elements: C element from C<%args>; current working
256             directory.
257              
258             =item * Return Value
259              
260             Upon success, returns an L object.
261              
262             =back
263              
264             =cut
265              
266             sub process_typemaps {
267 14     14 1 9879 my ($tmap, $pwd) = @_;
268              
269 14 100       180 my @tm = ref $tmap ? @{$tmap} : ($tmap);
  13         117  
270              
271 14         111 foreach my $typemap (@tm) {
272 6 100       144 die "Can't find $typemap in $pwd\n" unless -r $typemap;
273             }
274              
275 12         161 push @tm, standard_typemap_locations( \@INC );
276              
277 12         4240 require ExtUtils::Typemaps;
278 12         266 my $typemap = ExtUtils::Typemaps->new;
279 12         37 foreach my $typemap_loc (@tm) {
280 88 100       1482 next unless -f $typemap_loc;
281             # skip directories, binary files etc.
282 24 50       1710 warn("Warning: ignoring non-text typemap file '$typemap_loc'\n"), next
283             unless -T $typemap_loc;
284              
285 24         258 $typemap->merge(file => $typemap_loc, replace => 1);
286             }
287              
288 12         622 return $typemap;
289             }
290              
291             =head2 C
292              
293             =over 4
294              
295             =item * Purpose
296              
297             Performs a mapping at several places inside C loop.
298              
299             =item * Arguments
300              
301             $type = map_type($self, $type, $varname);
302              
303             List of three arguments.
304              
305             =item * Return Value
306              
307             String holding augmented version of second argument.
308              
309             =back
310              
311             =cut
312              
313             sub map_type {
314 97     97 1 4110 my ($self, $type, $varname) = @_;
315              
316             # C++ has :: in types too so skip this
317 97 100       290 $type =~ tr/:/_/ unless $self->{RetainCplusplusHierarchicalTypes};
318 97         243 $type =~ s/^array\(([^,]*),(.*)\).*/$1 */s;
319 97 100       207 if ($varname) {
320 39 100       187 if ($type =~ / \( \s* \* (?= \s* \) ) /xg) {
321 1         8 (substr $type, pos $type, 0) = " $varname ";
322             }
323             else {
324 38         121 $type .= "\t$varname";
325             }
326             }
327 97         409 return $type;
328             }
329              
330             =head2 C
331              
332             =over 4
333              
334             =item * Purpose
335              
336             Writes to the C<.c> output file certain preprocessor directives and function
337             headers needed in all such files.
338              
339             =item * Arguments
340              
341             None.
342              
343             =item * Return Value
344              
345             Returns true.
346              
347             =back
348              
349             =cut
350              
351             sub standard_XS_defs {
352 9     9 1 992 print <<"EOF";
353             #ifndef PERL_UNUSED_VAR
354             # define PERL_UNUSED_VAR(var) if (0) var = var
355             #endif
356              
357             #ifndef dVAR
358             # define dVAR dNOOP
359             #endif
360              
361              
362             /* This stuff is not part of the API! You have been warned. */
363             #ifndef PERL_VERSION_DECIMAL
364             # define PERL_VERSION_DECIMAL(r,v,s) (r*1000000 + v*1000 + s)
365             #endif
366             #ifndef PERL_DECIMAL_VERSION
367             # define PERL_DECIMAL_VERSION \\
368             PERL_VERSION_DECIMAL(PERL_REVISION,PERL_VERSION,PERL_SUBVERSION)
369             #endif
370             #ifndef PERL_VERSION_GE
371             # define PERL_VERSION_GE(r,v,s) \\
372             (PERL_DECIMAL_VERSION >= PERL_VERSION_DECIMAL(r,v,s))
373             #endif
374             #ifndef PERL_VERSION_LE
375             # define PERL_VERSION_LE(r,v,s) \\
376             (PERL_DECIMAL_VERSION <= PERL_VERSION_DECIMAL(r,v,s))
377             #endif
378              
379             /* XS_INTERNAL is the explicit static-linkage variant of the default
380             * XS macro.
381             *
382             * XS_EXTERNAL is the same as XS_INTERNAL except it does not include
383             * "STATIC", ie. it exports XSUB symbols. You probably don't want that
384             * for anything but the BOOT XSUB.
385             *
386             * See XSUB.h in core!
387             */
388              
389              
390             /* TODO: This might be compatible further back than 5.10.0. */
391             #if PERL_VERSION_GE(5, 10, 0) && PERL_VERSION_LE(5, 15, 1)
392             # undef XS_EXTERNAL
393             # undef XS_INTERNAL
394             # if defined(__CYGWIN__) && defined(USE_DYNAMIC_LOADING)
395             # define XS_EXTERNAL(name) __declspec(dllexport) XSPROTO(name)
396             # define XS_INTERNAL(name) STATIC XSPROTO(name)
397             # endif
398             # if defined(__SYMBIAN32__)
399             # define XS_EXTERNAL(name) EXPORT_C XSPROTO(name)
400             # define XS_INTERNAL(name) EXPORT_C STATIC XSPROTO(name)
401             # endif
402             # ifndef XS_EXTERNAL
403             # if defined(HASATTRIBUTE_UNUSED) && !defined(__cplusplus)
404             # define XS_EXTERNAL(name) void name(pTHX_ CV* cv __attribute__unused__)
405             # define XS_INTERNAL(name) STATIC void name(pTHX_ CV* cv __attribute__unused__)
406             # else
407             # ifdef __cplusplus
408             # define XS_EXTERNAL(name) extern "C" XSPROTO(name)
409             # define XS_INTERNAL(name) static XSPROTO(name)
410             # else
411             # define XS_EXTERNAL(name) XSPROTO(name)
412             # define XS_INTERNAL(name) STATIC XSPROTO(name)
413             # endif
414             # endif
415             # endif
416             #endif
417              
418             /* perl >= 5.10.0 && perl <= 5.15.1 */
419              
420              
421             /* The XS_EXTERNAL macro is used for functions that must not be static
422             * like the boot XSUB of a module. If perl didn't have an XS_EXTERNAL
423             * macro defined, the best we can do is assume XS is the same.
424             * Dito for XS_INTERNAL.
425             */
426             #ifndef XS_EXTERNAL
427             # define XS_EXTERNAL(name) XS(name)
428             #endif
429             #ifndef XS_INTERNAL
430             # define XS_INTERNAL(name) XS(name)
431             #endif
432              
433             /* Now, finally, after all this mess, we want an ExtUtils::ParseXS
434             * internal macro that we're free to redefine for varying linkage due
435             * to the EXPORT_XSUB_SYMBOLS XS keyword. This is internal, use
436             * XS_EXTERNAL(name) or XS_INTERNAL(name) in your code if you need to!
437             */
438              
439             #undef XS_EUPXS
440             #if defined(PERL_EUPXS_ALWAYS_EXPORT)
441             # define XS_EUPXS(name) XS_EXTERNAL(name)
442             #else
443             /* default to internal */
444             # define XS_EUPXS(name) XS_INTERNAL(name)
445             #endif
446              
447             EOF
448              
449 9         61 print <<"EOF";
450             #ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
451             #define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
452              
453             /* prototype to pass -Wmissing-prototypes */
454             STATIC void
455             S_croak_xs_usage(const CV *const cv, const char *const params);
456              
457             STATIC void
458             S_croak_xs_usage(const CV *const cv, const char *const params)
459             {
460             const GV *const gv = CvGV(cv);
461              
462             PERL_ARGS_ASSERT_CROAK_XS_USAGE;
463              
464             if (gv) {
465             const char *const gvname = GvNAME(gv);
466             const HV *const stash = GvSTASH(gv);
467             const char *const hvname = stash ? HvNAME(stash) : NULL;
468              
469             if (hvname)
470             Perl_croak_nocontext("Usage: %s::%s(%s)", hvname, gvname, params);
471             else
472             Perl_croak_nocontext("Usage: %s(%s)", gvname, params);
473             } else {
474             /* Pants. I don't think that it should be possible to get here. */
475             Perl_croak_nocontext("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params);
476             }
477             }
478             #undef PERL_ARGS_ASSERT_CROAK_XS_USAGE
479              
480             #define croak_xs_usage S_croak_xs_usage
481              
482             #endif
483              
484             /* NOTE: the prototype of newXSproto() is different in versions of perls,
485             * so we define a portable version of newXSproto()
486             */
487             #ifdef newXS_flags
488             #define newXSproto_portable(name, c_impl, file, proto) newXS_flags(name, c_impl, file, proto, 0)
489             #else
490             #define newXSproto_portable(name, c_impl, file, proto) (PL_Sv=(SV*)newXS(name, c_impl, file), sv_setpv(PL_Sv, proto), (CV*)PL_Sv)
491             #endif /* !defined(newXS_flags) */
492              
493             #if PERL_VERSION_LE(5, 21, 5)
494             # define newXS_deffile(a,b) Perl_newXS(aTHX_ a,b,file)
495             #else
496             # define newXS_deffile(a,b) Perl_newXS_deffile(aTHX_ a,b)
497             #endif
498              
499             EOF
500 9         73 return 1;
501             }
502              
503             =head2 C
504              
505             =over 4
506              
507             =item * Purpose
508              
509             Perform assignment to the C attribute.
510              
511             =item * Arguments
512              
513             $string = assign_func_args($self, $argsref, $class);
514              
515             List of three elements. Second is an array reference; third is a string.
516              
517             =item * Return Value
518              
519             String.
520              
521             =back
522              
523             =cut
524              
525             sub assign_func_args {
526 58     58 1 2978 my ($self, $argsref, $class) = @_;
527 58         95 my @func_args = @{$argsref};
  58         135  
528 58 100       165 shift @func_args if defined($class);
529              
530 58         158 for my $arg (@func_args) {
531 70 100       221 $arg =~ s/^/&/ if $self->{in_out}->{$arg};
532             }
533 58         267 return join(", ", @func_args);
534             }
535              
536             =head2 C
537              
538             =over 4
539              
540             =item * Purpose
541              
542             Within each function inside each Xsub, print to the F<.c> output file certain
543             preprocessor statements.
544              
545             =item * Arguments
546              
547             ( $self, $XSS_work_idx, $BootCode_ref ) =
548             analyze_preprocessor_statements(
549             $self, $statement, $XSS_work_idx, $BootCode_ref
550             );
551              
552             List of four elements.
553              
554             =item * Return Value
555              
556             Modifed values of three of the arguments passed to the function. In
557             particular, the C and C attributes are modified.
558              
559             =back
560              
561             =cut
562              
563             sub analyze_preprocessor_statements {
564 3     3 1 10 my ($self, $statement, $XSS_work_idx, $BootCode_ref) = @_;
565              
566 3 100       10 if ($statement eq 'if') {
567 1         2 $XSS_work_idx = @{ $self->{XSStack} };
  1         4  
568 1         2 push(@{ $self->{XSStack} }, {type => 'if'});
  1         11  
569             }
570             else {
571             $self->death("Error: '$statement' with no matching 'if'")
572 2 50       8 if $self->{XSStack}->[-1]{type} ne 'if';
573 2 100       6 if ($self->{XSStack}->[-1]{varname}) {
574 1         2 push(@{ $self->{InitFileCode} }, "#endif\n");
  1         4  
575 1         2 push(@{ $BootCode_ref }, "#endif");
  1         3  
576             }
577              
578 2         4 my(@fns) = keys %{$self->{XSStack}->[-1]{functions}};
  2         11  
579 2 100       7 if ($statement ne 'endif') {
580             # Hide the functions defined in other #if branches, and reset.
581 1         4 @{$self->{XSStack}->[-1]{other_functions}}{@fns} = (1) x @fns;
  1         5  
582 1         3 @{$self->{XSStack}->[-1]}{qw(varname functions)} = ('', {});
  1         5  
583             }
584             else {
585 1         2 my($tmp) = pop(@{ $self->{XSStack} });
  1         5  
586             0 while (--$XSS_work_idx
587 1   33     6 && $self->{XSStack}->[$XSS_work_idx]{type} ne 'if');
588             # Keep all new defined functions
589 1         10 push(@fns, keys %{$tmp->{other_functions}});
  1         6  
590 1         5 @{$self->{XSStack}->[$XSS_work_idx]{functions}}{@fns} = (1) x @fns;
  1         6  
591             }
592             }
593 3         19 return ($self, $XSS_work_idx, $BootCode_ref);
594             }
595              
596             =head2 C
597              
598             =over 4
599              
600             =item * Purpose
601              
602             =item * Arguments
603              
604             =item * Return Value
605              
606             =back
607              
608             =cut
609              
610             sub set_cond {
611 54     54 1 135 my ($ellipsis, $min_args, $num_args) = @_;
612 54         95 my $cond;
613 54 100       160 if ($ellipsis) {
    100          
614 4 100       16 $cond = ($min_args ? qq(items < $min_args) : 0);
615             }
616             elsif ($min_args == $num_args) {
617 49         111 $cond = qq(items != $min_args);
618             }
619             else {
620 1         4 $cond = qq(items < $min_args || items > $num_args);
621             }
622 54         158 return $cond;
623             }
624              
625             =head2 C
626              
627             =over 4
628              
629             =item * Purpose
630              
631             Figures out the current line number in the XS file.
632              
633             =item * Arguments
634              
635             C<$self>
636              
637             =item * Return Value
638              
639             The current line number.
640              
641             =back
642              
643             =cut
644              
645             sub current_line_number {
646 14     14 1 28 my $self = shift;
647 14         30 my $line_number = $self->{line_no}->[@{ $self->{line_no} } - @{ $self->{line} } -1];
  14         31  
  14         40  
648 14         62 return $line_number;
649             }
650              
651             =head2 C
652              
653             =over 4
654              
655             =item * Purpose
656              
657             =item * Arguments
658              
659             =item * Return Value
660              
661             =back
662              
663             =cut
664              
665             sub Warn {
666 8     8 1 2571 my $self = shift;
667 8         39 my $warn_line_number = $self->current_line_number();
668 8         71 print STDERR "@_ in $self->{filename}, line $warn_line_number\n";
669             }
670              
671             =head2 C
672              
673             =over 4
674              
675             =item * Purpose
676              
677             =item * Arguments
678              
679             =item * Return Value
680              
681             =back
682              
683             =cut
684              
685             sub blurt {
686 2     2 1 700 my $self = shift;
687 2         11 $self->Warn(@_);
688 2         8 $self->{errors}++
689             }
690              
691             =head2 C
692              
693             =over 4
694              
695             =item * Purpose
696              
697             =item * Arguments
698              
699             =item * Return Value
700              
701             =back
702              
703             =cut
704              
705             sub death {
706 0     0 1 0 my $self = shift;
707 0         0 $self->Warn(@_);
708 0         0 exit 1;
709             }
710              
711             =head2 C
712              
713             =over 4
714              
715             =item * Purpose
716              
717             =item * Arguments
718              
719             =item * Return Value
720              
721             =back
722              
723             =cut
724              
725             sub check_conditional_preprocessor_statements {
726 60     60 1 6450 my ($self) = @_;
727 60         101 my @cpp = grep(/^\#\s*(?:if|e\w+)/, @{ $self->{line} });
  60         263  
728 60 100       246 if (@cpp) {
729 5         6 my $cpplevel;
730 5         10 for my $cpp (@cpp) {
731 13 100       57 if ($cpp =~ /^\#\s*if/) {
    100          
    100          
732 4         9 $cpplevel++;
733             }
734             elsif (!$cpplevel) {
735 2         8 $self->Warn("Warning: #else/elif/endif without #if in this function");
736             print STDERR " (precede it with a blank line if the matching #if is outside the function)\n"
737 2 100       8 if $self->{XSStack}->[-1]{type} eq 'if';
738 2         9 return;
739             }
740             elsif ($cpp =~ /^\#\s*endif/) {
741 3         7 $cpplevel--;
742             }
743             }
744 3 100       17 $self->Warn("Warning: #if without #endif in this function") if $cpplevel;
745             }
746             }
747              
748             =head2 C
749              
750             =over 4
751              
752             =item * Purpose
753              
754             Escapes a given code source name (typically a file name but can also
755             be a command that was read from) so that double-quotes and backslashes are escaped.
756              
757             =item * Arguments
758              
759             A string.
760              
761             =item * Return Value
762              
763             A string with escapes for double-quotes and backslashes.
764              
765             =back
766              
767             =cut
768              
769             sub escape_file_for_line_directive {
770 41     41 1 110 my $string = shift;
771 41         105 $string =~ s/\\/\\\\/g;
772 41         107 $string =~ s/"/\\"/g;
773 41         180 return $string;
774             }
775              
776             =head2 C
777              
778             =over 4
779              
780             =item * Purpose
781              
782             Do error reporting for missing typemaps.
783              
784             =item * Arguments
785              
786             The C object.
787              
788             An C object.
789              
790             The string that represents the C type that was not found in the typemap.
791              
792             Optionally, the string C or C to choose
793             whether the error is immediately fatal or not. Default: C
794              
795             =item * Return Value
796              
797             Returns nothing. Depending on the arguments, this
798             may call C or C, the former of which is
799             fatal.
800              
801             =back
802              
803             =cut
804              
805             sub report_typemap_failure {
806 0     0 1   my ($self, $tm, $ctype, $error_method) = @_;
807 0   0       $error_method ||= 'blurt';
808              
809 0           my @avail_ctypes = $tm->list_mapped_ctypes;
810              
811 0           my $err = "Could not find a typemap for C type '$ctype'.\n"
812             . "The following C types are mapped by the current typemap:\n'"
813             . join("', '", @avail_ctypes) . "'\n";
814              
815 0           $self->$error_method($err);
816 0           return();
817             }
818              
819             1;
820              
821             # vim: ts=2 sw=2 et: