File Coverage

blib/lib/Data/Sah/Compiler/human.pm
Criterion Covered Total %
statement 211 248 85.0
branch 82 108 75.9
condition 54 69 78.2
subroutine 28 29 96.5
pod 6 13 46.1
total 381 467 81.5


line stmt bran cond sub pod time code
1              
2             use 5.010;
3 24     24   356 use strict;
  24         72  
4 24     24   111 use warnings;
  24         40  
  24         433  
5 24     24   93 #use Log::Any::IfLOG qw($log);
  24         49  
  24         694  
6              
7             use Data::Dmp qw(dmp);
8 24     24   981 use Mo qw(build default);
  24         3685  
  24         1237  
9 24     24   121 use POSIX qw(locale_h);
  24         46  
  24         115  
10 24     24   5679 use Text::sprintfn;
  24         47  
  24         182  
11 24     24   39701  
  24         19717  
  24         9776  
12             extends 'Data::Sah::Compiler';
13              
14             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
15             our $DATE = '2022-10-19'; # DATE
16             our $DIST = 'Data-Sah'; # DIST
17             our $VERSION = '0.914'; # VERSION
18              
19             # every type extension is registered here
20             our %typex; # key = type, val = [clause, ...]
21              
22              
23 10335     10335 0 21254 my ($self, $cd, $msg) = @_;
24             return unless $cd->{args}{format} eq 'msg_catalog';
25              
26 9615     9615   16015 my $spath = join("/", @{ $cd->{spath} });
27 9615 100       24898 $cd->{_msg_catalog}{$spath} = $msg;
28             }
29 9572         12690  
  9572         20572  
30 9572         51202 my ($self, $args) = @_;
31              
32             $self->SUPER::check_compile_args($args);
33              
34 5070     5070 0 10005 my @fmts = ('inline_text', 'inline_err_text', 'markdown', 'msg_catalog');
35             $args->{format} //= $fmts[0];
36 5070         14972 unless (grep { $_ eq $args->{format} } @fmts) {
37             $self->_die({}, "Unsupported format, use one of: ".join(", ", @fmts));
38 5070         13371 }
39 5070   33     13228 }
40 5070 50       9311  
  20280         41448  
41 0         0 my ($self, %args) = @_;
42              
43             my $cd = $self->SUPER::init_cd(%args);
44             if (($cd->{args}{format} // '') eq 'msg_catalog') {
45             $cd->{_msg_catalog} //= $cd->{outer_cd}{_msg_catalog};
46 5070     5070 0 43027 $cd->{_msg_catalog} //= {};
47             }
48 5070         34125 $cd;
49 5070 100 50     24310 }
50 5046   66     23708  
51 5046   100     15437 my ($self, $cd, $expr) = @_;
52              
53 5070         21641 # for now we dump expression as is. we should probably parse it first to
54             # localize number, e.g. "1.1 + 2" should become "1,1 + 2" in id_ID.
55              
56             # XXX for nicer output, perhaps say "the expression X" instead of just "X",
57 4     4 0 6 # especially if X has a variable or rather complex.
58             $expr;
59             }
60              
61             my ($self, $val) = @_;
62              
63             return $val unless ref($val);
64 4         9 dmp($val);
65             }
66              
67             # translate
68 12821     12821 0 55910 my ($self, $cd, $text) = @_;
69              
70 12821 100       36032 my $lang = $cd->{args}{lang};
71 6048         15185  
72             #$log->tracef("translating text '%s' to '%s'", $text, $lang);
73              
74             return $text if $lang eq 'en_US';
75             my $translations;
76 98035     98035   136367 {
77             no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
78 98035         126494 $translations = \%{"Data::Sah::Lang::$lang\::translations"};
79             }
80             return $translations->{$text} if defined($translations->{$text});
81             if ($cd->{args}{mark_missing_translation}) {
82 98035 100       254147 return "(no $lang text:$text)";
83 36         39 } else {
84             return $text;
85 24     24   169 }
  24         52  
  24         4157  
  36         39  
86 36         39 }
  36         94  
87              
88 36 50       105 # ($cd, 3, "element") -> "3rd element"
89 0 0       0 my ($self, $cd, $n, $noun) = @_;
90 0         0  
91             my $lang = $cd->{args}{lang};
92 0         0  
93             # we assume _xlt() has been called (and thus the appropriate
94             # Data::Sah::Lang::* has been loaded)
95              
96             if ($lang eq 'en_US') {
97             require Lingua::EN::Numbers::Ordinate;
98 62     62   143 return Lingua::EN::Numbers::Ordinate::ordinate($n) . " $noun";
99             } else {
100 62         97 no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
101             return "Data::Sah::Lang::$lang\::ordinate"->($n, $noun);
102             }
103             }
104              
105 62 100       292 my ($self, $cd, $ccl) = @_;
106 61         2270 #$log->errorf("TMP: add_ccl %s", $ccl);
107 61         1164  
108             $ccl->{xlt} //= 1;
109 24     24   180  
  24         60  
  24         51013  
110 1         5 my $clause = $cd->{clause} // "";
111             $ccl->{type} //= "clause";
112              
113             my $do_xlt = 1;
114              
115 9589     9589   15504 my $hvals = {
116             modal_verb => $self->_xlt($cd, "must"),
117             modal_verb_neg => $self->_xlt($cd, "must not"),
118 9589   100     40659  
119             # so they can overriden through hash_values
120 9589   100     24649 field => $self->_xlt($cd, "field"),
121 9589   100     27126 fields => $self->_xlt($cd, "fields"),
122              
123 9589         15302 %{ $cd->{args}{hash_values} // {} },
124             };
125             my $mod="";
126              
127             # is .human for desired language specified? if yes, use that instead
128              
129             {
130             my $lang = $cd->{args}{lang};
131             my $dlang = $cd->{clset_dlang} // "en_US"; # undef if not in clause
132             my $suffix = $lang eq $dlang ? "" : ".alt.lang.$lang";
133 9589   50     20761 if ($clause) {
  9589         56691  
134             delete $cd->{uclset}{$_} for
135 9589         23105 grep {/\A\Q$clause.human\E(\.|\z)/} keys %{$cd->{uclset}};
136             if (defined $cd->{clset}{"$clause.human$suffix"}) {
137             $ccl->{type} = 'clause';
138             $ccl->{fmt} = $cd->{clset}{"$clause.human$suffix"};
139             goto FILL_FORMAT;
140 9589         14853 }
  9589         14466  
141 9589   100     28205 } else {
142 9589 100       20014 delete $cd->{uclset}{$_} for
143 9589 100       19003 grep {/\A\.name(\.|\z)/} keys %{$cd->{uclset}};
144 4548         7075 if (defined $cd->{clset}{".name$suffix"}) {
145 261         2284 $ccl->{type} = 'noun';
  4548         14446  
146 4548 50       16874 $ccl->{fmt} = $cd->{clset}{".name$suffix"};
147 0         0 $ccl->{vals} = undef;
148 0         0 goto FILL_FORMAT;
149 0         0 }
150             }
151             }
152 5041         6933  
153 0         0 goto TRANSLATE unless $clause;
  5041         15447  
154 5041 50       20430  
155 0         0 my $ie = $cd->{cl_is_expr};
156 0         0 my $im = $cd->{cl_is_multi};
157 0         0 my $op = $cd->{cl_op} // "";
158 0         0 my $cv = $cd->{clset}{$clause};
159             my $vals = $ccl->{vals} // [$cv];
160              
161             # handle .is_expr
162              
163 9589 100       28061 if ($ie) {
164             if (!$ccl->{expr}) {
165 4548         7310 $ccl->{fmt} = "($clause -> %s" . ($op ? " op=$op" : "") . ")";
166 4548         7167 $do_xlt = 0;
167 4548   100     11578 $vals = [$self->expr($cd, $vals)];
168 4548         7246 }
169 4548   100     14255 goto ERR_LEVEL;
170             }
171              
172             # handle .op
173 4548 100       10328  
174 4 50       7 if ($op eq 'not') {
175 0 0       0 ($hvals->{modal_verb}, $hvals->{modal_verb_neg}) =
176 0         0 ($hvals->{modal_verb_neg}, $hvals->{modal_verb});
177 0         0 $vals = [map {$self->literal($_)} @$vals];
178             } elsif ($im && $op eq 'and') {
179 4         17 if (@$cv == 2) {
180             $vals = [sprintf($self->_xlt($cd, "%s and %s"),
181             $self->literal($cv->[0]),
182             $self->literal($cv->[1]))];
183             } else {
184 4544 100 100     22619 $vals = [sprintf($self->_xlt($cd, "all of %s"),
    100 100        
    100 66        
    100          
185             $self->literal($cv))];
186 305         952 }
187 305         752 } elsif ($im && $op eq 'or') {
  377         789  
188             if (@$cv == 2) {
189 559 100       1203 $vals = [sprintf($self->_xlt($cd, "%s or %s"),
190 450         1086 $self->literal($cv->[0]),
191             $self->literal($cv->[1]))];
192             } else {
193             $vals = [sprintf($self->_xlt($cd, "one of %s"),
194 109         274 $self->literal($cv))];
195             }
196             } elsif ($im && $op eq 'none') {
197             ($hvals->{modal_verb}, $hvals->{modal_verbneg}) =
198 558 100       1393 ($hvals->{modal_verb_neg}, $hvals->{modal_verb});
199 449         1159 if (@$cv == 2) {
200             $vals = [sprintf($self->_xlt($cd, "%s nor %s"),
201             $self->literal($cv->[0]),
202             $self->literal($cv->[1]))];
203 109         316 } else {
204             $vals = [sprintf($self->_xlt($cd, "any of %s"),
205             $self->literal($cv))];
206             }
207             } else {
208 270         788 $vals = [map {$self->literal($_)} @$vals];
209 270 100       695 }
210 216         587  
211             ERR_LEVEL:
212              
213             # handle .err_level
214 54         124 if ($ccl->{type} eq 'clause' && grep { $_ eq 'constraint' } @{ $cd->{cl_meta}{tags} // [] }) {
215             if (($cd->{clset}{"$clause.err_level"}//'error') eq 'warn') {
216             if ($op eq 'not') {
217             $hvals->{modal_verb} = $self->_xlt($cd, "should not");
218 2852         5318 $hvals->{modal_verb_neg} = $self->_xlt($cd, "should");
  3892         6901  
219             } else {
220             $hvals->{modal_verb} = $self->_xlt($cd, "should");
221             $hvals->{modal_verb_neg} = $self->_xlt($cd, "should not");
222             }
223             }
224 4548 100 50     93397 }
  4277   100     16584  
  4277         12705  
225 4187 100 100     18060 delete $cd->{uclset}{"$clause.err_level"};
226 54 50       209  
227 0         0 TRANSLATE:
228 0         0  
229             if ($ccl->{xlt}) {
230 54         120 if (ref($ccl->{fmt}) eq 'ARRAY') {
231 54         108 $ccl->{fmt} = [map {$self->_xlt($cd, $_)} @{$ccl->{fmt}}];
232             } elsif (!ref($ccl->{fmt})) {
233             $ccl->{fmt} = $self->_xlt($cd, $ccl->{fmt});
234             }
235 4548         9940 }
236              
237             FILL_FORMAT:
238              
239 9589 100       22730 if (ref($ccl->{fmt}) eq 'ARRAY') {
240 9586 100       26651 $ccl->{text} = [map {sprintfn($_, (map {$_//""} ($hvals, @$vals)))}
    50          
241 5041         8658 @{$ccl->{fmt}}];
  10082         19238  
  5041         9432  
242             } elsif (!ref($ccl->{fmt})) {
243 4545         11521 $ccl->{text} = sprintfn($ccl->{fmt}, (map {$_//""} ($hvals, @$vals)));
244             }
245             delete $ccl->{fmt} unless $cd->{args}{debug};
246              
247             PUSH:
248             push @{$cd->{ccls}}, $ccl;
249 9589 100       25928  
    50          
250 10082   50     198004 $self->_add_msg_catalog($cd, $ccl);
  10082         33157  
251 5041         7176 }
  5041         8378  
252              
253 4548   100     9337 # add a compiled clause (ccl), which will be combined at the end of compilation
  10208         26346  
254             # to be the final result. args is a hashref with these keys:
255 9589 50       615517 #
256             # * type* - str (default 'clause'). either 'noun', 'clause', 'list' (bulleted
257             # list, a clause followed by a list of items, each of them is also a ccl)
258 9589         13572 #
  9589         18180  
259             # * fmt* - str/2-element array. human text which can be used as the first
260 9589         23587 # argument to sprintf. string. if type=noun, can be a two-element arrayref to
261             # contain singular and plural version of noun.
262             #
263             # * expr - bool. fmt can handle .is_expr=1. for example, 'len=' => '1+1' can be
264             # compiled into 'length must be 1+1'. other clauses cannot handle expression,
265             # e.g. 'between=' => '[2, 2*2]'. this clause will be using the generic message
266             # 'between must [2, 2*2]'
267             #
268             # * vals - arrayref (default [clause value]). values to fill fmt with.
269             #
270             # * items - arrayref. required if type=list. a single ccl or a list of ccls.
271             #
272             # * xlt - bool (default 1). set to 0 if fmt has been translated, and should not
273             # be translated again.
274             #
275             # add_ccl() is called by clause handlers and handles using .human, translating
276             # fmt, sprintf(fmt, vals) into 'text', .err_level (adding 'must be %s', 'should
277             # not be %s'), .is_expr, .op.
278             my ($self, $cd, @ccls) = @_;
279              
280             my $op = $cd->{cl_op} // '';
281              
282             my $ccl;
283             if (@ccls == 1) {
284             $self->_add_ccl($cd, $ccls[0]);
285             } else {
286             my $inner_cd = $self->init_cd(outer_cd => $cd);
287             $inner_cd->{args} = $cd->{args};
288             $inner_cd->{clause} = $cd->{clause};
289 9589     9589 0 21667 for (@ccls) {
290             $self->_add_ccl($inner_cd, $_);
291 9589   100     29781 }
292              
293 9589         12257 $ccl = {
294 9589 50       20092 type => 'list',
295 9589         22362 vals => [],
296             items => $inner_cd->{ccls},
297 0         0 multi => 0,
298 0         0 };
299 0         0 if ($op eq 'or') {
300 0         0 $ccl->{fmt} = 'any of the following %(modal_verb)s be true';
301 0         0 } elsif ($op eq 'and') {
302             $ccl->{fmt} = 'all of the following %(modal_verb)s be true';
303             } elsif ($op eq 'none') {
304             $ccl->{fmt} = 'none of the following %(modal_verb)s be true';
305             # or perhaps, fmt = 'All of the following ...' but set op to 'not'?
306             }
307             $self->_add_ccl($cd, $ccl);
308 0         0 }
309             }
310 0 0       0  
    0          
    0          
311 0         0 # format ccls to form final result. at the end of compilation, we have a tree of
312             # ccls. this method accept a single ccl (of type either noun/clause) or an array
313 0         0 # of ccls (which it will join together).
314             my ($self, $cd, $ccls) = @_;
315 0         0  
316             # used internally to determine if the result is a single noun, in which case
317             # when format is inline_err_text, we add 'Not of type '. XXX: currently this
318 0         0 # is the wrong way to count? we shouldn't count children? perhaps count from
319             # msg_catalog instead?
320             local $cd->{_fmt_noun_count} = 0;
321             local $cd->{_fmt_etc_count} = 0;
322              
323             my $f = $cd->{args}{format};
324             my $res;
325             if ($f eq 'inline_text' || $f eq 'inline_err_text' || $f eq 'msg_catalog') {
326 10822     10822 0 18521 $res = $self->_format_ccls_itext($cd, $ccls);
327             if ($f eq 'inline_err_text') {
328             #$log->errorf("TMP: noun=%d, etc=%d", $cd->{_fmt_noun_count}, $cd->{_fmt_etc_count});
329             if ($cd->{_fmt_noun_count} == 1 && $cd->{_fmt_etc_count} == 0) {
330             # a single noun (type name), we should add some preamble
331             $res = sprintf(
332 10822         20414 $self->_xlt($cd, "Not of type %s"),
333 10822         16674 $res
334             );
335 10822         15953 } elsif (!$cd->{_fmt_noun_count}) {
336 10822         14310 # a clause (e.g. "must be >= 10"), already looks like errmsg
337 10822 50 100     50156 } else {
      66        
338 10822         22108 # a noun + clauses (e.g. "integer, must be even"). add preamble
339 10822 100       23568 $res = sprintf(
340             $self->_xlt(
341 5755 100 100     20959 $cd, "Does not satisfy the following schema: %s"),
    100          
342             $res
343 221         565 );
344             }
345             }
346             } else {
347             $res = $self->_format_ccls_markdown($cd, $ccls);
348             }
349             $res;
350             }
351 138         285  
352             my ($self, $cd, $ccls) = @_;
353              
354             local $cd->{args}{mark_missing_translation} = 0;
355             my $c_comma = $self->_xlt($cd, ", ");
356              
357             if (ref($ccls) eq 'HASH' && $ccls->{type} =~ /^(noun|clause)$/) {
358             if ($ccls->{type} eq 'noun') {
359 0         0 $cd->{_fmt_noun_count}++;
360             } else {
361 10822         45296 $cd->{_fmt_etc_count}++;
362             }
363             # handle a single noun/clause ccl
364             my $ccl = $ccls;
365 21957     21957   31533 return ref($ccl->{text}) eq 'ARRAY' ? $ccl->{text}[0] : $ccl->{text};
366             } elsif (ref($ccls) eq 'HASH' && $ccls->{type} eq 'list') {
367 21957         38329 # handle a single list ccl
368 21957         36039 my $c_openpar = $self->_xlt($cd, "(");
369             my $c_closepar = $self->_xlt($cd, ")");
370 21957 100 100     110062 my $c_colon = $self->_xlt($cd, ": ");
    100 66        
    50          
371 15703 100       28346 my $ccl = $ccls;
372 5731         9423  
373             my $txt = $ccl->{text}; $txt =~ s/\s+$//;
374 9972         13833 my @t = ($txt, $c_colon);
375             my $i = 0;
376             for (@{ $ccl->{items} }) {
377 15703         19434 push @t, $c_comma if $i;
378 15703 100       63072 my $it = $self->_format_ccls_itext($cd, $_);
379             if ($it =~ /\Q$c_comma/) {
380             push @t, $c_openpar, $it, $c_closepar;
381 551         1279 } else {
382 551         1044 push @t, $it;
383 551         1161 }
384 551         772 $i++;
385             }
386 551         1709 return join("", @t);
  551         1655  
387 551         1030 } elsif (ref($ccls) eq 'ARRAY') {
388 551         772 # handle an array of ccls
389 551         747 return join($c_comma, map {$self->_format_ccls_itext($cd, $_)} @$ccls);
  551         1306  
390 612 100       1375 } else {
391 612         1285 $self->_die($cd, "Can't format $ccls");
392 612 100       2583 }
393 284         540 }
394              
395 328         645 my ($self, $cd, $ccls) = @_;
396              
397 612         1164 $self->_die($cd, "Sorry, markdown not yet implemented");
398             }
399 551         2188  
400             my ($self, $cd) = @_;
401              
402 5703         13855 my $lang = $cd->{args}{lang};
  10523         21491  
403             die "Invalid language '$lang', please use letters only"
404 0         0 unless $lang =~ /\A\w+\z/;
405              
406             my @modp;
407             unless ($lang eq 'en_US') {
408             push @modp, "Data/Sah/Lang/$lang.pm";
409 0     0   0 for my $cl (@{ $typex{$cd->{type}} // []}) {
410             my $modp = "Data/Sah/Lang/$lang/TypeX/$cd->{type}/$cl.pm";
411 0         0 $modp =~ s!::!/!g; # $cd->{type} might still contain '::'
412             push @modp, $modp;
413             }
414             }
415 5070     5070   10086 my $i;
416             for my $modp (@modp) {
417 5070         9155 $i++;
418 5070 50       19203 unless (exists $INC{$modp}) {
419             if ($i == 1) {
420             # test to check whether Data::Sah::Lang::$lang exists. if it
421 5070         7052 # does not, we fallback to en_US.
422 5070 100       12664 require Module::Installed::Tiny;
423 3         9 if (!Module::Installed::Tiny::module_installed($modp)) {
424 3   50     4 #$log->debug("$mod cannot be found, falling back to en_US");
  3         16  
425 0         0 $cd->{args}{lang} = 'en_US';
426 0         0 last;
427 0         0 }
428             }
429             #$log->trace("Loading $modp ...");
430 5070         8831 require $modp;
431 5070         14537  
432 3         5 # negative-cache, so we don't have to try again
433 3 100       9 $INC{$modp} = undef;
434 1 50       3 }
435             }
436             }
437 1         960  
438 1 50       1446 my ($self, $cd) = @_;
439              
440 0         0 # set locale so that numbers etc are printed according to locale (e.g.
441 0         0 # sprintf("%s", 1.2) prints '1,2' in id_ID).
442             $cd->{_orig_locale} = setlocale(LC_ALL);
443              
444             # XXX do we need to set everything? LC_ADDRESS, LC_TELEPHONE, LC_PAPER, ...
445 1         573 my $res = setlocale(LC_ALL, $cd->{args}{locale} // $cd->{args}{lang});
446             warn "Unsupported locale $cd->{args}{lang}"
447             if $cd->{args}{debug} && !defined($res);
448 1         7 }
449              
450             my ($self, $cd) = @_;
451              
452             $self->_load_lang_modules($cd);
453             }
454 5070     5070 1 11589  
455             my ($self, $cd) = @_;
456              
457             # by default, human clause handler can handle multiple values (e.g.
458 5070         23860 # "div_by&"=>[2, 3] becomes "must be divisible by 2 and 3" instead of having
459             # to be ["must be divisible by 2", "must be divisible by 3"]. some clauses
460             # that don't can override this value to 0.
461 5070   33     48955 $cd->{CLAUSE_DO_MULTI} = 1;
462             }
463 5070 50 33     20308  
464             my ($self, $cd) = @_;
465              
466             # reset what we set in before_clause()
467 5070     5070 1 10872 delete $cd->{CLAUSE_DO_MULTI};
468             }
469 5070         11511  
470             my ($self, $cd) = @_;
471              
472             # quantify NOUN (e.g. integer) into 'required integer', 'optional integer',
473 5324     5324 1 10663 # or 'forbidden integer'.
474              
475             # my $q;
476             # if (!$cd->{clset}{'required.is_expr'} &&
477             # !(grep {$_ eq 'required'} @{ $cd->{args}{skip_clause} })) {
478             # if ($cd->{clset}{required}) {
479 5324         12141 # $q = 'required %s';
480             # } else {
481             # $q = 'optional %s';
482             # }
483 5321     5321 1 9202 # } elsif ($cd->{clset}{forbidden} && !$cd->{clset}{'forbidden.is_expr'} &&
484             # !(grep { $_ eq 'forbidden' } @{ $cd->{args}{skip_clause} })) {
485             # $q = 'forbidden %s';
486 5321         11229 # }
487             # if ($q && @{$cd->{ccls}} && $cd->{ccls}[0]{type} eq 'noun') {
488             # $q = $self->_xlt($cd, $q);
489             # for (ref($cd->{ccls}[0]{text}) eq 'ARRAY' ?
490 5067     5067 1 8850 # @{ $cd->{ccls}[0]{text} } : $cd->{ccls}[0]{text}) {
491             # $_ = sprintf($q, $_);
492             # }
493             # }
494              
495             $cd->{result} = $self->format_ccls($cd, $cd->{ccls});
496             }
497              
498             my ($self, $cd) = @_;
499              
500             setlocale(LC_ALL, $cd->{_orig_locale});
501              
502             if ($cd->{args}{format} eq 'msg_catalog') {
503             $cd->{result} = $cd->{_msg_catalog};
504             }
505             }
506              
507             1;
508             # ABSTRACT: Compile Sah schema to human language
509              
510              
511             =pod
512              
513             =encoding UTF-8
514              
515 5067         14591 =head1 NAME
516              
517             Data::Sah::Compiler::human - Compile Sah schema to human language
518              
519 5067     5067 1 9031 =head1 VERSION
520              
521 5067         56892 This document describes version 0.914 of Data::Sah::Compiler::human (from Perl distribution Data-Sah), released on 2022-10-19.
522              
523 5067 100       13156 =head1 SYNOPSIS
524 5043         11493  
525             =head1 DESCRIPTION
526              
527             This class is derived from L<Data::Sah::Compiler>. It generates human language
528             text.
529              
530             =for Pod::Coverage ^(name|literal|expr|add_ccl|format_ccls|check_compile_args|handle_.+|before_.+|after_.+)$
531              
532             =head1 ATTRIBUTES
533              
534             =head1 METHODS
535              
536             =head2 new() => OBJ
537              
538             =head2 $c->compile(%args) => RESULT
539              
540             Aside from base class' arguments, this class supports these arguments (suffix
541             C<*> denotes required argument):
542              
543             =over
544              
545             =item * format => STR (default: C<inline_text>)
546              
547             Format of text to generate. Either C<inline_text>, C<inline_err_text>, or
548             C<markdown>. Note that you can easily convert Markdown to HTML, there are
549             libraries in Perl, JavaScript, etc to do that.
550              
551             Sample C<inline_text> output:
552              
553             integer, must satisfy all of the following: (divisible by 3, at least 10)
554              
555             C<inline_err_text> is just like C<inline_text>, except geared towards producing
556             an error message. Currently, instead of producing "integer" from schema "int",
557             it produces "Not of type integer". The rest is identical.
558              
559             Sample C<markdown> output:
560              
561             integer, must satisfy all of the following:
562              
563             * divisible by 3
564             * at least 10
565              
566             =item * hash_values => hash
567              
568             Optional, supply more keys to hash value to C<sprintfn> which will be used
569             during compilation.
570              
571             =back
572              
573             =head3 Compilation data
574              
575             This subclass adds the following compilation data (C<$cd>).
576              
577             Keys which contain compilation state:
578              
579             =over 4
580              
581             =back
582              
583             Keys which contain compilation result:
584              
585             =over 4
586              
587             =back
588              
589             =head1 HOMEPAGE
590              
591             Please visit the project's homepage at L<https://metacpan.org/release/Data-Sah>.
592              
593             =head1 SOURCE
594              
595             Source repository is at L<https://github.com/perlancar/perl-Data-Sah>.
596              
597             =head1 AUTHOR
598              
599             perlancar <perlancar@cpan.org>
600              
601             =head1 CONTRIBUTING
602              
603              
604             To contribute, you can send patches by email/via RT, or send pull requests on
605             GitHub.
606              
607             Most of the time, you don't need to build the distribution yourself. You can
608             simply modify the code, then test via:
609              
610             % prove -l
611              
612             If you want to build the distribution (e.g. to try to install it locally on your
613             system), you can install L<Dist::Zilla>,
614             L<Dist::Zilla::PluginBundle::Author::PERLANCAR>,
615             L<Pod::Weaver::PluginBundle::Author::PERLANCAR>, and sometimes one or two other
616             Dist::Zilla- and/or Pod::Weaver plugins. Any additional steps required beyond
617             that are considered a bug and can be reported to me.
618              
619             =head1 COPYRIGHT AND LICENSE
620              
621             This software is copyright (c) 2022, 2021, 2020, 2019, 2018, 2017, 2016, 2015, 2014, 2013, 2012 by perlancar <perlancar@cpan.org>.
622              
623             This is free software; you can redistribute it and/or modify it under
624             the same terms as the Perl 5 programming language system itself.
625              
626             =head1 BUGS
627              
628             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=Data-Sah>
629              
630             When submitting a bug or request, please include a test-file or a
631             patch to an existing test-file that illustrates the bug or desired
632             feature.
633              
634             =cut