File Coverage

blib/lib/TidyView/Options.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package TidyView::Options;
2              
3 2     2   33120 use strict;
  2         14  
  2         64  
4 2     2   12 use warnings;
  2         4  
  2         64  
5              
6             # handles the GUI part of representing options
7              
8             # PerlTidy::Options handles what options that are available, and their 'type', TidyView::Options draws them
9              
10             # we have, if you will an MVC type pattern with PerlTidy::Options as the model,
11             # and TidyView::Options as the view. TidyView::Run is the controller
12              
13 2     2   466 use PerlTidy::Options;
  2         4  
  2         49  
14 2     2   989 use TidyView::Frame;
  0            
  0            
15              
16             use Tk;
17             use Tk::BrowseEntry;
18              
19             use Data::Dumper;
20              
21             use Log::Log4perl qw(get_logger);
22              
23             =pod
24              
25             TidyView::Options - responsible for all the rendering tasks of the options supplied from PerlTidy::Options
26              
27             TidyView::Options and PerlTidy::Options co-operate closely, but TidyView's main focus is to render options and
28             collect any values a user may place in an options widget. PerlTidy::Options is responsible for knowing what options
29             tidyview knows about. Currently TidyView::Options is responsible for formatting the string that
30             ultimately gets written into a .perltidyrc file - mainly because it holds all the values set by the user.
31              
32             =cut
33              
34             # each of these holds the widget instances, e.g. every checkbox widget instance is in the %checkButtons hash, etc
35             my %checkButtons;
36             my %spinButtons;
37             my %labEntries;
38             my %colors;
39             my %listBoxes;
40              
41             # the top-level option types e.g. Whitespace control etc
42             my @sections;
43              
44             my $sectionActive = 0; # expose this so we can ask what the current section is, so redraws can return to
45             # the same section
46              
47             INIT {
48             @sections = PerlTidy::Options->getSections(); # ask Perltidy what sections we should draw
49              
50             # Old Tk's dont support Tk::Spinbox at all. Hence we have several helper functions that "do
51             # the right thing" for each version of Tk. We assign the appropriate set of functions to some
52             # typeglobs in the INIT block, as that is the easiest way to check the Tk version just before we
53             # get things going. At runtime, a call to __PACKAGE__->typeglobname() will call the right function
54              
55             # note that we replace Tk::Spinbox with Tk::Entry if we detect an old Tk->VERSION()
56              
57             # Note that this is the reason we introduced the dependency of 'use version' - the overloaded
58             # stringification and relational operators make comparing versions trivial.
59              
60             if ($^O !~ m/(?:win32|cygwin)/i) {
61              
62             require version;
63              
64             die $@ if $@;
65              
66             import version qw(qv);
67              
68             if (qv(Tk->VERSION()) < "804.027") { # is an out-of-date Tk
69             *_numericWidget = \&_numericAsTextbox;
70             } else { # is an up-to-date Tk
71             *_numericWidget = \&_numericAsSpinbox;
72             }
73             } else { # is windows, which cant distinquish between Version.pm and version.pm,
74             # and hence is cursed to use older Tk widgets.
75             *_numericWidget = \&_numericAsTextbox;
76             }
77             }
78              
79             =pod
80              
81             build($self, %args)
82              
83             Takes a single argument in the %arg hash - parent, which is the Tk:Frame widget you want the
84             PerlTidy::Options rendered in. From there it asks a series of questions of PerlTidy::View about
85             sections and options in sections, and decides how best to render them. The look and feel of much of the
86             apploaction is controlled from here
87              
88             =cut
89              
90             # render the options for the currently selected selection
91             sub build {
92             my (undef, %args) = @_;
93              
94             my $logger = get_logger((caller(0))[3]);
95              
96             my ($parent, $startSection) = @args{qw( parent startSection )};
97              
98             $logger->logdie("no parent") unless $parent;
99              
100             __PACKAGE__->_buildSelectedSections(
101             parent => $parent,
102             startSection => $startSection,
103             );
104             }
105              
106             sub buildFromConfig {
107             my (undef, %args) = @_;
108              
109             my $logger = get_logger((caller(0))[3]);
110              
111             my ($config, $parent) = @args{qw(config parent)};
112              
113             # make sure we delete all the currently selected options
114             %checkButtons = ();
115             %spinButtons = ();
116             %labEntries = ();
117             %colors = ();
118             %listBoxes = ();
119              
120             foreach my $option (keys %$config) {
121             my $section = PerlTidy::Options->getSection(name => $option);
122              
123             $logger->logdie("no known section for option $option") unless $section;
124              
125             my $type = PerlTidy::Options->getValueType(
126             section => $section,
127             entry => $option,
128             );
129              
130             $logger->logdie("no known value type for option $option in section $section") unless defined $type;
131              
132             my $category = __PACKAGE__->_getTypeCategory(type => $type);
133              
134             $logger->logdie("unknown category $category for option $option in section $section") unless $category;
135              
136             if ( $category eq 'checkbox' ) {
137             $checkButtons{$option} = $config->{$option};
138             next;
139             } elsif ( $category eq 'integer' ) {
140             $spinButtons{$option} = $config->{$option};
141             next;
142             } elsif ( $category eq 'string' ) {
143             $labEntries{$option} = $config->{$option};
144             next;
145             } elsif ( $category eq 'colour' ) {
146             $colors{$option} = $config->{$option};
147             next;
148             } elsif ( $category eq 'list' ) {
149             $listBoxes{$option} = $config->{$option};
150             next;
151             }
152             }
153              
154             # having reset the options, refresh the GUI
155              
156             foreach my $slave ($parent->packSlaves()) {
157             $slave->packForget();
158             }
159              
160             __PACKAGE__->build(
161             parent => $parent,
162             startSection => $sectionActive, # dont redraw with section[0], look at where we are
163             );
164              
165             return;
166             }
167              
168             # builds the options for selected section, and sets up a callback to use when the section changes
169             sub _buildSelectedSections {
170             my (undef, %args) = @_;
171              
172             my $logger = get_logger((caller(0))[3]);
173              
174             my ($parent, $startSection) = @args{qw(parent startSection)};
175              
176             my $sectionsFrame = $parent->Frame()->pack(
177             -fill => 'x',
178             -expand => 0,
179             );
180              
181             # TODO - make the text widget (not the listbox widget
182             my $browser = $parent->BrowseEntry(
183             -label => "Formatting Section",
184             -autolimitheight => 1,
185             -autolistwidth => 1,
186             -browsecmd => __PACKAGE__->_createDrawSectionCallback($sectionsFrame),
187             -choices => \@sections,
188             -listheight => scalar(@sections),
189             )->pack(
190             -side => 'top',
191             -anchor => 'w',
192             -before => $sectionsFrame,
193             );
194              
195             my $startValue = $startSection || 0;
196              
197             # set section in drop list to active section (otherwise if floats back to the top by default
198              
199             my $thisListbox = $browser->Subwidget('slistbox')->Subwidget('listbox');
200              
201             $thisListbox->activate($startValue);
202              
203             $thisListbox->focus();
204              
205             $thisListbox->see($startValue);
206              
207             # after all that we also need to set the entry widget to our current value
208              
209             my $thisEntryWidget = $browser->Subwidget('entry');
210              
211             $thisEntryWidget->delete(0, 'end');
212              
213             $thisEntryWidget->insert(0, $sections[$startValue]);
214              
215             __PACKAGE__->_buildAllEntries(
216             parent => $sectionsFrame,
217             section => $sections[$startSection || 0],
218             );
219              
220             return;
221             }
222              
223             # build the options for the requested section, providing a frame for each option set (all the checkboxes together, all lists together etc...)
224             sub _buildAllEntries {
225             my (undef, %args) = @_;
226              
227             my $logger = get_logger((caller(0))[3]);
228              
229             my ($parent, $section) = @args{qw(parent section)};
230              
231             # we should improve this to support future possible option type - i.e. dont hard-code it - capture it dynamically and ask a question when required
232             (my %types) = map {$_ => []} qw(checkbox integer color string list);
233              
234             # the basic scheme for drawing the entries for a section is -
235             # for each entry type (one of undef, string/flag, a number, color
236             # or array, we create a frame that we pack to the left, and file from
237             # the top
238              
239             # work out which values tyes we have, so we can draw frames for them
240             foreach my $entry (PerlTidy::Options->getEntries(section => $section)) {
241             my $valueType = PerlTidy::Options->getValueType(
242             section => $section,
243             entry => $entry,
244             );
245              
246             my $typeClass = __PACKAGE__->_getTypeCategory(type => $valueType);
247              
248             push @{$types{$typeClass}}, $entry;
249             }
250              
251             # TODO - fold long option lists
252             # sometimes, in one section, there is a very large number of options of one type
253             # We want to spread these across the page a bit. So if there are more than
254             # say 10 options of one type (type is the set (checkboxes posIntegers colors strings lists)
255             # we create a new frame for each block of 10
256              
257             # now we know the value types, create a frame and pack the options with this option type in there
258             # creates a frame for this option set, but only if there are any option in the set
259              
260             foreach my $type (sort grep {@{$types{$_}} > 0} keys %types) {
261              
262             my $typeFrame = TidyView::Frame->new(
263             parent => $parent,
264             frameOptions => {
265             -relief => 'sunken',
266             -borderwidth => 5,
267             },
268             );
269              
270             foreach my $entry (sort @{$types{$type}}) {
271              
272             my $entryFrame = TidyView::Frame->new(
273             parent => $typeFrame,
274             frameOptions => {
275             -relief => 'flat',
276             -borderwidth => 0,
277             },
278             packOptions => {
279             -anchor => 'n',
280             -side => 'top',
281             -fill => 'x',
282             -expand => 0,
283             },
284             );
285              
286             # organise them by their type
287             __PACKAGE__->_buildEntry(
288             section => $section,
289             entry => $entry,
290             parent => $entryFrame,
291             );
292             }
293             }
294             return;
295             }
296              
297             # for an individual option, determine the best widget to draw it based on its entry type, and try to
298             # register its value to update a conveniently provided scalar reference - not all widgets can do this
299              
300             sub _buildEntry {
301             my (undef, %args) = @_;
302              
303             my $logger = get_logger((caller(0))[3]);
304              
305             my ($parent, $section, $entry) = @args{qw(parent section entry)};
306              
307             unless (defined $parent) {
308             $logger->warn("invalid parent widget " . Dumper($parent) . "passed");
309             return;
310             }
311              
312             my $valueType = PerlTidy::Options->getValueType(
313             section => $section,
314             entry => $entry,
315             asReference => 1,
316             );
317              
318             my $valueClass = ref($valueType) || $valueType;
319              
320             my $typeClass = __PACKAGE__->_getTypeCategory(type => $valueClass);
321              
322             my $defaultValue = PerlTidy::Options->getDefaultValue(entry => $entry);
323              
324             $logger->error("no default entry for $entry") unless defined $defaultValue;
325              
326             if ( $typeClass eq 'checkbox') { # simple checkbox required
327              
328             $parent->Label(-text => $entry)->pack(
329             -side => 'left',
330             );
331              
332             # change to support the -variable option...
333             $parent->Checkbutton(
334             -variable => \$checkButtons{$entry},
335             )->pack(
336             -side => 'right',
337             -fill => 'both',
338             );
339              
340             unless (defined $checkButtons{$entry}) {
341             $checkButtons{$entry} = $defaultValue;
342             }
343              
344             } elsif ( $typeClass eq 'string' ) { # text box input required
345              
346             $parent->Label(-text => $entry)->pack(
347             -side => 'left',
348             -anchor => 'n',
349             );
350              
351             $parent->Entry(-textvariable => \$labEntries{$entry})->pack(
352             -side => 'right',
353             -anchor => 'n',
354             );
355              
356             $labEntries{$entry} ||= $defaultValue;
357             } elsif ( $typeClass eq 'integer') { # integer up/down input required
358              
359             $parent->Label(-text => $entry)->pack(
360             -side => 'left',
361             );
362              
363             my $value;
364             if (exists $spinButtons{$entry} and defined $spinButtons{$entry}) {
365             $value = $spinButtons{$entry};
366             } else {
367             $value = $defaultValue;
368             }
369              
370             __PACKAGE__->_numericWidget($parent, $entry, $value);
371              
372             } elsif ($typeClass eq 'list') { # dropdown list inpuit required
373             $parent->Label(-text => $entry)->pack(
374             -side => 'left',
375             -anchor => 'n',
376             );
377              
378             # some valueTypes are lists of values e.g. (dos, unix, mac) and some are ranges e.g. (0,3)
379             # create a list of values if a range
380             my $listValue = __PACKAGE__->_mapRangeToList($valueType);
381              
382             my $browser = $parent->BrowseEntry(
383             -autolimitheight => 1,
384             -autolistwidth => 1,
385             -browsecmd => __PACKAGE__->_generateBrowseListCallback($entry),
386             -choices => $listValue,
387             -listheight => scalar(@$listValue),
388             )->pack(
389             -side => 'right',
390             -anchor => 'w',
391             );
392              
393             my $thisListbox = $browser->Subwidget('slistbox')->Subwidget('listbox');
394              
395             my $index;
396             if (exists $listBoxes{$entry} and defined $listBoxes{$entry}) {
397             $index = $listBoxes{$entry};
398             } else {
399             $index = 0;
400             }
401              
402             $thisListbox->activate($index);
403              
404             $thisListbox->focus();
405              
406             $thisListbox->see($index);
407              
408             # after all that we also need to set the entry widget to our current value
409              
410             my $thisEntryWidget = $browser->Subwidget('entry');
411              
412             $thisEntryWidget->delete(0, 'end');
413              
414             $thisEntryWidget->insert(0, $index);
415              
416             } elsif ( $typeClass eq 'color' ) { # color dialog input required
417             $colors{$entry} = $parent->Button(
418             -text => "Choose $entry color",
419             -command => __PACKAGE__->_generateColorCallback($parent, $entry),
420             )->pack(
421             -side => 'left',
422             -anchor => 'n',
423             );
424              
425             } else {
426             $logger->logdie("Unsupported value type -> $typeClass");
427             }
428              
429             return;
430             }
431              
432             sub _generateBrowseListCallback {
433             my (undef, $entry) = @_;
434              
435             return sub {
436             my ($browser, $value) = @_;
437              
438             my $logger = get_logger((caller(0))[3]);
439              
440             $listBoxes{$entry} = $value;
441              
442             my $thisListbox = $browser->Subwidget('slistbox')->Subwidget('listbox');
443              
444             # uses the fact that all lists are numeric from 0..X
445              
446             my $index = (exists $listBoxes{$entry} and defined $listBoxes{$entry}) ? $listBoxes{$entry} : 0;
447              
448             $thisListbox->activate($index);
449              
450             $thisListbox->focus();
451              
452             $thisListbox->see($index);
453              
454             # after all that we also need to set the entry widget to our current value
455              
456             my $thisEntryWidget = $browser->Subwidget('entry');
457              
458             $thisEntryWidget->delete(0, 'end');
459              
460             $thisEntryWidget->insert(0, $index);
461             };
462             }
463              
464             sub _generateColorCallback {
465             my (undef, $parent, $entry) = @_;
466              
467             return sub {
468             $colors{$entry} = $parent->chooseColor(-title => $entry);
469             };
470             }
471              
472             sub _createDrawSectionCallback {
473             my (undef, $sectionFrame) = @_;
474              
475             return sub {
476             my (undef, $section) = @_;
477              
478             my $logger = get_logger((caller(0))[3]);
479              
480             ($sectionActive) = ($section =~ m/^(\d+)\./);
481             $sectionActive--;
482              
483             # empty all widgets from old section from the section frame,
484             # then draw widgets for new section in the section frame.
485              
486             foreach my $slave ($sectionFrame->packSlaves()) {
487             $slave->destroy();
488             }
489              
490             __PACKAGE__->_buildAllEntries(
491             parent => $sectionFrame,
492             section => $section,
493             );
494             };
495             }
496              
497             =pod
498              
499             asembleOptions(undef, %args) - responsible for formatting the user-selected options into
500             a format usable as a .perltidyrc file. Returns a string suitable for writing to a .perltidyrc file.
501              
502             Removes any options that have the same value as the perltidy default, to minimise the length of the
503             resulting .perltidyrc file
504              
505             Takes one argument currently in the %args hash - separator. Used to spearate the options in formatting.
506             Most commonly set to "\n" so that options appear one-per-line - additionally, if it is "\n", then additional
507             comments appear in the resultant string. Should probably be set to a whitespace of sime kind. If not supplied,
508             separator defaults to ' '.
509              
510             =cut
511              
512             sub assembleOptions {
513             my (undef, %args) = @_;
514              
515             my $logger = get_logger((caller(0))[3]);
516              
517             my ($separator) = @args{qw(separator)};
518              
519             $separator ||= ' ';
520              
521             my $optionString = '';
522              
523             # add a nice comment for the file-based output
524             $optionString .= "\n# ON-OFF style options\n\n" if $separator eq "\n";
525              
526             # values in the %checkButtons
527             $optionString .= __PACKAGE__->_generateCheckboxOptions($separator);
528              
529             $optionString .= "\n# Numeric-value style options\n\n" if $separator eq "\n";
530              
531             # values in the %spinButtons are the value spun too
532             $optionString .= join('',
533             map { "--$_=$spinButtons{$_} $separator" }
534             grep {__PACKAGE__->_differentToDefault(
535             name => $_,
536             currentValue => $spinButtons{$_},
537             )}
538             sort keys %spinButtons);
539              
540             $optionString .= "\n# Text-value style options\n\n" if $separator eq "\n";
541              
542             # values in the %labEntries are the value entered by the user (or the default) only assemble those that are defined
543             # and not '' (0 and '0' are OK (true but 0) values
544              
545             $optionString .= join('',
546             map { defined($labEntries{$_}) and $labEntries{$_} ne '' ? "--$_='$labEntries{$_}' $separator" : "" }
547             grep {__PACKAGE__->_differentToDefault(
548             name => $_,
549             currentValue => $labEntries{$_},
550             )}
551             sort keys %labEntries);
552              
553             $optionString .= "\n# Color options\n\n" if $separator eq "\n";
554              
555             # values in the %colors are the value selected in the dialogue, or the button widget - output scalars only
556             $optionString .= join('',
557             map { "--$_=$colors{$_} $separator" }
558             grep { not ref($colors{$_}) and __PACKAGE__->_differentToDefault(
559             name => $_,
560             currentValue => $colors{$_},
561             )}
562             sort keys %colors);
563              
564             $optionString .= "\n# List-selection style options\n\n" if $separator eq "\n";
565              
566             # values in the %listBoxes are the value selected in the dropdown or the default
567             $optionString .= join('',
568             map { "--$_=$listBoxes{$_} $separator" }
569             grep {__PACKAGE__->_differentToDefault(
570             name => $_,
571             currentValue => $listBoxes{$_},
572             )}
573             sort keys %listBoxes);
574              
575             return $optionString;
576             }
577              
578             =pod
579              
580             storeUnsupportOptions(undef, %args) - responsible for storing away any optiosn specified that
581             supported by the GUI - these are options that support perltidy itself, or debugging thereof.
582             Because these arent supported in the GUI, they get forgot, as ther are no widgets to hold them.
583             However we still need them when writing the perltidy options back out.
584              
585             =cut
586              
587             my %unsupportedOptions;
588              
589             sub storeUnsupportedOptions {
590             my (undef, %args) = @_;
591              
592             my $logger = get_logger((caller(0))[3]);
593              
594             my ($rawOptions) = @args{qw(rawOptions)};
595              
596             foreach my $option (keys %$rawOptions) {
597             if (PerlTidy::Options->getSection(name => $option) =~ m/^(?:0|13)./) { # from unsupported section
598             $unsupportedOptions{$option} = delete $rawOptions->{$option};
599             }
600             }
601             }
602              
603             =pod
604              
605             clearUnsupportedOptions(undef) - responsible for clearing away any options that were parsed
606             by perltidy, but for which we dont hold widgets for. Usually this is done just after reading
607             and parsing a new perltidy options file.
608              
609             =cut
610              
611             sub clearUnsupportedOptions {
612             %unsupportedOptions = ();
613             }
614              
615             sub assembleUnsupportedOptions {
616             my (undef, %args) = @_;
617              
618             my $logger = get_logger((caller(0))[3]);
619              
620             my ($separator) = @args{qw(separator)};
621              
622             $separator ||= ' ';
623              
624             my $unsupportedOptionsString = '';
625              
626             # add a nice comment for the file-based output
627             $unsupportedOptionsString .= "\n# options not supported by tidyview\n\n" if $separator eq "\n";
628              
629             foreach my $option (keys %unsupportedOptions) {
630             my $type = __PACKAGE__->_getTypeCategory(type => PerlTidy::Options->getType(entry => $option));
631              
632             if ($type eq 'checkbox') {
633             $unsupportedOptionsString .= "--$option $separator";
634             } else {
635             $unsupportedOptionsString .= "--$option=$unsupportedOptions{$option} $separator";
636             }
637             }
638              
639             return $unsupportedOptionsString;
640             }
641              
642             # checkboxes need somewhat more specialised procesing in order to get their output right
643             sub _generateCheckboxOptions {
644             my (undef, $separator) = @_;
645              
646             my $optionString = '';
647              
648             # output option string for values in the %checkButtons that differ from default
649             foreach my $key (sort keys %checkButtons) {
650             # if the option is the same as the default, we dont both outputting anything
651              
652             next unless __PACKAGE__->_differentToDefault(
653             name => $key,
654             currentValue => $checkButtons{$key},
655             );
656              
657             $optionString .= '--';
658              
659             # if the option is 0 or undef, then we need to output it as --noXXX, otherwise --XXX
660             $optionString .= 'no-' unless $checkButtons{$key};
661              
662             $optionString .= "$key $separator";
663             }
664              
665             return $optionString;
666             }
667              
668             # Functions to support various versions of Tk widgets.
669              
670             # older Tk's dont have a Spinbox, use a Textbox
671             sub _numericAsTextbox {
672             my (undef, $parent, $entry, $value) = @_;
673              
674             my $numericWidget = $parent->Entry(-textvariable => \$labEntries{$entry})->pack(
675             -side => 'right',
676             -anchor => 'n',
677             );
678              
679             $listBoxes{$entry} = $value;
680              
681             return $numericWidget;
682             }
683              
684             # modern Tk's have this handy widget for numeric values
685             sub _numericAsSpinbox {
686             my (undef, $parent, $entry, $value) = @_;
687              
688             my $numericWidget = $parent->Spinbox(-from => 0,
689             -increment => 1,
690             -text => 10,
691             -to => 1000,
692             -validatecommand => 0,
693             -textvariable => \$spinButtons{$entry},
694             )->pack(
695             -side => 'right',
696             );
697              
698             $spinButtons{$entry} = $value;
699              
700             return $numericWidget;
701             }
702              
703             # there are a few places where we need to map the type to a GUI widget - this function helps place all the
704             # different value types in one place
705              
706             sub _getTypeCategory {
707             my (undef, %args) = @_;
708              
709             my $logger = get_logger((caller(0))[3]);
710              
711             my ($type) = @args{qw(type)};
712              
713             if ( not defined $type or not $type or $type eq '!' ) {
714             return 'checkbox';
715             } elsif ($type =~ m/^(?:=s)$/) { # we dont support :s optional strings for now
716             return 'string';
717             } elsif ($type =~ m/^(?:=i)$/) { # we dont support :i optional integers for now
718             return 'integer';
719             } elsif ($type eq 'ARRAY') {
720             return 'list';
721             # } elsif ($type eq 'color') {
722             # push @{$types{colors}}, $entry;
723             } else {
724             $logger->logdie("unknown entry type $type");
725             }
726             }
727              
728             # converts ranges of [x, y] to arrayref of [x, x+1, ..., y-1, y]
729             sub _mapRangeToList {
730             my (undef, $range) = @_;
731              
732             if (ref($range) eq 'ARRAY' and
733             @$range == 2 and
734             $range->[0] =~ m/^(?:\d+)$/ and
735             $range->[1] =~ m/^(?:\d+)$/ and
736             $range->[0] < $range->[1]) { # check the range is well-formed
737             return [($range->[0]..$range->[1])];
738             } {
739             return $range;
740             }
741             }
742              
743             sub _differentToDefault {
744             my (undef, %args) = @_;
745              
746             my ($name, $currentValue) = @args{qw(name currentValue)};
747              
748             return 1 unless defined $name; # say its different, cause we cant compare it to anything
749              
750             return 1 unless defined $currentValue; # different to any possible default
751              
752             my $defaultValue = PerlTidy::Options->getDefaultValue(entry => $name);
753              
754             return 1 unless defined $defaultValue; # doesnt have a default, hence is always different
755              
756             return $defaultValue ne $currentValue; # compare everything as a string
757             }
758              
759             1;