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   404 use strict;
  24         75  
4 24     24   104 use warnings;
  24         45  
  24         532  
5 24     24   101 #use Log::Any::IfLOG qw($log);
  24         44  
  24         745  
6              
7             use Data::Dmp qw(dmp);
8 24     24   987 use Mo qw(build default);
  24         3548  
  24         1232  
9 24     24   122 use POSIX qw(locale_h);
  24         67  
  24         138  
10 24     24   6053 use Text::sprintfn;
  24         45  
  24         189  
11 24     24   41370  
  24         20517  
  24         10016  
12             extends 'Data::Sah::Compiler';
13              
14             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
15             our $DATE = '2022-09-30'; # DATE
16             our $DIST = 'Data-Sah'; # DIST
17             our $VERSION = '0.913'; # VERSION
18              
19             # every type extension is registered here
20             our %typex; # key = type, val = [clause, ...]
21              
22              
23 10335     10335 0 22220 my ($self, $cd, $msg) = @_;
24             return unless $cd->{args}{format} eq 'msg_catalog';
25              
26 9615     9615   19318 my $spath = join("/", @{ $cd->{spath} });
27 9615 100       22889 $cd->{_msg_catalog}{$spath} = $msg;
28             }
29 9572         14046  
  9572         19015  
30 9572         53988 my ($self, $args) = @_;
31              
32             $self->SUPER::check_compile_args($args);
33              
34 5070     5070 0 9575 my @fmts = ('inline_text', 'inline_err_text', 'markdown', 'msg_catalog');
35             $args->{format} //= $fmts[0];
36 5070         15792 unless (grep { $_ eq $args->{format} } @fmts) {
37             $self->_die({}, "Unsupported format, use one of: ".join(", ", @fmts));
38 5070         11453 }
39 5070   33     11052 }
40 5070 50       8650  
  20280         40732  
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 42937 $cd->{_msg_catalog} //= {};
47             }
48 5070         33061 $cd;
49 5070 100 50     21498 }
50 5046   66     22818  
51 5046   100     15946 my ($self, $cd, $expr) = @_;
52              
53 5070         21795 # 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         12 dmp($val);
65             }
66              
67             # translate
68 12821     12821 0 56450 my ($self, $cd, $text) = @_;
69              
70 12821 100       34103 my $lang = $cd->{args}{lang};
71 6048         16911  
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   134475 {
77             no strict 'refs'; ## no critic: TestingAndDebugging::ProhibitNoStrict
78 98035         132546 $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       251206 return "(no $lang text:$text)";
83 36         36 } else {
84             return $text;
85 24     24   198 }
  24         43  
  24         4263  
  36         36  
86 36         36 }
  36         122  
87              
88 36 50       103 # ($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   130 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       107 my ($self, $cd, $ccl) = @_;
106 61         1342 #$log->errorf("TMP: add_ccl %s", $ccl);
107 61         1106  
108             $ccl->{xlt} //= 1;
109 24     24   197  
  24         50  
  24         52403  
110 1         6 my $clause = $cd->{clause} // "";
111             $ccl->{type} //= "clause";
112              
113             my $do_xlt = 1;
114              
115 9589     9589   14775 my $hvals = {
116             modal_verb => $self->_xlt($cd, "must"),
117             modal_verb_neg => $self->_xlt($cd, "must not"),
118 9589   100     35936  
119             # so they can overriden through hash_values
120 9589   100     22991 field => $self->_xlt($cd, "field"),
121 9589   100     26290 fields => $self->_xlt($cd, "fields"),
122              
123 9589         12021 %{ $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     22624 if ($clause) {
  9589         55093  
134             delete $cd->{uclset}{$_} for
135 9589         22976 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         12259 }
  9589         14939  
141 9589   100     26324 } else {
142 9589 100       17759 delete $cd->{uclset}{$_} for
143 9589 100       16291 grep {/\A\.name(\.|\z)/} keys %{$cd->{uclset}};
144 4548         5746 if (defined $cd->{clset}{".name$suffix"}) {
145 261         2260 $ccl->{type} = 'noun';
  4548         12808  
146 4548 50       14870 $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         6225  
153 0         0 goto TRANSLATE unless $clause;
  5041         14105  
154 5041 50       20045  
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       27835 if ($ie) {
164             if (!$ccl->{expr}) {
165 4548         6722 $ccl->{fmt} = "($clause -> %s" . ($op ? " op=$op" : "") . ")";
166 4548         6301 $do_xlt = 0;
167 4548   100     10481 $vals = [$self->expr($cd, $vals)];
168 4548         7569 }
169 4548   100     14967 goto ERR_LEVEL;
170             }
171              
172             # handle .op
173 4548 100       8743  
174 4 50       9 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         18 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     21747 $vals = [sprintf($self->_xlt($cd, "all of %s"),
    100 100        
    100 66        
    100          
185             $self->literal($cv))];
186 305         799 }
187 305         710 } elsif ($im && $op eq 'or') {
  377         731  
188             if (@$cv == 2) {
189 559 100       1315 $vals = [sprintf($self->_xlt($cd, "%s or %s"),
190 450         1029 $self->literal($cv->[0]),
191             $self->literal($cv->[1]))];
192             } else {
193             $vals = [sprintf($self->_xlt($cd, "one of %s"),
194 109         245 $self->literal($cv))];
195             }
196             } elsif ($im && $op eq 'none') {
197             ($hvals->{modal_verb}, $hvals->{modal_verbneg}) =
198 558 100       1318 ($hvals->{modal_verb_neg}, $hvals->{modal_verb});
199 449         1041 if (@$cv == 2) {
200             $vals = [sprintf($self->_xlt($cd, "%s nor %s"),
201             $self->literal($cv->[0]),
202             $self->literal($cv->[1]))];
203 109         279 } else {
204             $vals = [sprintf($self->_xlt($cd, "any of %s"),
205             $self->literal($cv))];
206             }
207             } else {
208 270         726 $vals = [map {$self->literal($_)} @$vals];
209 270 100       535 }
210 216         451  
211             ERR_LEVEL:
212              
213             # handle .err_level
214 54         131 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         5040 $hvals->{modal_verb_neg} = $self->_xlt($cd, "should");
  3892         6903  
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     89456 }
  4277   100     15621  
  4277         12472  
225 4187 100 100     17503 delete $cd->{uclset}{"$clause.err_level"};
226 54 50       143  
227 0         0 TRANSLATE:
228 0         0  
229             if ($ccl->{xlt}) {
230 54         129 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         10705 }
236              
237             FILL_FORMAT:
238              
239 9589 100       17844 if (ref($ccl->{fmt}) eq 'ARRAY') {
240 9586 100       25340 $ccl->{text} = [map {sprintfn($_, (map {$_//""} ($hvals, @$vals)))}
    50          
241 5041         6946 @{$ccl->{fmt}}];
  10082         18023  
  5041         8769  
242             } elsif (!ref($ccl->{fmt})) {
243 4545         10897 $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       23112  
    50          
250 10082   50     191973 $self->_add_msg_catalog($cd, $ccl);
  10082         32453  
251 5041         8006 }
  5041         8988  
252              
253 4548   100     8640 # add a compiled clause (ccl), which will be combined at the end of compilation
  10208         25484  
254             # to be the final result. args is a hashref with these keys:
255 9589 50       607682 #
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         18679 #
  9589         21276  
259             # * fmt* - str/2-element array. human text which can be used as the first
260 9589         23444 # 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 20683 for (@ccls) {
290             $self->_add_ccl($inner_cd, $_);
291 9589   100     30218 }
292              
293 9589         12725 $ccl = {
294 9589 50       19316 type => 'list',
295 9589         22583 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 18412 $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         22533 $self->_xlt($cd, "Not of type %s"),
333 10822         19027 $res
334             );
335 10822         15386 } elsif (!$cd->{_fmt_noun_count}) {
336 10822         14454 # a clause (e.g. "must be >= 10"), already looks like errmsg
337 10822 50 100     42911 } else {
      66        
338 10822         22275 # a noun + clauses (e.g. "integer, must be even"). add preamble
339 10822 100       24529 $res = sprintf(
340             $self->_xlt(
341 5755 100 100     17233 $cd, "Does not satisfy the following schema: %s"),
    100          
342             $res
343 221         472 );
344             }
345             }
346             } else {
347             $res = $self->_format_ccls_markdown($cd, $ccls);
348             }
349             $res;
350             }
351 138         304  
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         44605 $cd->{_fmt_etc_count}++;
362             }
363             # handle a single noun/clause ccl
364             my $ccl = $ccls;
365 21957     21957   31098 return ref($ccl->{text}) eq 'ARRAY' ? $ccl->{text}[0] : $ccl->{text};
366             } elsif (ref($ccls) eq 'HASH' && $ccls->{type} eq 'list') {
367 21957         42045 # handle a single list ccl
368 21957         38695 my $c_openpar = $self->_xlt($cd, "(");
369             my $c_closepar = $self->_xlt($cd, ")");
370 21957 100 100     107483 my $c_colon = $self->_xlt($cd, ": ");
    100 66        
    50          
371 15703 100       27293 my $ccl = $ccls;
372 5731         7896  
373             my $txt = $ccl->{text}; $txt =~ s/\s+$//;
374 9972         12583 my @t = ($txt, $c_colon);
375             my $i = 0;
376             for (@{ $ccl->{items} }) {
377 15703         18007 push @t, $c_comma if $i;
378 15703 100       57139 my $it = $self->_format_ccls_itext($cd, $_);
379             if ($it =~ /\Q$c_comma/) {
380             push @t, $c_openpar, $it, $c_closepar;
381 551         1046 } else {
382 551         961 push @t, $it;
383 551         1070 }
384 551         725 $i++;
385             }
386 551         757 return join("", @t);
  551         1514  
387 551         1047 } elsif (ref($ccls) eq 'ARRAY') {
388 551         703 # handle an array of ccls
389 551         672 return join($c_comma, map {$self->_format_ccls_itext($cd, $_)} @$ccls);
  551         1081  
390 612 100       1137 } else {
391 612         1206 $self->_die($cd, "Can't format $ccls");
392 612 100       2169 }
393 284         499 }
394              
395 328         565 my ($self, $cd, $ccls) = @_;
396              
397 612         1023 $self->_die($cd, "Sorry, markdown not yet implemented");
398             }
399 551         1977  
400             my ($self, $cd) = @_;
401              
402 5703         12899 my $lang = $cd->{args}{lang};
  10523         18132  
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   8173 my $i;
416             for my $modp (@modp) {
417 5070         9463 $i++;
418 5070 50       17114 unless (exists $INC{$modp}) {
419             if ($i == 1) {
420             # test to check whether Data::Sah::Lang::$lang exists. if it
421 5070         7237 # does not, we fallback to en_US.
422 5070 100       10669 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         15  
425 0         0 $cd->{args}{lang} = 'en_US';
426 0         0 last;
427 0         0 }
428             }
429             #$log->trace("Loading $modp ...");
430 5070         7036 require $modp;
431 5070         12923  
432 3         4 # negative-cache, so we don't have to try again
433 3 100       9 $INC{$modp} = undef;
434 1 50       4 }
435             }
436             }
437 1         905  
438 1 50       1299 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         574 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         6 }
449              
450             my ($self, $cd) = @_;
451              
452             $self->_load_lang_modules($cd);
453             }
454 5070     5070 1 8483  
455             my ($self, $cd) = @_;
456              
457             # by default, human clause handler can handle multiple values (e.g.
458 5070         20770 # "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     49420 $cd->{CLAUSE_DO_MULTI} = 1;
462             }
463 5070 50 33     15792  
464             my ($self, $cd) = @_;
465              
466             # reset what we set in before_clause()
467 5070     5070 1 10887 delete $cd->{CLAUSE_DO_MULTI};
468             }
469 5070         11064  
470             my ($self, $cd) = @_;
471              
472             # quantify NOUN (e.g. integer) into 'required integer', 'optional integer',
473 5324     5324 1 9866 # 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         11597 # $q = 'required %s';
480             # } else {
481             # $q = 'optional %s';
482             # }
483 5321     5321 1 8712 # } elsif ($cd->{clset}{forbidden} && !$cd->{clset}{'forbidden.is_expr'} &&
484             # !(grep { $_ eq 'forbidden' } @{ $cd->{args}{skip_clause} })) {
485             # $q = 'forbidden %s';
486 5321         11235 # }
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 7747 # @{ $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         12133 =head1 NAME
516              
517             Data::Sah::Compiler::human - Compile Sah schema to human language
518              
519 5067     5067 1 8522 =head1 VERSION
520              
521 5067         53886 This document describes version 0.913 of Data::Sah::Compiler::human (from Perl distribution Data-Sah), released on 2022-09-30.
522              
523 5067 100       12160 =head1 SYNOPSIS
524 5043         11077  
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