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   414 use strict;
  24         70  
4 24     24   103 use warnings;
  24         39  
  24         461  
5 24     24   111 #use Log::Any::IfLOG qw($log);
  24         38  
  24         704  
6              
7             use Data::Dmp qw(dmp);
8 24     24   907 use Mo qw(build default);
  24         3484  
  24         1183  
9 24     24   152 use POSIX qw(locale_h);
  24         34  
  24         116  
10 24     24   5850 use Text::sprintfn;
  24         42  
  24         199  
11 24     24   40375  
  24         19783  
  24         10004  
12             extends 'Data::Sah::Compiler';
13              
14             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
15             our $DATE = '2022-08-20'; # DATE
16             our $DIST = 'Data-Sah'; # DIST
17             our $VERSION = '0.912'; # VERSION
18              
19             # every type extension is registered here
20             our %typex; # key = type, val = [clause, ...]
21              
22              
23 10335     10335 0 21236 my ($self, $cd, $msg) = @_;
24             return unless $cd->{args}{format} eq 'msg_catalog';
25              
26 9615     9615   15642 my $spath = join("/", @{ $cd->{spath} });
27 9615 100       25106 $cd->{_msg_catalog}{$spath} = $msg;
28             }
29 9572         13173  
  9572         19115  
30 9572         54086 my ($self, $args) = @_;
31              
32             $self->SUPER::check_compile_args($args);
33              
34 5070     5070 0 8981 my @fmts = ('inline_text', 'inline_err_text', 'markdown', 'msg_catalog');
35             $args->{format} //= $fmts[0];
36 5070         16815 unless (grep { $_ eq $args->{format} } @fmts) {
37             $self->_die({}, "Unsupported format, use one of: ".join(", ", @fmts));
38 5070         12517 }
39 5070   33     13321 }
40 5070 50       8264  
  20280         42023  
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 43486 $cd->{_msg_catalog} //= {};
47             }
48 5070         34499 $cd;
49 5070 100 50     22753 }
50 5046   66     22935  
51 5046   100     14435 my ($self, $cd, $expr) = @_;
52              
53 5070         21256 # 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 8 # 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         10 dmp($val);
65             }
66              
67             # translate
68 12821     12821 0 55556 my ($self, $cd, $text) = @_;
69              
70 12821 100       36069 my $lang = $cd->{args}{lang};
71 6048         15952  
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   132266 {
77             no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
78 98035         123533 $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       260425 return "(no $lang text:$text)";
83 36         36 } else {
84             return $text;
85 24     24   167 }
  24         52  
  24         4163  
  36         37  
86 36         35 }
  36         97  
87              
88 36 50       104 # ($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   121 return Lingua::EN::Numbers::Ordinate::ordinate($n) . " $noun";
99             } else {
100 62         86 no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
101             return "Data::Sah::Lang::$lang\::ordinate"->($n, $noun);
102             }
103             }
104              
105 62 100       113 my ($self, $cd, $ccl) = @_;
106 61         2620 #$log->errorf("TMP: add_ccl %s", $ccl);
107 61         1042  
108             $ccl->{xlt} //= 1;
109 24     24   191  
  24         53  
  24         51862  
110 1         5 my $clause = $cd->{clause} // "";
111             $ccl->{type} //= "clause";
112              
113             my $do_xlt = 1;
114              
115 9589     9589   16016 my $hvals = {
116             modal_verb => $self->_xlt($cd, "must"),
117             modal_verb_neg => $self->_xlt($cd, "must not"),
118 9589   100     36037  
119             # so they can overriden through hash_values
120 9589   100     23658 field => $self->_xlt($cd, "field"),
121 9589   100     28623 fields => $self->_xlt($cd, "fields"),
122              
123 9589         11936 %{ $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     19681 if ($clause) {
  9589         56685  
134             delete $cd->{uclset}{$_} for
135 9589         21776 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         12042 }
  9589         13820  
141 9589   100     25198 } else {
142 9589 100       17760 delete $cd->{uclset}{$_} for
143 9589 100       17677 grep {/\A\.name(\.|\z)/} keys %{$cd->{uclset}};
144 4548         7876 if (defined $cd->{clset}{".name$suffix"}) {
145 261         2348 $ccl->{type} = 'noun';
  4548         14171  
146 4548 50       15396 $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         7613  
153 0         0 goto TRANSLATE unless $clause;
  5041         15219  
154 5041 50       19432  
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       26176 if ($ie) {
164             if (!$ccl->{expr}) {
165 4548         6290 $ccl->{fmt} = "($clause -> %s" . ($op ? " op=$op" : "") . ")";
166 4548         6684 $do_xlt = 0;
167 4548   100     9989 $vals = [$self->expr($cd, $vals)];
168 4548         7964 }
169 4548   100     13728 goto ERR_LEVEL;
170             }
171              
172             # handle .op
173 4548 100       9740  
174 4 50       10 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         21 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     21957 $vals = [sprintf($self->_xlt($cd, "all of %s"),
    100 100        
    100 66        
    100          
185             $self->literal($cv))];
186 305         801 }
187 305         757 } elsif ($im && $op eq 'or') {
  377         778  
188             if (@$cv == 2) {
189 559 100       1135 $vals = [sprintf($self->_xlt($cd, "%s or %s"),
190 450         989 $self->literal($cv->[0]),
191             $self->literal($cv->[1]))];
192             } else {
193             $vals = [sprintf($self->_xlt($cd, "one of %s"),
194 109         300 $self->literal($cv))];
195             }
196             } elsif ($im && $op eq 'none') {
197             ($hvals->{modal_verb}, $hvals->{modal_verbneg}) =
198 558 100       1111 ($hvals->{modal_verb_neg}, $hvals->{modal_verb});
199 449         999 if (@$cv == 2) {
200             $vals = [sprintf($self->_xlt($cd, "%s nor %s"),
201             $self->literal($cv->[0]),
202             $self->literal($cv->[1]))];
203 109         215 } else {
204             $vals = [sprintf($self->_xlt($cd, "any of %s"),
205             $self->literal($cv))];
206             }
207             } else {
208 270         787 $vals = [map {$self->literal($_)} @$vals];
209 270 100       707 }
210 216         578  
211             ERR_LEVEL:
212              
213             # handle .err_level
214 54         129 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         5001 $hvals->{modal_verb_neg} = $self->_xlt($cd, "should");
  3892         7353  
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     88231 }
  4277   100     14931  
  4277         12812  
225 4187 100 100     18191 delete $cd->{uclset}{"$clause.err_level"};
226 54 50       139  
227 0         0 TRANSLATE:
228 0         0  
229             if ($ccl->{xlt}) {
230 54         141 if (ref($ccl->{fmt}) eq 'ARRAY') {
231 54         109 $ccl->{fmt} = [map {$self->_xlt($cd, $_)} @{$ccl->{fmt}}];
232             } elsif (!ref($ccl->{fmt})) {
233             $ccl->{fmt} = $self->_xlt($cd, $ccl->{fmt});
234             }
235 4548         8975 }
236              
237             FILL_FORMAT:
238              
239 9589 100       18355 if (ref($ccl->{fmt}) eq 'ARRAY') {
240 9586 100       28273 $ccl->{text} = [map {sprintfn($_, (map {$_//""} ($hvals, @$vals)))}
    50          
241 5041         6264 @{$ccl->{fmt}}];
  10082         17114  
  5041         9483  
242             } elsif (!ref($ccl->{fmt})) {
243 4545         10354 $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       27190  
    50          
250 10082   50     200869 $self->_add_msg_catalog($cd, $ccl);
  10082         34135  
251 5041         6581 }
  5041         9860  
252              
253 4548   100     7875 # add a compiled clause (ccl), which will be combined at the end of compilation
  10208         26953  
254             # to be the final result. args is a hashref with these keys:
255 9589 50       603633 #
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         12928 #
  9589         19868  
259             # * fmt* - str/2-element array. human text which can be used as the first
260 9589         26600 # 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 20699 for (@ccls) {
290             $self->_add_ccl($inner_cd, $_);
291 9589   100     29394 }
292              
293 9589         14573 $ccl = {
294 9589 50       18738 type => 'list',
295 9589         22360 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 18007 $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         20606 $self->_xlt($cd, "Not of type %s"),
333 10822         18408 $res
334             );
335 10822         19971 } elsif (!$cd->{_fmt_noun_count}) {
336 10822         12527 # a clause (e.g. "must be >= 10"), already looks like errmsg
337 10822 50 100     52470 } else {
      66        
338 10822         25602 # a noun + clauses (e.g. "integer, must be even"). add preamble
339 10822 100       23755 $res = sprintf(
340             $self->_xlt(
341 5755 100 100     18707 $cd, "Does not satisfy the following schema: %s"),
    100          
342             $res
343 221         483 );
344             }
345             }
346             } else {
347             $res = $self->_format_ccls_markdown($cd, $ccls);
348             }
349             $res;
350             }
351 138         325  
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         43824 $cd->{_fmt_etc_count}++;
362             }
363             # handle a single noun/clause ccl
364             my $ccl = $ccls;
365 21957     21957   30622 return ref($ccl->{text}) eq 'ARRAY' ? $ccl->{text}[0] : $ccl->{text};
366             } elsif (ref($ccls) eq 'HASH' && $ccls->{type} eq 'list') {
367 21957         41254 # handle a single list ccl
368 21957         39279 my $c_openpar = $self->_xlt($cd, "(");
369             my $c_closepar = $self->_xlt($cd, ")");
370 21957 100 100     106002 my $c_colon = $self->_xlt($cd, ": ");
    100 66        
    50          
371 15703 100       28378 my $ccl = $ccls;
372 5731         8265  
373             my $txt = $ccl->{text}; $txt =~ s/\s+$//;
374 9972         15026 my @t = ($txt, $c_colon);
375             my $i = 0;
376             for (@{ $ccl->{items} }) {
377 15703         18526 push @t, $c_comma if $i;
378 15703 100       64399 my $it = $self->_format_ccls_itext($cd, $_);
379             if ($it =~ /\Q$c_comma/) {
380             push @t, $c_openpar, $it, $c_closepar;
381 551         1095 } else {
382 551         1027 push @t, $it;
383 551         938 }
384 551         742 $i++;
385             }
386 551         853 return join("", @t);
  551         1715  
387 551         1136 } elsif (ref($ccls) eq 'ARRAY') {
388 551         751 # handle an array of ccls
389 551         707 return join($c_comma, map {$self->_format_ccls_itext($cd, $_)} @$ccls);
  551         1151  
390 612 100       1148 } else {
391 612         1156 $self->_die($cd, "Can't format $ccls");
392 612 100       2270 }
393 284         546 }
394              
395 328         571 my ($self, $cd, $ccls) = @_;
396              
397 612         1056 $self->_die($cd, "Sorry, markdown not yet implemented");
398             }
399 551         2126  
400             my ($self, $cd) = @_;
401              
402 5703         10394 my $lang = $cd->{args}{lang};
  10523         20076  
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   11380 my $i;
416             for my $modp (@modp) {
417 5070         11318 $i++;
418 5070 50       19370 unless (exists $INC{$modp}) {
419             if ($i == 1) {
420             # test to check whether Data::Sah::Lang::$lang exists. if it
421 5070         7561 # does not, we fallback to en_US.
422 5070 100       11492 require Module::Installed::Tiny;
423 3         8 if (!Module::Installed::Tiny::module_installed($modp)) {
424 3   50     3 #$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         9317 require $modp;
431 5070         12974  
432 3         4 # negative-cache, so we don't have to try again
433 3 100       10 $INC{$modp} = undef;
434 1 50       3 }
435             }
436             }
437 1         1651  
438 1 50       1318 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         561 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         5 }
449              
450             my ($self, $cd) = @_;
451              
452             $self->_load_lang_modules($cd);
453             }
454 5070     5070 1 10978  
455             my ($self, $cd) = @_;
456              
457             # by default, human clause handler can handle multiple values (e.g.
458 5070         25926 # "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     50501 $cd->{CLAUSE_DO_MULTI} = 1;
462             }
463 5070 50 33     17453  
464             my ($self, $cd) = @_;
465              
466             # reset what we set in before_clause()
467 5070     5070 1 9678 delete $cd->{CLAUSE_DO_MULTI};
468             }
469 5070         12019  
470             my ($self, $cd) = @_;
471              
472             # quantify NOUN (e.g. integer) into 'required integer', 'optional integer',
473 5324     5324 1 10639 # 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         11954 # $q = 'required %s';
480             # } else {
481             # $q = 'optional %s';
482             # }
483 5321     5321 1 10966 # } elsif ($cd->{clset}{forbidden} && !$cd->{clset}{'forbidden.is_expr'} &&
484             # !(grep { $_ eq 'forbidden' } @{ $cd->{args}{skip_clause} })) {
485             # $q = 'forbidden %s';
486 5321         10638 # }
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 8687 # @{ $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         13273 =head1 NAME
516              
517             Data::Sah::Compiler::human - Compile Sah schema to human language
518              
519 5067     5067 1 9214 =head1 VERSION
520              
521 5067         55500 This document describes version 0.912 of Data::Sah::Compiler::human (from Perl distribution Data-Sah), released on 2022-08-20.
522              
523 5067 100       13954 =head1 SYNOPSIS
524 5043         11196  
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