/[pub]/suikawiki/script/bin/mkplugin2.pl
Suika

Contents of /suikawiki/script/bin/mkplugin2.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.20 - (hide annotations) (download)
Tue Sep 21 03:18:21 2004 UTC (20 years, 1 month ago) by wakaba
Branch: MAIN
Changes since 1.19: +4 -3 lines
File MIME type: text/plain
%m--wikipage-obsolete rule added

1 wakaba 1.1 #!/usr/bin/perl
2     use strict;
3 wakaba 1.20 our $VERSION = do{my @r=(q$Revision: 1.19 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
4 wakaba 1.5 require Message::Markup::SuikaWikiConfig20::Parser;
5 wakaba 1.1
6 wakaba 1.2 {
7 wakaba 1.1 my $src = '';
8     my $srcfile = shift;
9     open SRC, $srcfile or die "$0: $!"; {
10     local $/ = undef;
11     $src = <SRC>;
12     } close SRC;
13    
14     sub literal ($) {
15     my $s = shift;
16 wakaba 1.2 if (ref ($s) eq 'ARRAY') {
17     q<[> . list (@$s) . q<]>;
18     } elsif (ref ($s) eq 'HASH') {
19     q<{> . hash (%$s) . q<}>;
20     } elsif (ref ($s) eq 'bare') {
21     $$s;
22     } else {
23     $s =~ s/([#\\])/\\$1/g;
24     q<q#> . $s . q<#>;
25     }
26     }
27     sub list (@) {
28     join ', ', map {literal $_} @_;
29     }
30     sub hash (%) {
31     my $i = 0;
32     list map {($i++ % 2) ? $_ : do {my $s = $_; $s =~ s/(?<=.)-/_/; $s}} @_;
33 wakaba 1.1 }
34     sub n11n ($) {
35     my $s = shift;
36     $s =~ s/\s+/ /g;
37     $s;
38     }
39 wakaba 1.2 sub m13ed_val_list ($$) {
40     my ($src, $key) = @_;
41     my @r;
42     for (@{$src->child_nodes}) {
43     if ($_->local_name eq $key) {
44     push @r, [scalar $_->inner_text,
45     scalar $_->get_attribute ('lang', make_new_node => 1)
46     ->inner_text,
47     scalar $_->get_attribute ('script', make_new_node => 1)
48     ->inner_text];
49     }
50     }
51     list @r;
52     }
53     sub barecode ($) {
54     bless \$_[0], 'bare';
55     }
56     sub code ($$) {
57     my ($Info, $code) = @_;
58     for (keys %{$Info->{const}}) {
59 wakaba 1.11 $code =~ s/\$$_\b/$Info->{const}->{$_}/ge;
60 wakaba 1.2 }
61     $code =~ s/__FUNCPACK__/$Info->{module_name}/g;
62 wakaba 1.19 $code =~ s/__FUNCPACK{([^{}]+)}__/(\$SuikaWiki::Plugin::Registry::Info{@{[literal $1]}}->{module_name} || SuikaWiki::Plugin->module_package (@{[literal $1]}))/g;
63 wakaba 1.17 $code =~ s{<Q:([^:]+):([^>]+)>}{
64     if ($Info->{Namespace}->{$1}) {
65     literal $Info->{Namespace}->{$1}.$2;
66     } else {
67     warn qq(Namespace prefix "$1" not defined);
68     literal $2;
69     }
70     }ge;
71 wakaba 1.16
72 wakaba 1.13 $Info->{-message_error_used} = 1 if $code =~ /\buse\s+Message::Util::Error\b/;
73     if (not $Info->{-message_error_used} and
74     ($code =~ /\btry\s*\{/ or $code =~ /\bcatch\s*\{/)) {
75     warn "Message::Util::Error MUST be 'use'd before 'try'...'catch' block used";
76     }
77 wakaba 1.2 $code;
78     }
79 wakaba 1.5 sub change_package ($$) {
80     my ($Info, $pack) = @_;
81     unless ($Info->{current_package} eq $pack) {
82     $Info->{current_package} = $pack;
83     return qq{package $pack;\n\n};
84     } else {
85     return '';
86     }
87     }
88     sub quoted_string ($) {
89     my $s = shift;
90     $s =~ s/([\\"])/\\$1/g;
91     '"'.$s.'"';
92     }
93 wakaba 1.6 sub line ($;%) {
94     my ($Info, %opt) = @_;
95    
96     unless ($opt{file}) {
97     if ($opt{reset}) {
98     $opt{file} = sprintf '(WikiPlugin module %s, chunk %d)',
99     $Info->{Name},
100     ++$Info->{chunk_count};
101     } elsif ($opt{realfile}) {
102     $opt{file} = sprintf '(WikiPlugin module %s, chunk from %s)',
103     $Info->{Name},
104     $opt{realfile};
105     } else {
106 wakaba 1.7 $opt{file} = sprintf '(WikiPlugin module source %s, block %s)',
107     $Info->{source_file},
108 wakaba 1.6 $opt{node_path};
109     }
110     }
111    
112     $opt{file} =~ s/"/''/g;
113     sprintf '%s#line %d "%s"%s', "\n", $opt{line_no} || 1, $opt{file}, "\n";
114     }
115 wakaba 1.9 sub literal_or_code ($$) {
116     my ($Info, $s) = @_;
117     substr ($s, 0, 1) ne '{' ? literal ($s)
118     : code ($Info, substr ($s, 1, length ($s) - 2));
119     }
120 wakaba 1.19 sub expanded_uri ($$$) {
121     my ($Info, $prefix, $lname) = @_;
122 wakaba 1.20 warn "$0: $prefix: Namespace prefix not declared"
123     unless $Info->{Namespace}->{$prefix};
124 wakaba 1.19 $Info->{Namespace}->{$prefix} . $lname;
125     }
126 wakaba 1.1
127 wakaba 1.5 my $parser = Message::Markup::SuikaWikiConfig20::Parser->new;
128 wakaba 1.1 my $plugins = $parser->parse_text ($src);
129     my $meta = $plugins->get_attribute ('Plugin')
130     or die "$0: Required 'Plugin' section not found";
131 wakaba 1.2 my %Info = (provide => {},
132     Name => n11n $meta->get_attribute ('Name')->value);
133 wakaba 1.7 $Info{source_file} = $srcfile;
134 wakaba 1.1 $Info{name_literal} = literal $Info{Name};
135     my @date = gmtime;
136     $Info{LastModified} = sprintf '%04d-%02d-%02dT%02d:%02d:%02d+00:00',
137     $date[5] + 1900, $date[4] + 1, @date[3,2,1,0];
138     $Info{Version} = sprintf '%04d.%02d%02d.%02d%02d',
139     $date[5] + 1900, $date[4] + 1, @date[3,2,1];
140 wakaba 1.2 $Info{InterfaceVersion} = '2.9.1';
141     $Info{mkpluginVersion} = '2.'.$VERSION;
142 wakaba 1.1 $Info{module_name} = q#SuikaWiki::Plugin::plugin#;
143     $Info{module_name} = random_module_name (\%Info, $Info{Name});
144    
145     print <<EOH;
146     use strict;
147 wakaba 1.5 @{[change_package \%Info, 'SuikaWiki::Plugin::Registry']}
148 wakaba 1.1 our \%Info;
149     \$Info{$Info{name_literal}}->{Name} = $Info{name_literal};
150     EOH
151 wakaba 1.5 for (qw/Version InterfaceVersion mkpluginVersion module_name/) {
152 wakaba 1.13 print qq{\$Info{$Info{name_literal}}->{@{[literal $_]}} = @{[literal $Info{$_}]};\n};
153 wakaba 1.2 }
154 wakaba 1.13 for (qw/LastModified Date.RCS/) {
155 wakaba 1.1 $Info{$_} = n11n $meta->get_attribute ($_,make_new_node=>1)->value;
156     next unless length $Info{$_};
157 wakaba 1.13 print qq{\$Info{$Info{name_literal}}->{@{[literal $_]}} = } . literal $Info{$_};
158 wakaba 1.1 print ";\n";
159     }
160 wakaba 1.2 for (qw/RequiredPlugin RequiredModule/) {
161 wakaba 1.1 $Info{$_} = $meta->get_attribute ($_);
162     next unless ref $Info{$_}; $Info{$_} = $Info{$_}->value;
163     print qq{\$Info{$Info{name_literal}}->{$_} = [};
164     print join ', ', map {literal $_} @{$Info{$_}};
165     print "];\n";
166     }
167 wakaba 1.2 for (qw/Description License RelatedWikiPage RelatedURI/) {
168     my $r = m13ed_val_list $meta, $_;
169     next unless $r;
170     print qq{\$Info{$Info{name_literal}}->{$_} = [$r];\n};
171     }
172    
173     print qq{\$Info{$Info{name_literal}}->{Author} = [} .( list map {
174     [
175     [ barecode m13ed_val_list ($_, 'Name') ],
176     [ $_->get_attribute ('Mail', make_new_node => 1)->value ],
177     [ $_->get_attribute ('URI', make_new_node => 1)->value ],
178     ]
179     } grep { $_->local_name eq 'Author' } @{$meta->child_nodes}
180     ). qq{];\n};
181 wakaba 1.1
182 wakaba 1.16 for (@{$meta->get_attribute ('Namespace', make_new_node => 1)->child_nodes}) {
183     $Info{Namespace}->{$_->local_name} = $_->value;
184     }
185    
186 wakaba 1.5 my $use = $meta->get_attribute ('Use');
187     if (ref $use) {
188     print change_package \%Info, $Info{module_name};
189 wakaba 1.6 print line \%Info, node_path => 'Plugin/Use';
190 wakaba 1.13 print code \%Info, $use->inner_text;
191 wakaba 1.6 print line \%Info, reset => 1;
192 wakaba 1.5 }
193    
194 wakaba 1.1 for (@{$plugins->child_nodes}) {
195 wakaba 1.2 if ($_->local_name eq 'FormattingRule') {
196     print "\n", make_rule ($_, \%Info);
197     } elsif ($_->local_name eq 'ViewDefinition') {
198 wakaba 1.1 print "\n", make_viewdef ($_, \%Info);
199     } elsif ($_->local_name eq 'ViewFragment') {
200     print "\n", make_viewfragment ($_, \%Info);
201 wakaba 1.2 } elsif ($_->local_name eq 'Function') {
202     print "\n", make_function ($_, \%Info);
203     } elsif ($_->local_name eq 'Resource') {
204     print "\n", make_resdef ($_, \%Info);
205     } elsif ($_->local_name eq 'PluginConst') {
206     register_plugin_const ($_, \%Info);
207 wakaba 1.5 } elsif ($_->local_name eq 'Format') {
208     print "\n", make_format ($_, \%Info);
209 wakaba 1.15 } elsif ($_->local_name eq 'FormattingRuleAlias') {
210     print "\n", make_rule_alias ($_, \%Info);
211 wakaba 1.9 # Parameter
212     # PluginCategory
213 wakaba 1.1 }
214     }
215    
216 wakaba 1.5 print change_package \%Info, q(SuikaWiki::Plugin::Registry);
217 wakaba 1.2 print qq{\$Info{$Info{name_literal}}->{provide} = } . literal $Info{provide};
218     print qq{;\n};
219    
220 wakaba 1.1 print "\n1;\n";
221     exit;
222 wakaba 1.2 }
223    
224 wakaba 1.5 sub make_format ($$) {
225     my ($src, $Info) = @_;
226     my $module_name = 'SuikaWiki::Format::Definition::'.$src->get_attribute_value ('ModuleName');
227     my $r = change_package $Info, $module_name;
228 wakaba 1.13 local $Info->{-message_error_used} = 0;
229 wakaba 1.5 $r .= qq{our \@ISA;\n};
230     if (my $isa = $src->get_attribute_value ('Inherit')) {
231     for (@$isa) {
232     $r .= qq{push \@ISA, @{[literal 'SuikaWiki::Format::Definition::'.$_]};\n};
233     }
234     } else {
235     $r .= qq{push \@ISA, 'SuikaWiki::Format::Definition::template';\n};
236     }
237     if (my $name = $src->get_attribute_value ('Name')) {
238 wakaba 1.16 $r .= qq{\$SuikaWiki::Format::Definition::Class{@{[literal 'MAGIC:'.$name.'/'.$src->get_attribute_value ('Version', default => '').'##']}} = '$module_name';\n};
239 wakaba 1.5 }
240     if (my $type = $src->get_attribute_value ('Type')) {
241 wakaba 1.10 $type .= join '', map {
242     ';'. $_->local_name .'='. quoted_string $_->inner_text
243     } sort {
244     $a->local_name cmp $b->local_name
245     } @{$src->get_attribute ('Type')->child_nodes};
246 wakaba 1.16 $r .= qq{\$SuikaWiki::Format::Definition::Class{@{[literal 'IMT:'.$type.'##']}} = '$module_name';\n};
247 wakaba 1.5 }
248    
249 wakaba 1.8 my $convert = line $Info, line_no => __LINE__ + 2, realfile => __FILE__;
250     $convert .= <<'EOH';
251 wakaba 1.5 our $Converter;
252     sub convert ($$;%) {
253     my ($self, $source, %opt) = @_;
254 wakaba 1.16 my $t = SuikaWiki::Format::Definition->serialize_media_type (%opt);
255     my $converter = $Converter->{$t->{_}};
256 wakaba 1.12 return ($converter->{$opt{return_type} or 'Main'} or
257 wakaba 1.16 CORE::die "Buggy implementation: $t->{_}/@{[$opt{return_type} or 'Main']} not defined")
258 wakaba 1.12 ->($self, $source, \%opt)
259     if $converter;
260 wakaba 1.5 $self->SUPER::convert ($source, %opt);
261     }
262     EOH
263    
264     for (@{$src->child_nodes}) {
265     if ($_->local_name eq 'Converter') {
266 wakaba 1.8 if ($convert) {
267     $r .= $convert;
268     $r .= line $Info, reset => 1;
269     undef $convert;
270     }
271 wakaba 1.5 $r .= make_format_converter ($_, $Info);
272 wakaba 1.10 } elsif ($_->local_name eq 'WikiForm') {
273     $r .= q(sub wikiform {)."\n".q(my ($self, $source, %opt) = @_;);
274     $r .= line $Info, node_path => qq(Format[module-name()=$module_name]/WikiForm);
275     $r .= code $Info, $_->get_attribute_value ('Main');
276     $r .= line $Info, reset => 1;
277     $r .= qq(}\n);
278 wakaba 1.11 } elsif ($_->local_name eq 'HeadSummary') {
279     $r .= q(sub headsummary {)."\n".q(my ($self, $source, %opt) = @_;);
280     $r .= line $Info, node_path => qq(Format[module-name()=$module_name]/HeadSummary);
281     $r .= code $Info, $_->get_attribute_value ('Main');
282     $r .= line $Info, reset => 1;
283     $r .= qq(}\n);
284 wakaba 1.10 } elsif ($_->local_name eq 'NextIndex') {
285     my $name = $_->get_attribute_value ('Name', default => '');
286     $r .= q(sub next_index_for_).$name
287     . q( {)."\n".q(my ($self, $source, %opt) = @_;)
288     . line $Info, node_path => qq(Format[module-name()=$module_name]/NextIndex[Name=$name]);
289     $r .= code $Info, $_->get_attribute_value ('Main');
290     $r .= line $Info, reset => 1;
291     $r .= qq(}\n);
292 wakaba 1.19 } elsif ({qw/content_written 1 content_removed 1 content_type_changed_from 1
293     content_prop_modified 1/}
294 wakaba 1.18 ->{my $node_name = $_->local_name}) {
295     $r .= q(sub ).$node_name
296     . q( {)."\n".q(my ($self, %opt) = @_;)
297     . line $Info, node_path => qq(Format[module-name()=$module_name]/$node_name]);
298     $r .= code $Info, $_->get_attribute_value ('Main');
299     $r .= line $Info, reset => 1;
300     $r .= qq(}\n);
301 wakaba 1.5 } elsif ($_->local_name eq 'Use') {
302 wakaba 1.6 $r .= line $Info, node_path => qq(Format[module-name()=$module_name]/Use);
303 wakaba 1.13 $r .= code $Info, $_->inner_text;
304 wakaba 1.19 } elsif ($_->local_name eq 'Prop') {
305     $r .= q<sub prop ($$;%) { my ($self, $name, %opt) = @_;>;
306     my $f = 0;
307     for my $prop (@{$_->child_nodes}) {
308     if ($prop->local_name =~ /^([^:]+):([^:]+)$/) {
309     $r .= qq{if (\$name eq @{[literal expanded_uri $Info, $1, $2]}) { return @{[literal $prop->value]} } els};
310     $f = 1;
311     }
312     }
313     $r .= q<e {> if $f;
314     $r .= q{$self->SUPER::prop ($name, %opt);};
315     $r .= q<}> if $f;
316     $r .= qq<\n}\n>;
317 wakaba 1.5 }
318     }
319     $r;
320     }
321    
322     sub make_format_converter ($$) {
323     my ($src, $Info) = @_;
324     my %def;
325 wakaba 1.16 for (qw/Type Name Version TypeURIReference IsFragment IsPlaceholder/) {
326     $def{$_} = $src->get_attribute_value ($_);
327     delete $def{$_} unless defined $def{$_};
328     }
329     $def{Type_param} = {map {$_->local_name => $_->value}
330     @{$src->get_attribute ('Type', make_new_node => 1)
331     ->child_nodes}};
332     my $type = serialize_media_type ($Info,
333     Type => $def{Type},
334     Type_param => $def{Type_param},
335     Name => $def{Name},
336     Version => $def{Version},
337     URIReference => $def{TypeURIReference},
338     IsFragment => $def{IsFragment},
339     IsPlaceholder => $def{IsPlaceholder});
340     $def{serialized_type} = $type->{_};
341 wakaba 1.5
342 wakaba 1.12 for (qw/Main ToString ToOctetStream/) {
343     my $def = $src->get_attribute_value ($_);
344     next unless $def;
345     $def{$_} = line ($Info, node_path => '//Converter/'.$_)
346     . $def
347     . line ($Info, reset => 1);
348     if ($def{$_} =~ /\$r\b/) {
349     $def{$_} = 'my $r;'."\n".$def{$_}."\n".'$r';
350     }
351     $def{$_} = barecode code $Info,
352     'sub {my ($self, $source, $opt) = @_;'
353     . $def{$_} . '}';
354     }
355 wakaba 1.5
356     my $r = list %def;
357 wakaba 1.16 if ($type->{Type}) {
358     $r = qq{\$Converter->{@{[literal $type->{Type}]}} = {$r};\n};
359     $r .= qq{\$Converter->{@{[literal $type->{Magic}]}} = \$Converter->{@{[literal $type->{Type}]}};\n}
360     if $type->{Magic};
361     $r .= qq{\$Converter->{@{[literal $type->{URIReference}]}} = \$Converter->{@{[literal $type->{Type}]}};\n}
362     if $type->{URIReference};
363     } elsif ($type->{Magic}) {
364     $r = qq{\$Converter->{@{[literal $type->{Magic}]}} = {$r};\n};
365     $r .= qq{\$Converter->{@{[literal $type->{URIReference}]}} = \$Converter->{@{[literal $type->{Magic}]}};\n}
366     if $type->{URIReference};
367     } elsif ($type->{URIReference}) {
368     $r = qq{\$Converter->{@{[literal $type->{URIReference}]}} = {$r};\n};
369 wakaba 1.9 } else {
370 wakaba 1.16 $r = 'BEGIN { die "Invalid Syntax of Converter: Type or Name or TypeURIReference property required" }';
371 wakaba 1.5 }
372     $r;
373     }
374    
375 wakaba 1.16 sub serialize_media_type ($%) {
376     my ($Info, %opt) = @_;
377     my %return;
378     if ($opt{Type}) {
379     $return{Type} = 'IMT:'.$opt{Type};
380     if ($opt{Type_param}) {
381     $return{Type} .= join '', map {my $s;
382     ';'. $_ .'="'
383     . (($s = $opt{Type_param}->{$_}) =~ s/([\\"])/\\$1/g, $s)
384     . '"'
385     } sort {
386     $a cmp $b
387     } keys %{$opt{Type_param}};
388     }
389     }
390     if ($opt{Magic}) {
391     $return{Magic} = 'MAGIC:'.$opt{Magic};
392     } elsif ($opt{Name}) {
393     $return{Name} = 'MAGIC:'.$opt{Name}.'/*';
394     $return{Magic} = 'MAGIC:'.$opt{Name}.'/'.$opt{Version} if $opt{Version};
395     }
396     if ($opt{URIReference}) {
397     $return{URIReference} = $opt{URIReference};
398     }
399     my $flag = '##';
400     $flag .= 'f' if $opt{IsFragment};
401     $flag .= 'p' if $opt{IsPlaceholder};
402     for (qw/URIReference Type Magic Name/) {
403     $return{$_} .= $flag if $return{$_};
404     }
405     $return{_} = $return{URIReference} || $return{Type}
406     || $return{Magic} || $return{Name};
407     \%return;
408     }
409    
410    
411 wakaba 1.2 sub make_function ($$) {
412     my ($src, $Info) = @_;
413     ## TODO: support of ARGV property
414 wakaba 1.6 my $name;
415 wakaba 1.2 my $r = <<EOH;
416 wakaba 1.5 @{[change_package $Info, $Info->{module_name}]}
417 wakaba 1.6 sub @{[$name = $src->get_attribute_value ('Name')]} {
418     @{[line $Info, node_path => "Function[Name='$name']/Main"]}@{[
419     code $Info, $src->get_attribute_value ('Main')
420 wakaba 1.9 ]}
421 wakaba 1.2 }
422 wakaba 1.9 @{[line $Info, reset => 1]}
423 wakaba 1.2 EOH
424     }
425    
426     sub register_plugin_const ($$) {
427     my ($src, $Info) = @_;
428     for (@{$src->child_nodes}) {
429 wakaba 1.11 $Info->{const}->{$_->local_name} = literal_or_code $Info, $_->value;
430 wakaba 1.2 }
431     }
432    
433     sub make_resdef ($$) {
434     my ($src, $Info) = @_;
435 wakaba 1.5 my $r = change_package $Info, 'SuikaWiki::Plugin::Resource';
436 wakaba 1.13 local $Info->{-message_error_used} = 0;
437 wakaba 1.5 $r .= qq{our \$BaseResource;\n};
438 wakaba 1.2 for (@{$src->child_nodes}) {
439     if ($_->node_type eq '#element') {
440     my $lang = literal ($_->get_attribute_value ('lang') || 'und');
441     my $script = literal $_->get_attribute_value ('script');
442     my $name = literal $_->local_name;
443     my $val = literal n11n $_->value;
444     $r .= qq{\$BaseResource->{$lang}->{$script}->{$name} = $val;\n};
445     }
446     }
447     $r;
448     }
449 wakaba 1.1
450     sub make_viewfragment ($$) {
451     my ($src, $Info) = @_;
452     my $r = '';
453 wakaba 1.4 my $body = <<EOH;
454     {
455 wakaba 1.2 Main => @{[literal $src->get_attribute_value ('Formatting')]},
456     Order => @{[0+$src->get_attribute_value ('Order')]},
457     Description => [@{[m13ed_val_list $src, 'Description']}],
458     };
459     EOH
460 wakaba 1.4 ## Recommended format
461     my $name = $src->get_attribute_value ('Template');
462     if (ref ($name) and @$name > 1) {
463     $r .= qq({my \$def = $body;\n);
464     for (@$name) {
465     my $name = $_; $name =~ tr/-/_/;
466     $r .= qq(push \@{\$SuikaWiki::View::Implementation::TemplateFragment{@{[literal $name]}}}, \$def;\n);
467     push @{$Info->{provide}->{viewfragment}}, {Name => $name};
468     }
469     $r .= qq(}\n);
470     } else { ## Obsoleted format
471     $name = ref $name ? $name->[0] : $src->get_attribute_value ('Name');
472     $name =~ tr/-/_/;
473     $r .= qq(push \@{\$SuikaWiki::View::Implementation::TemplateFragment{@{[literal $name]}}}, $body);
474     push @{$Info->{provide}->{viewfragment}}, {Name => $name};
475     }
476 wakaba 1.1 $r;
477     }
478    
479     sub make_viewdef ($$) {
480     my ($src, $Info) = @_;
481     my $ViewProp = {};
482     my $r = '';
483 wakaba 1.6 $ViewProp->{Name} = n11n $src->get_attribute_value ('Mode');
484 wakaba 1.10 $ViewProp->{Name} =~ s/(?<=.)-/_/g;
485 wakaba 1.1 $ViewProp->{pack_name} = random_module_name ($Info, $ViewProp->{Name});
486    
487 wakaba 1.2 $ViewProp->{condition_stringified} = hash
488 wakaba 1.1 mode => $ViewProp->{Name},
489     map {($_->local_name => $_->value)}
490     @{$src->get_attribute ('Condition',make_new_node=>1)->child_nodes};
491    
492     $r .= <<EOH;
493     push \@SuikaWiki::View::Implementation::CommonViewDefs, {
494     condition => {$ViewProp->{condition_stringified}},
495     object_class => q#$ViewProp->{pack_name}#,
496     };
497 wakaba 1.5 @{[change_package $Info, $ViewProp->{pack_name}]}
498 wakaba 1.1 our \@ISA = q#SuikaWiki::View::template#;
499     EOH
500 wakaba 1.13 local $Info->{-message_error_used} = 0;
501 wakaba 1.11 my $use = $src->get_attribute ('Use');
502     if (ref $use) {
503     $r .= line $Info, node_path => "ViewDefinition[Mode='$ViewProp->{Name}']/Use";
504 wakaba 1.13 $r .= code $Info, $use->inner_text;
505     $r .= "\n\n";
506 wakaba 1.11 }
507    
508 wakaba 1.1 for (@{$src->child_nodes}) {
509 wakaba 1.2 if ($_->local_name eq 'template') {
510 wakaba 1.8 $r .= make_view_template_method ($_, $Info, $ViewProp);
511 wakaba 1.2 } elsif ($_->local_name eq 'method') {
512 wakaba 1.6 my $method_name = $_->get_attribute_value ('Name');
513 wakaba 1.1 $r .= ({
514 wakaba 1.8 main => q(sub main ($$) {)."\n".q(my ($self, $opt, $opt2) = @_;)."\n",
515 wakaba 1.1 main_pre => q(sub main_pre ($$$) {)."\n".q(my ($self, $opt, $opt2) = @_;)."\n",
516     main_post => q(sub main_post ($$$) {)."\n".q(my ($self, $opt, $opt2) = @_;)."\n",
517 wakaba 1.6 }->{$method_name}
518     ||qq(sub @{[$method_name]} {\n))
519     . line ($Info, node_path => "ViewDefinition[Mode='$ViewProp->{Name}']/method[Name='$method_name']")
520 wakaba 1.2 . code ($Info, $_->value)
521 wakaba 1.9 . qq(}\n)
522     . line ($Info, reset => 1);
523 wakaba 1.1 }
524     }
525 wakaba 1.2 my $prop = {Name => $ViewProp->{Name},
526     Description => barecode m13ed_val_list $_, 'Description'};
527     push @{$Info->{provide}->{viewdef}}, $prop;
528     $r;
529     }
530    
531     sub make_view_template_method ($$) {
532 wakaba 1.8 my ($src, $Info, $ViewProp) = @_;
533 wakaba 1.18 my $media_type = $src->get_attribute_value
534     ('media-type',
535     default => q<application/octet-stream>);
536 wakaba 1.2 my $r = <<EOH;
537    
538     sub main (\$\$\$) {
539     my (\$self, \$opt, \$opt2) = \@_;
540     require SuikaWiki::Output::HTTP;
541     \$opt2->{output} = SuikaWiki::Output::HTTP->new
542     (wiki => \$self->{view}->{wiki},
543     view => \$self->{view}, viewobj => \$self);
544     for (\@{\$self->{view}->{wiki}->{var}->{client}->{used_for_negotiate}},
545     'Accept-Language') {
546     \$opt2->{output}->add_negotiate_header_field (\$_);
547     }
548    
549     \$opt2->{template} = @{[literal $src->get_attribute ('body', make_new_node => 1)->inner_text]};
550     \$opt2->{o} = bless {
551     ## SuikaWiki 3 WikiPlugin interface
552     wiki => \$self->{view}->{wiki},
553     plugin => \$self->{view}->{wiki}->{plugin},
554     var => {},
555     }, 'SuikaWiki::Plugin';
556     @{[do{my $x=$src->get_attribute('http-status-code',make_new_node=>1)->inner_text;
557     $x?q{$opt2->{output}->{status_code} = }.(0 + $x).q{;}:q{}}]}
558     @{[do{my $x=$src->get_attribute('http-status-phrase',make_new_node=>1)->inner_text;
559 wakaba 1.8 $x?q{$opt2->{output}->{status_phrase} = }.literal($x).q{;}:q{}}]}
560 wakaba 1.18 \$opt2->{output}->{entity}->{media_type} = @{[literal $media_type]};
561    
562 wakaba 1.2 @{[($src->get_attribute ('use-media-type-charset',make_new_node=>1)
563     ->inner_text || 0) ?
564     q{$opt2->{output}->{entity}->{charset} = $self->{view}->{wiki}->{config}->{charset}->{output};}:
565     q{}]}
566     @{[do{my $x = $src->get_attribute ('expires',make_new_node=>1)->inner_text;
567     if ($x =~ /%%(\w+)%%/) {
568     qq{\$opt2->{output}->set_expires (%{\$self->{view}->{wiki}->{config}->{entity}->{expires}->{@{[literal $1]}}});};
569     } else {
570     qq{\$opt2->{output}->set_expires (delta => @{[0 + $x]});};
571     }
572     }]}
573 wakaba 1.12 \$opt2->{output}->{entity}->{body_is_octet_stream} = @{[
574     $src->get_attribute ('body')->get_attribute_value ('is-octet-stream', default_value => 0)
575     or 0
576     ]};
577 wakaba 1.2
578     \$self->{view}->{wiki}->init_db;
579     \$self->main_pre (\$opt, \$opt2);
580    
581 wakaba 1.18 @{[$media_type eq 'text/html' ? q{require Message::Markup::XML::Serialize::HTML;} : '']}
582 wakaba 1.8 use Message::Util::Error;
583     try {
584     \$opt2->{output}->{entity}->{body}
585 wakaba 1.18 = @{[$media_type eq 'text/html' ? q{Message::Markup::XML::Serialize::HTML::html_simple} : '']}
586     (SuikaWiki::Plugin->formatter ('view')
587     ->replace (\$opt2->{template}, param => \$opt2->{o}));
588 wakaba 1.8 } \$self->{view}->{wiki}->{config}->{catch}->{ @{[
589     $ViewProp->{Name} eq '-error' ? 'formatter_view_error'
590     : 'formatter_view' ]} };
591 wakaba 1.2 \$opt2->{output}->output (output => 'http-cgi');
592    
593     \$self->main_post (\$opt, \$opt2);
594     }
595     EOH
596     }
597    
598     sub make_rule ($$) {
599     my ($src, $Info) = @_;
600     my $type = $src->get_attribute ('Category', make_new_node => 1)->value || [];
601     my $name = $src->get_attribute ('Name', make_new_node => 1)->value;
602 wakaba 1.5 $name =~ s/(?<=.)-/_/g;
603    
604     my $reg_block;
605     $reg_block = qr/[^{}]*(?>[^{}]+|{(??{$reg_block})})*/;
606 wakaba 1.10 my %code;
607     for my $codename ([qw/Formatting main/], [qw/After after/],
608     [qw/Before before/], [qw/Pre pre/], [qw/Post post/],
609     [qw/Attribute attr/]) {
610     my $main = code $Info, $src->get_attribute_value ($codename->[0]);
611     next unless $main;
612     $main = line ($Info, node_path =>
613     "FormattingRule[name()='@{[list $type]}/$name']/".$codename->[0])
614     . $main;
615    
616 wakaba 1.11 if ( $main =~ /\$f\b/
617 wakaba 1.10 or $main =~ /\$rule_name\b/
618     or $main =~ /\$[opr]\b/
619 wakaba 1.11 or $main =~ /[%\$]opt\b/
620     or $main =~ /\$param_(?:name|value)\n/) {
621     if ($codename->[0] ne 'Attribute') {
622     $main = q{my ($f, $rule_name, $p, $o, %opt) = @_;}."\n".$main;
623     } else {
624     $main = q{my ($f, $rule_name, $p, $o, $param_name => $param_value, %opt) = @_;}."\n".$main;
625     }
626     }
627 wakaba 1.10 if ($main =~ /\$r\b/) {
628     warn qq(Rule @{[list $type]}/$name: Use of \$r is deprecated);
629     $main = q{my $r = '';} . "\n" . $main . "\n"
630     . q{$p->{-parent}->append_node ($r, node_or_text => 1);};
631     }
632     $main =~ s{__ATTR(TEXT|NODE)?:%(\w+|{$reg_block})(?:->{($reg_block)})?__;}
633     {($1 eq 'TEXT' ? '$p->{'.literal_or_code($Info, $2)
634     .'} = do { my $r = ' : '')
635     .'$f->parse_attr ($p=>'.literal_or_code($Info, $2).', $o, '
636     .($3?'-parent => '.$3.', ':'')
637     .($1?'-non_parsed_to_node => 1, ':'')
638     .'%opt)'
639     .($1 eq 'TEXT' ? '; ref $r?$r->inner_text:$r}'
640     : '')
641     .';'}ge;
642     $code{$codename->[1]} = barecode "sub {$main}";
643     }
644    
645     my $main = literal {
646     Description => [barecode m13ed_val_list $src, 'Description'],
647     Parameter => {do {
648     my @r;
649     for (@{$src->child_nodes}) {
650     if ($_->local_name eq 'Parameter') {
651     push @r, $_->get_attribute_value ('Name')
652     => {Type => $_->get_attribute_value ('Type'),
653     Default => $_->get_attribute_value ('Default'),
654     Description => [barecode m13ed_val_list $_, 'Description']};
655     }
656     }
657     @r;
658     }},
659     %code,
660     };
661     $main .= line $Info, reset => 1;
662    
663    
664     my $amain = <<EOH;
665 wakaba 1.2 {
666 wakaba 1.5 main => sub {$main},
667 wakaba 1.9 @{[line ($Info, reset => 1)]}
668 wakaba 1.2 Description => [@{[m13ed_val_list $src, 'Description']}],
669     Parameter => {@{[do{
670     }]}},
671     }
672     EOH
673 wakaba 1.5 my $r = change_package $Info, $Info->{module_name};
674 wakaba 1.13 local $Info->{-message_error_used} = 0;
675 wakaba 1.2 if (@$type == 1) {
676     $type->[0] =~ tr/-/_/;
677 wakaba 1.5 $r .= qq{\$SuikaWiki::Plugin::Rule{$type->[0]}->{$name} = $main;\n};
678 wakaba 1.2 push @{$Info->{provide}->{rule}->{$type->[0]}}, $name;
679     } else {
680 wakaba 1.5 $r .= qq({my \$def = $main;\n);
681 wakaba 1.2 for my $type (@$type) {
682     $type =~ tr/-/_/;
683     $r .= qq{\$SuikaWiki::Plugin::Rule{$type}->{$name} = \$def;\n};
684     push @{$Info->{provide}->{rule}->{$type}}, $name;
685     }
686     $r .= qq(};\n);
687     }
688 wakaba 1.1 $r;
689     }
690    
691 wakaba 1.15 =item FormattingRuleAlias
692    
693     Generating an alias name for a formatting rule that is already loaded.
694     Example:
695    
696     FormattingRuleAlias:
697     @Category[list]:
698     category-1
699     category-2
700     ...
701     @Name: new-rule-name
702     @Reference:
703     @@Category: one-of-category
704     @@Name: one-of-name
705    
706     associates C<(I<category-1>, I<new-rule-name>)>,
707     C<(I<category-2>, I<new-rule-name>)>, ...
708     with C<(I<one-of-category>, I<one-of-name>)>.
709    
710     =cut
711    
712     sub make_rule_alias ($$) {
713     my ($src, $Info) = @_;
714     my $type = $src->get_attribute ('Category', make_new_node => 1)->value || [];
715     my $name = $src->get_attribute ('Name', make_new_node => 1)->value;
716    
717     my $ref = $src->get_attribute ('Reference', make_new_node => 1);
718     my $c = $ref->get_attribute_value ('Category');
719     my $n = $ref->get_attribute_value ('Name');
720    
721     s/(?<=.)-/_/g for $n, $name;
722     tr/-/_/ for $c, @$type;
723    
724     my $def = qq{\$SuikaWiki::Plugin::Rule{@{[literal $c]}}->{@{[literal $n]}} or warn qq{Formatting rule "$c/$n" not defined}};
725    
726     my $r = change_package $Info, $Info->{module_name};
727     for my $type (@$type) {
728     $r .= qq{\$SuikaWiki::Plugin::Rule{@{[literal $type]}}->{@{[literal $name]}} = $def;\n};
729     push @{$Info->{provide}->{rule}->{$type}}, $name;
730     }
731     $r;
732     }
733    
734 wakaba 1.1
735     sub random_module_name ($;$) {
736     my ($Info, $subname) = @_;
737     $subname =~ s/[^0-9A-Za-z_:]//g;
738     my @date = gmtime;
739     my @rand = ('A'..'Z','a'..'z',0..9,'_');
740 wakaba 1.2 sprintf '%s::%s%s%s', $Info->{module_name}, $subname,
741 wakaba 1.1 sprintf ('%02d%02d%02d%02d%02d%02d', @date[5,4,3,2,1,0]),
742     join ('', @rand[rand@rand,rand@rand,rand@rand,rand@rand]);
743     }
744 wakaba 1.14
745     =head1 NAME
746    
747     mkplugin2.pl - SuikaWiki: WikiPlugin Generator
748    
749     =head1 SYNOPSIS
750    
751     mkplugin2.pl pluginsrc.wp2 > plugin.pm
752    
753     =head1 DESCRIPTION
754    
755     C<mkplugin2.pl> generates WikiPlugin module as a Perl module file
756     from WikiPlugin source description. WikiPlugin source description
757     is described in SuikaWikiConfig/2.0 format and it contains
758     definitions of wiki constructions (such as formatting rules and
759     WikiView definitions) as both machine understandable code and
760     human readable documentation. For more information, see
761     <http://suika.fam.cx/~wakaba/-temp/wiki/wiki?WikiPlugin>.
762    
763     This script is part of SuikaWiki.
764    
765     =head1 HISTORY AND COMPATIBILITY
766    
767     C<mkplugin2.pl> introduced as part of SuikaWiki 3.
768     It converts SuikaWiki 3 WikiPlugin source descriptions
769     (in SuikaWikiConfig/2.0) into Perl modules with SuikaWiki 3 interface.
770    
771     SuikaWiki 2 has C<mkplugin.pl>. It also converts WikiPlugin
772     source descriptions into Perl modules. But it support
773     SuikaWiki 2 format of WikiPlugin source description that differs from
774     SuikaWiki 3 format. Wiki programming interface (not limited to
775     WikiPlugin related one) of SuikaWiki 3 also incompatible with that
776     of SuikaWiki 2 so that it is impossible to use SuikaWiki 2 WikiPlugin
777     module with SuikaWiki 3 and vice versa.
778    
779     =head1 SEE ALSO
780    
781     C<SuikaWiki::Plugin>, SuikaWiki:WikiPlugin
782     <http://suika.fam.cx/~wakaba/-temp/wiki/wiki?WikiPlugin>.
783    
784     =head1 LICENSE
785    
786     Copyright 2003-2004 Wakaba <w@suika.fam.cx>. All rights reserved.
787    
788     This program is free software; you can redistribute it and/or
789     modify it under the same terms as Perl itself.
790    
791     =cut
792    
793 wakaba 1.20 1; # $Date: 2004/07/25 06:54:28 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24