File Coverage

blib/lib/Locale/Maketext/Utils/Phrase/Norm.pm
Criterion Covered Total %
statement 116 132 87.8
branch 51 64 79.6
condition 8 15 53.3
subroutine 34 41 82.9
pod 10 10 100.0
total 219 262 83.5


line stmt bran cond sub pod time code
1             package Locale::Maketext::Utils::Phrase::Norm;
2              
3 4     4   499143 use strict;
  4         9  
  4         167  
4 4     4   49 use warnings;
  4         8  
  4         294  
5              
6             $Locale::Maketext::Utils::Phrase::Norm::VERSION = '0.2';
7              
8 4     4   2288 use Module::Want ();
  4         8680  
  4         107  
9 4     4   29 use Carp ();
  4         12  
  4         11965  
10              
11             # IF YOU CHANGE THIS CHANGE THE “DEFAULT FILTERS” POD SECTION ALSO
12             my @default_filters = qw(NonBytesStr WhiteSpace Grapheme Ampersand Markup Ellipsis BeginUpper EndPunc Consider Escapes Compiles); # IF YOU CHANGE THIS CHANGE THE “DEFAULT FILTERS” POD SECTION ALSO
13              
14             # IF YOU CHANGE THIS CHANGE THE “DEFAULT FILTERS” POD SECTION ALSO
15              
16             # TODO ?: Acronym, IntroComma, Parens (needs CLDR char/pattern in Locales.pm) [output,chr,(not|in|the|markup|list|or any if amp() etc happen )???
17              
18             sub new_target {
19 9 100   9 1 7754 my $conf = ref( $_[-1] ) eq 'HASH' ? pop(@_) : {};
20              
21             # IF YOU CHANGE THIS CHANGE THE “new_target()” POD SECTION ALSO
22 9         34 $conf->{'exclude_filters'}{'BeginUpper'} = 1; # IF YOU CHANGE THIS CHANGE THE “new_target()” POD SECTION ALSO
23 9         21 $conf->{'exclude_filters'}{'EndPunc'} = 1; # IF YOU CHANGE THIS CHANGE THE “new_target()” POD SECTION ALSO
24              
25             # IF YOU CHANGE THIS CHANGE THE “new_target()” POD SECTION ALSO
26              
27 9         20 push @_, $conf;
28 9         35 goto &new_source;
29             }
30              
31             sub new {
32 2     2 1 3501 Carp::carp('new() is deprecated, use new_source() instead');
33 2         1194 goto &new_source;
34             }
35              
36             sub new_source {
37 76     76 1 721356 my $ns = shift;
38 76 50       1349 $ns = ref($ns) if ref($ns); # just the class ma'am
39              
40 76 100       376 my $conf = ref( $_[-1] ) eq 'HASH' ? pop(@_) : {};
41              
42 76         358 my @filters;
43             my %cr2ns;
44 76         0 my $n; # buffer
45 76         0 my @filternames;
46              
47 76 50       407 for $n ( $conf->{'skip_defaults_when_given_filters'} ? ( @_ ? @_ : @default_filters ) : ( @default_filters, @_ ) ) {
    100          
48 376 50       1538 my $name = $n =~ m/[:']/ ? $n : __PACKAGE__ . "::$n";
49              
50 376 50 66     2018 next if ( exists $conf->{'exclude_filters'}{$n} && $conf->{'exclude_filters'}{$n} ) || ( exists $conf->{'exclude_filters'}{$name} && $conf->{'exclude_filters'}{$name} );
      33        
      66        
51              
52 342 50       983 if ( Module::Want::have_mod($name) ) {
53 342 50       10778 if ( my $cr = $name->can('normalize_maketext_string') ) {
54 342         881 push @filters, $cr;
55 342         1207 $cr2ns{"$cr"} = $name;
56 342         1110 push @filternames, $name;
57             }
58             else {
59 0         0 Carp::carp("$name does not implement normalize_maketext_string()");
60 0         0 return;
61             }
62             }
63             else {
64 0         0 Carp::carp($@);
65 0         0 return;
66             }
67             }
68              
69 76 50       251 if ( !@filters ) {
70 0         0 Carp::carp("Filter list is empty!");
71 0         0 return;
72             }
73              
74 76 50       351 my $run_extra_filters = exists $conf->{'run_extra_filters'} ? ( $conf->{'run_extra_filters'} ? 1 : 0 ) : 0;
    100          
75              
76 76         683 my $new_obj = bless {
77             'filters' => \@filters,
78             'cache' => {},
79             'filter_namespace' => \%cr2ns,
80             'filternames' => \@filternames,
81             'run_extra_filters' => $run_extra_filters,
82             'maketext_object' => undef,
83             }, $ns;
84              
85 76 100       282 if ( exists $conf->{'maketext_object'} ) {
86 3 100       14 $new_obj->set_maketext_object( $conf->{'maketext_object'} ) || return;
87             }
88              
89 74         384 return $new_obj;
90             }
91              
92             sub set_maketext_object {
93 4     4 1 15 my ( $self, $mt_obj ) = @_;
94 4 100       14 if ( ref($mt_obj) ) {
95 3 100       54 if ( $mt_obj->can('makethis') ) {
96 2         10 $self->delete_cache();
97 2         7 $self->{'maketext_object'} = $mt_obj;
98             }
99             else {
100 1         8 Carp::carp('Given maketext object does not have a makethis() method.');
101 1         775 return;
102             }
103             }
104             else {
105 1         6 Carp::carp('Given maketext object is not a reference.');
106 1         479 return;
107             }
108              
109 2         12 return $self->{'maketext_object'};
110             }
111              
112             sub get_maketext_object {
113 93 100   93 1 444 return $_[0]->{'maketext_object'} if defined $_[0]->{'maketext_object'};
114              
115             # Do not delete cache since filters clas call this mid stream
116              
117 15         2041 require Locale::Maketext::Utils::Mock;
118 15         178 $_[0]->{'maketext_object'} = Locale::Maketext::Utils::Mock->get_handle(); # We can't do a class or else we get this sort of thing: Can't use string ("Locale::Maketext::Utils") as a HASH ref while "strict refs" in use at …/Locale/Maketext.pm line N.
119              
120 15         57 return $_[0]->{'maketext_object'};
121             }
122              
123             sub enable_extra_filters {
124 0     0 1 0 $_[0]->delete_cache();
125 0         0 $_[0]->{'run_extra_filters'} = 1;
126             }
127              
128             sub disable_extra_filters {
129 0     0 1 0 $_[0]->delete_cache();
130 0         0 $_[0]->{'run_extra_filters'} = 0;
131             }
132              
133             sub run_extra_filters {
134 0 0   0 1 0 return 1 if $_[0]->{'run_extra_filters'};
135 0         0 return;
136             }
137              
138             sub delete_cache {
139 35     35 1 30123 delete $_[0]->{'cache'};
140             }
141              
142             sub normalize {
143 128     128 1 64342 my ( $self, $string ) = @_;
144              
145 128 100       507 if ( !defined $string ) {
146 4         81 Carp::carp('You must pass a value to normalize()');
147 4         3448 return;
148             }
149              
150 124 50       552 return $self->{'cache'}{$string} if exists $self->{'cache'}{$string};
151              
152 124         1136 $self->{'cache'}{$string} = bless {
153             'status' => 1,
154             'warning_count' => 0,
155             'violation_count' => 0,
156             'filter_results' => [],
157             'orig_str' => $string,
158             'aggregate_result' => $string,
159             },
160             'Locale::Maketext::Utils::Phrase::Norm::_Res';
161              
162 124         298 my $cr; # buffer
163 124         238 foreach $cr ( @{ $self->{'filters'} } ) {
  124         445  
164 790         11171 push @{ $self->{'cache'}{$string}{'filter_results'} }, bless {
165             'status' => 1,
166             'package' => $self->{'filter_namespace'}{"$cr"},
167             'orig_str' => $string,
168             'new_str' => $string,
169             'violations' => [], # status 0
170             'warnings' => [], # status -1 (true but not 1)
171             '_get_mt' => sub {
172 77     77   274 return $self->get_maketext_object();
173             },
174             '_run_extra' => sub {
175 0     0   0 return $self->run_extra_filters();
176             },
177             },
178 790         1428 'Locale::Maketext::Utils::Phrase::Norm::_Res::Filter';
179              
180 790         4276 my ( $filter_rc, $violation_count, $warning_count, $filter_modifies_string ) = $cr->( $self->{'cache'}{$string}{'filter_results'}[-1] );
181              
182             # Update string's overall aggregate modifcation
183 790 100       1959 if ($filter_modifies_string) {
184              
185             # Run aggregate value through filter, not perfect since it isn't operating on the same value as above
186             my $agg_filt = bless {
187             'status' => 1,
188             'package' => $self->{'filter_namespace'}{"$cr"},
189             'orig_str' => $self->{'cache'}{$string}{'aggregate_result'},
190             'new_str' => $self->{'cache'}{$string}{'aggregate_result'},
191             'violations' => [], # status 0
192             'warnings' => [], # status -1 (true but not 1)
193             '_get_mt' => sub {
194 13     13   45 return $self->get_maketext_object();
195             },
196             '_run_extra' => sub {
197 0     0   0 return $self->run_extra_filters();
198             },
199             },
200 81         1654 'Locale::Maketext::Utils::Phrase::Norm::_Res::Filter';
201              
202 81         390 $cr->($agg_filt);
203 81         242 $self->{'cache'}{$string}{'aggregate_result'} = $agg_filt->get_new_str();
204             }
205              
206             # Update string's overall result
207 790         3135 $self->{'cache'}{$string}{'violation_count'} += $violation_count;
208 790         1618 $self->{'cache'}{$string}{'warning_count'} += $warning_count;
209 790 100       2316 if ( $self->{'cache'}{$string}->{'status'} ) {
210 706 100       6162 if ( !$filter_rc ) {
    100          
211 39         102 $self->{'cache'}{$string}{'status'} = $filter_rc;
212             }
213             elsif ( $self->{'cache'}{$string}->{'status'} != -1 ) {
214 599         1304 $self->{'cache'}{$string}{'status'} = $filter_rc;
215             }
216             }
217              
218 790 50 66     4835 last if !$filter_rc && $self->{'stop_filter_on_error'}; # TODO: document, add POD, methods, new_source(), tests etc.
219             }
220              
221 124         561 return $self->{'cache'}{$string};
222             }
223              
224             package Locale::Maketext::Utils::Phrase::Norm::_Res;
225              
226             sub get_status {
227 116     116   2122 return $_[0]->{'status'};
228             }
229              
230             sub get_warning_count {
231 114     114   778 return $_[0]->{'warning_count'};
232             }
233              
234             sub get_violation_count {
235 114     114   755 return $_[0]->{'violation_count'};
236             }
237              
238             sub get_filter_results {
239 66     66   281 return $_[0]->{'filter_results'};
240             }
241              
242             sub get_orig_str {
243 66     66   498 return $_[0]->{'orig_str'};
244             }
245              
246             sub get_aggregate_result {
247 70   33 70   758 return $_[0]->{'aggregate_result'} || $_[0]->{'orig_str'};
248             }
249              
250             sub filters_modify_string {
251 114 100   114   951 return 1 if $_[0]->{'aggregate_result'} ne $_[0]->{'orig_str'};
252 44         260 return;
253             }
254              
255             package Locale::Maketext::Utils::Phrase::Norm::_Res::Filter;
256              
257             sub run_extra_filters {
258 0     0   0 return $_[0]->{'_run_extra'}->();
259             }
260              
261             sub get_maketext_object {
262 90     90   315 return $_[0]->{'_get_mt'}->();
263             }
264              
265             sub add_violation {
266 188     188   466 my ( $self, $error ) = @_;
267 188         333 $self->{'status'} = 0;
268 188         313 push @{ $self->{'violations'} }, $error;
  188         631  
269             }
270              
271             sub add_warning {
272 148     148   408 my ( $self, $warning ) = @_;
273 148 50       424 $self->{'status'} = -1 if !$self->get_violations();
274 148         272 push @{ $self->{'warnings'} }, $warning;
  148         544  
275             }
276              
277             sub get_status {
278 66     66   507 return $_[0]->{'status'};
279             }
280              
281             sub get_package {
282 66     66   1529 return $_[0]->{'package'};
283             }
284              
285             sub get_orig_str {
286 235     235   1246 return $_[0]->{'orig_str'};
287             }
288              
289             sub get_new_str {
290 147     147   1433 return $_[0]->{'new_str'};
291             }
292              
293             sub get_violations {
294 1409 100   1409   2318 return if !@{ $_[0]->{'violations'} };
  1409         6081  
295 384         1606 return $_[0]->{'violations'};
296             }
297              
298             sub get_warnings {
299 1269 100   1269   2079 return if !@{ $_[0]->{'warnings'} };
  1269         4722  
300 400         1503 return $_[0]->{'warnings'};
301             }
302              
303             sub get_string_sr {
304 742     742   2097 return \$_[0]->{'new_str'};
305             }
306              
307             sub get_warning_count {
308 1003 100   1003   2646 return $_[0]->get_warnings() ? scalar( @{ $_[0]->get_warnings() } ) : 0;
  166         4428  
309             }
310              
311             sub get_violation_count {
312 1003 100   1003   3972 return $_[0]->get_violations() ? scalar( @{ $_[0]->get_violations() } ) : 0;
  156         401  
313             }
314              
315             sub return_value {
316 937     937   2175 my ($self) = @_;
317 937         2641 return ( $self->{'status'}, $self->get_violation_count(), $self->get_warning_count(), $self->filter_modifies_string() );
318             }
319              
320             sub return_value_noop {
321 0     0   0 return ( 2, 0, 0, 0 );
322             }
323              
324             sub filter_modifies_string {
325 1003 100   1003   4785 return 1 if $_[0]->{'orig_str'} ne $_[0]->{'new_str'};
326 727         3040 return;
327             }
328              
329             1;
330              
331             __END__
332              
333             =encoding utf-8
334              
335             =head1 NAME
336              
337             Locale::Maketext::Utils::Phrase::Norm - Normalize and perform lint-like analysis of phrases
338              
339             =head1 VERSION
340              
341             This document describes Locale::Maketext::Utils::Phrase::Norm version 0.2
342              
343             =head1 SYNOPSIS
344              
345             use Locale::Maketext::Utils::Phrase::Norm;
346              
347             my $norm = Locale::Maketext::Utils::Phrase::Norm->new_source() || die;
348              
349             my $result = $norm->normalize('This office has worked [quant,_1,day,days,zero days] without an “accident”.');
350              
351             # process $result
352              
353             =head1 DESCRIPTION
354              
355             Analyze, report, and normalize a maketext style phrase based on rules organized into filter modules.
356              
357             =head1 INTERFACE
358              
359             =head2 Main object
360              
361             =head3 new_source()
362              
363             A “source phrase” is a phrase (suitable for a call to maketext()) in the main locale’s language that we intend to be localizable.
364              
365             Typically this is the key of a lexicon’s hash but it can be the value if the main locale lexicon’s key is an “Arbitrary Key”, that is, if the value is different from the key in the main locale’s lexicon.
366              
367             new_source() creates a new object with all the filters initialized for source phrases.
368              
369             Giving no arguments means it will employ all of the default filter modules (documented in L</"DEFAULT FILTERS">).
370              
371             Otherwise the optional arguments are:
372              
373             =over 4
374              
375             =item A list of filter module name spaces to run after the default filter modules.
376              
377             If the given module name does not contain any package seperators it will be treated as if it needs prepended with 'Locale::Maketext::Utils::Phrase::Norm::'.
378              
379             e.g. Given 'Locale::Maketext::Utils::Phrase::Norm::MyCoolFilter' you can pass the name 'MyCoolFilter'.
380              
381             =item The last argument can be a hashref of options:
382              
383             my $norm = Locale::Maketext::Utils::Phrase::Norm->new_source('My::Filter::XYZ'); # all default filters followed by the My::Filter::XYZ filter
384              
385             my $norm = Locale::Maketext::Utils::Phrase::Norm->new_source('My::Filter::XYZ', { 'skip_defaults_when_given_filters' => 1 }); # only do My::Filter::XYZ the filter
386              
387             The options are outlined below and are all optional:
388              
389             =over 4
390              
391             =item 'skip_defaults_when_given_filters'
392              
393             Boolean.
394              
395             When false (default) your filters are appended to the list of default filters.
396              
397             When true the default list of filters is not used.
398              
399             =item 'maketext_object'
400              
401             An object that can be used by filters should they need one to perform their task. Currently, it must have a makethis() method.
402              
403             The main object and filter each have a L<get_maketext_object()> method to fetch this when needed.
404              
405             If you did not specify an argument here L<get_maketext_object()> returns a L<Locale::Maketext::Utils::Mock> object. That means all the cool stuff in your locale object that you might want to use in your filter will not be available.
406              
407             =item 'run_extra_filters'
408              
409             Boolean.
410              
411             When false (default) L</extra filters> are not executed.
412              
413             When true the L</extra filters> are executed.
414              
415             =item 'exclude_filters'
416              
417             A hashref of filters to exclude from the object regardless of the list in effect.
418              
419             The key can be the long or short name space of the filter modules and the value must be true for the entry to take effect.
420              
421             =back
422              
423             =back
424              
425             new_source() carp()s and returns false if there is some sort of failure (documented in L</"DIAGNOSTICS">).
426              
427             =head3 new_target()
428              
429             A “target phrase” is the translated version of a given source phrase. This is the value of a non-main-locale lexicon’s hash.
430              
431             new_target() is just like new_source() but uses a subset of the L</"DEFAULT FILTERS"> that apply to translations.
432              
433             Currently the exclusion of L<BeginUpper|Locale::Maketext::Utils::Phrase::Norm::BeginUpper> and L<EndPunc|Locale::Maketext::Utils::Phrase::Norm::EndPunc> from the L</"DEFAULT FILTERS"> is what makes up this object.
434              
435             =head3 normalize()
436              
437             Takes a phrase as the only argument and returns a result object (documented in L</"Result Object">).
438              
439             =head3 delete_cache()
440              
441             The result of normalize() is cached internally so calling it subsequent times with the same string won’t result in it being reprocessed.
442              
443             This method deletes the internal cache. Returns the hashref that was removed.
444              
445             =head3 get_maketext_object()
446              
447             Returns the object you instantiated the L</"Main object"> with.
448              
449             If you did not specify an argument a L<Locale::Maketext::Utils::Mock> object is used. That means all the cool stuff in your locale object that you might want to use in your filter will not be available.
450              
451             =head3 set_maketext_object()
452              
453             Takes the same object you’d pass to the constructor method via ‘maketext_object’.
454              
455             This is what will be used on subsequent calls to normalize().
456              
457             =head3 run_extra_filters()
458              
459             Boolean return value of if we are running L</extra filters> or not.
460              
461             =head3 enable_extra_filters()
462              
463             No arguments, enables the running of any L</extra filters> on subsequent calls to normalize().
464              
465             =head3 disable_extra_filters()
466              
467             No arguments, disables the running of any L</extra filters> on subsequent calls to normalize().
468              
469             =head2 Result Object
470              
471             =head3 get_status()
472              
473             Returns the status of all the filters:
474              
475             =over 4
476              
477             =item True means no violations
478              
479             =item -1 (i.e. still true) means there were warnings but no violations.
480              
481             =item False means there was at least one violation and possibly warnings.
482              
483             =back
484              
485             =head3 get_warning_count()
486              
487             Return the number of warnings from all filters combined.
488              
489             =head3 get_violation_count()
490              
491             Return the number of violations from all filters combined.
492              
493             =head3 get_filter_results()
494              
495             Return an array ref of filter result objects (documented in L</"Filter Result Object">).
496              
497             =head3 get_orig_str()
498              
499             Get the phrase as passed in before any modifications by filters.
500              
501             =head3 get_aggregate_result()
502              
503             Get the phrase after all filters had a chance to modify it.
504              
505             =head3 filters_modify_string()
506              
507             Returns true if any of the filters resulted in a string different from what you passed it. False otherwise.
508              
509             =head2 Filter Result Object
510              
511             =head3 Intended for use in a filter module.
512              
513             See L</"ANATOMY OF A FILTER MODULE"> for more info.
514              
515             =head4 add_violation()
516              
517             Add a violation.
518              
519             =head4 add_warning()
520              
521             Add a warning.
522              
523             =head4 get_string_sr()
524              
525             Returns a SCALAR reference to the modified version of the string that the filter can use to modify the string.
526              
527             =head4 return_value()
528              
529             returns an array of the status, violation count, warning count, and filter_modifies_string().
530              
531             It is what the filter’s normalize_maketext_string() should return;
532              
533             =head4 get_maketext_object()
534              
535             Returns the object you instantiated the L</"Main object"> with.
536              
537             If you did not specify an argument a L<Locale::Maketext::Utils::Mock> object is used. That means all the cool stuff in your locale object that you might want to use in your filter will not be available.
538              
539             =head4 run_extra_filters()
540              
541             Returns a boolean value of if we are running extra filters or not.
542              
543             if ( $filter->run_extra_filters() ) {
544             # do extra check for violations/warnings here
545             }
546              
547             You can use this to check if the filter should run certain tests or not. You can even skip an entire filter by use of L<return_value_noop()>.
548              
549             =head4 return_value_noop()
550              
551             Get an appropriate L<return_value()> for when the entire filter falls under the category of L</extra filters>.
552              
553             return $filter->return_value_noop() if !$filter->run_extra_filters();
554              
555             =head3 Intended for use when processing results.
556              
557             These can be used in a filter module’s filter code if you find use for them there. See L</"ANATOMY OF A FILTER MODULE"> for more info.
558              
559             =head4 get_status()
560              
561             Returns the status of the filter:
562              
563             =over 4
564              
565             =item True means no violations
566              
567             =item -1 (i.e. still true) means there were warnings but no violations.
568              
569             =item False means there was at least one violation and possibly warnings.
570              
571             =back
572              
573             =head4 get_package()
574              
575             Get the current filter’s package.
576              
577             =head4 get_orig_str()
578              
579             Get the phrase as passed in before any modifications by the filter.
580              
581             =head4 get_new_str()
582              
583             Get the phrase after the filter had a chance to modify it.
584              
585             =head4 get_violations()
586              
587             Return an array ref of violations added via add_violation().
588              
589             If there are no violations it returns false.
590              
591             =head4 get_warnings()
592              
593             Return an array ref of violations added via add_warning().
594              
595             If there are no warnings it returns false.
596              
597             =head4 get_warning_count()
598              
599             Returns the number of warnings the filter resulted in.
600              
601             =head4 get_violation_count()
602              
603             Returns the number of violations the filter resulted in.
604              
605             =head4 filter_modifies_string()
606              
607             Returns true if the filter resulted in a string different from what you passed it. False otherwise.
608              
609             =head1 DEFAULT FILTERS
610              
611             The included default filters are listed below in the order they are executed by default.
612              
613             =over 4
614              
615             =item L<NonBytesStr|Locale::Maketext::Utils::Phrase::Norm::NonBytesStr>
616              
617             =item L<WhiteSpace|Locale::Maketext::Utils::Phrase::Norm::WhiteSpace>
618              
619             =item L<Grapheme|Locale::Maketext::Utils::Phrase::Norm::Grapheme>
620              
621             =item L<Ampersand|Locale::Maketext::Utils::Phrase::Norm::Ampersand>
622              
623             =item L<Markup|Locale::Maketext::Utils::Phrase::Norm::Markup>
624              
625             =item L<Ellipsis|Locale::Maketext::Utils::Phrase::Norm::Ellipsis>
626              
627             =item L<BeginUpper|Locale::Maketext::Utils::Phrase::Norm::BeginUpper>
628              
629             =item L<EndPunc|Locale::Maketext::Utils::Phrase::Norm::EndPunc>
630              
631             =item L<Consider|Locale::Maketext::Utils::Phrase::Norm::Consider>
632              
633             =item L<Escapes|Locale::Maketext::Utils::Phrase::Norm::Escapes>
634              
635             =item L<Compiles|Locale::Maketext::Utils::Phrase::Norm::Compiles>
636              
637             =back
638              
639             =head2 extra filters
640              
641             It may be desireable for some filters to not run by default but still be easily applied when needed.
642              
643             The extra filter mechanism allows for this as documented specifically throught this POD.
644              
645             No filters fall under L</extra filters> currently.
646              
647             =head1 ANATOMY OF A FILTER MODULE
648              
649             A filter module is simply a package that defines a function that does the filtering of the phrase.
650              
651             =head2 normalize_maketext_string()
652              
653             This gets passed a single argument: the L</"Filter Result Object"> that defines data about the phrase.
654              
655             That object can be used to do the actual checks, modifications if any, and return the expected info back (via $filter->return_value).
656              
657             package My::Phrase::Filter::X;
658              
659             sub normalize_maketext_string {
660             my ($filter) = @_;
661              
662             my $string_sr = $filter->get_string_sr();
663              
664             if (${$string_sr} =~ s/X/[comment,unexpected X]/g) {
665             $filter->add_warning('X might be invalid might wanna check that');
666             # or
667             # $filter->add_violation('Text of violation here');
668             }
669              
670             return $filter->return_value;
671             }
672              
673             1;
674              
675             It’s a good idea to explain the filter in it’s POD. Check out L<_Stub|Locale::Maketext::Utils::Phrase::Norm::_Stub> for some boilerplate.
676              
677             =head1 DIAGNOSTICS
678              
679             =over
680              
681             =item C<< %s does not implement normalize_maketext_string() >>
682              
683             The constructor method was able to load the filter %s but that class does not have a normalize_maketext_string() method.
684              
685             =item C<< Can't locate %s.pm in @INC … >>
686              
687             The constructor method was not able to load the filter %s, the actual error comes from perl via $@ from L<Module::Want>
688              
689             =item C<< Filter list is empty! >>
690              
691             After all initialization and no other errors the list of filters is somehow empty.
692              
693             =item C<< Given maketext object does not have a makethis() method. >>
694              
695             The value of the maketext_object key you passed to the constructor method or the value passed to set_maketext_object() does not define a makethis() method.
696              
697             =item C<< Given maketext object is not a reference. >>
698              
699             The value of the maketext_object key you passed to the constructor method or the value passed to set_maketext_object() is not an object.
700              
701             =item C<< new() is deprecated, use new_source() instead >>
702              
703             Your code uses the deprecated constructor and needs to be updated.
704              
705             =item C<< You must pass a value to normalize() >>
706              
707             Your code called normalize() without giving it a value to, well, normalize.
708              
709             =back
710              
711             =head1 CONFIGURATION AND ENVIRONMENT
712              
713             Locale::Maketext::Utils::Phrase::Norm requires no configuration files or environment variables.
714              
715             =head1 DEPENDENCIES
716              
717             L<Module::Want>, L<Encode> (for the L<WhiteSpace|Locale::Maketext::Utils::Phrase::Norm::WhiteSpace> filter)
718              
719             =head1 INCOMPATIBILITIES
720              
721             None reported.
722              
723             =head1 BUGS AND LIMITATIONS
724              
725             No bugs have been reported.
726              
727             Please report any bugs or feature requests to
728             C<bug-locale-maketext-utils@rt.cpan.org>, or through the web interface at
729             L<http://rt.cpan.org>.
730              
731             =head1 SEE ALSO
732              
733             L<Locale::Maketext::Utils::Phrase::cPanel>
734              
735             =head1 AUTHOR
736              
737             Daniel Muey C<< <http://drmuey.com/cpan_contact.pl> >>
738              
739             =head1 LICENCE AND COPYRIGHT
740              
741             Copyright (c) 2012 cPanel, Inc. C<< <copyright@cpanel.net>> >>. All rights reserved.
742              
743             This library is free software; you can redistribute it and/or modify it under
744             the same terms as Perl itself, either Perl version 5.10.1 or, at your option,
745             any later version of Perl 5 you may have available.
746              
747             =head1 DISCLAIMER OF WARRANTY
748              
749             BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
750             FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
751             OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
752             PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER
753             EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
754             WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
755             ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH
756             YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL
757             NECESSARY SERVICING, REPAIR, OR CORRECTION.
758              
759             IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
760             WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
761             REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE
762             LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL,
763             OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE
764             THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
765             RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
766             FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
767             SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
768             SUCH DAMAGES.