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 |