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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.21 - (hide annotations) (download)
Mon Nov 8 09:57:49 2004 UTC (19 years, 11 months ago) by wakaba
Branch: MAIN
CVS Tags: suikawiki3-redirect, HEAD
Branch point for: helowiki, helowiki-2005
Changes since 1.20: +3 -2 lines
File MIME type: text/plain
Committed

1 wakaba 1.1 #!/usr/bin/perl
2     use strict;
3 wakaba 1.21 our $VERSION = do{my @r=(q$Revision: 1.20 $=~/\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.21 local $Error::Depth = $Error::Depth + 1;
261 wakaba 1.5 $self->SUPER::convert ($source, %opt);
262     }
263     EOH
264    
265     for (@{$src->child_nodes}) {
266     if ($_->local_name eq 'Converter') {
267 wakaba 1.8 if ($convert) {
268     $r .= $convert;
269     $r .= line $Info, reset => 1;
270     undef $convert;
271     }
272 wakaba 1.5 $r .= make_format_converter ($_, $Info);
273 wakaba 1.10 } elsif ($_->local_name eq 'WikiForm') {
274     $r .= q(sub wikiform {)."\n".q(my ($self, $source, %opt) = @_;);
275     $r .= line $Info, node_path => qq(Format[module-name()=$module_name]/WikiForm);
276     $r .= code $Info, $_->get_attribute_value ('Main');
277     $r .= line $Info, reset => 1;
278     $r .= qq(}\n);
279 wakaba 1.11 } elsif ($_->local_name eq 'HeadSummary') {
280     $r .= q(sub headsummary {)."\n".q(my ($self, $source, %opt) = @_;);
281     $r .= line $Info, node_path => qq(Format[module-name()=$module_name]/HeadSummary);
282     $r .= code $Info, $_->get_attribute_value ('Main');
283     $r .= line $Info, reset => 1;
284     $r .= qq(}\n);
285 wakaba 1.10 } elsif ($_->local_name eq 'NextIndex') {
286     my $name = $_->get_attribute_value ('Name', default => '');
287     $r .= q(sub next_index_for_).$name
288     . q( {)."\n".q(my ($self, $source, %opt) = @_;)
289     . line $Info, node_path => qq(Format[module-name()=$module_name]/NextIndex[Name=$name]);
290     $r .= code $Info, $_->get_attribute_value ('Main');
291     $r .= line $Info, reset => 1;
292     $r .= qq(}\n);
293 wakaba 1.19 } elsif ({qw/content_written 1 content_removed 1 content_type_changed_from 1
294     content_prop_modified 1/}
295 wakaba 1.18 ->{my $node_name = $_->local_name}) {
296     $r .= q(sub ).$node_name
297     . q( {)."\n".q(my ($self, %opt) = @_;)
298     . line $Info, node_path => qq(Format[module-name()=$module_name]/$node_name]);
299     $r .= code $Info, $_->get_attribute_value ('Main');
300     $r .= line $Info, reset => 1;
301     $r .= qq(}\n);
302 wakaba 1.5 } elsif ($_->local_name eq 'Use') {
303 wakaba 1.6 $r .= line $Info, node_path => qq(Format[module-name()=$module_name]/Use);
304 wakaba 1.13 $r .= code $Info, $_->inner_text;
305 wakaba 1.19 } elsif ($_->local_name eq 'Prop') {
306     $r .= q<sub prop ($$;%) { my ($self, $name, %opt) = @_;>;
307     my $f = 0;
308     for my $prop (@{$_->child_nodes}) {
309     if ($prop->local_name =~ /^([^:]+):([^:]+)$/) {
310     $r .= qq{if (\$name eq @{[literal expanded_uri $Info, $1, $2]}) { return @{[literal $prop->value]} } els};
311     $f = 1;
312     }
313     }
314     $r .= q<e {> if $f;
315     $r .= q{$self->SUPER::prop ($name, %opt);};
316     $r .= q<}> if $f;
317     $r .= qq<\n}\n>;
318 wakaba 1.5 }
319     }
320     $r;
321     }
322    
323     sub make_format_converter ($$) {
324     my ($src, $Info) = @_;
325     my %def;
326 wakaba 1.16 for (qw/Type Name Version TypeURIReference IsFragment IsPlaceholder/) {
327     $def{$_} = $src->get_attribute_value ($_);
328     delete $def{$_} unless defined $def{$_};
329     }
330     $def{Type_param} = {map {$_->local_name => $_->value}
331     @{$src->get_attribute ('Type', make_new_node => 1)
332     ->child_nodes}};
333     my $type = serialize_media_type ($Info,
334     Type => $def{Type},
335     Type_param => $def{Type_param},
336     Name => $def{Name},
337     Version => $def{Version},
338     URIReference => $def{TypeURIReference},
339     IsFragment => $def{IsFragment},
340     IsPlaceholder => $def{IsPlaceholder});
341     $def{serialized_type} = $type->{_};
342 wakaba 1.5
343 wakaba 1.12 for (qw/Main ToString ToOctetStream/) {
344     my $def = $src->get_attribute_value ($_);
345     next unless $def;
346     $def{$_} = line ($Info, node_path => '//Converter/'.$_)
347     . $def
348     . line ($Info, reset => 1);
349     if ($def{$_} =~ /\$r\b/) {
350     $def{$_} = 'my $r;'."\n".$def{$_}."\n".'$r';
351     }
352     $def{$_} = barecode code $Info,
353     'sub {my ($self, $source, $opt) = @_;'
354     . $def{$_} . '}';
355     }
356 wakaba 1.5
357     my $r = list %def;
358 wakaba 1.16 if ($type->{Type}) {
359     $r = qq{\$Converter->{@{[literal $type->{Type}]}} = {$r};\n};
360     $r .= qq{\$Converter->{@{[literal $type->{Magic}]}} = \$Converter->{@{[literal $type->{Type}]}};\n}
361     if $type->{Magic};
362     $r .= qq{\$Converter->{@{[literal $type->{URIReference}]}} = \$Converter->{@{[literal $type->{Type}]}};\n}
363     if $type->{URIReference};
364     } elsif ($type->{Magic}) {
365     $r = qq{\$Converter->{@{[literal $type->{Magic}]}} = {$r};\n};
366     $r .= qq{\$Converter->{@{[literal $type->{URIReference}]}} = \$Converter->{@{[literal $type->{Magic}]}};\n}
367     if $type->{URIReference};
368     } elsif ($type->{URIReference}) {
369     $r = qq{\$Converter->{@{[literal $type->{URIReference}]}} = {$r};\n};
370 wakaba 1.9 } else {
371 wakaba 1.16 $r = 'BEGIN { die "Invalid Syntax of Converter: Type or Name or TypeURIReference property required" }';
372 wakaba 1.5 }
373     $r;
374     }
375    
376 wakaba 1.16 sub serialize_media_type ($%) {
377     my ($Info, %opt) = @_;
378     my %return;
379     if ($opt{Type}) {
380     $return{Type} = 'IMT:'.$opt{Type};
381     if ($opt{Type_param}) {
382     $return{Type} .= join '', map {my $s;
383     ';'. $_ .'="'
384     . (($s = $opt{Type_param}->{$_}) =~ s/([\\"])/\\$1/g, $s)
385     . '"'
386     } sort {
387     $a cmp $b
388     } keys %{$opt{Type_param}};
389     }
390     }
391     if ($opt{Magic}) {
392     $return{Magic} = 'MAGIC:'.$opt{Magic};
393     } elsif ($opt{Name}) {
394     $return{Name} = 'MAGIC:'.$opt{Name}.'/*';
395     $return{Magic} = 'MAGIC:'.$opt{Name}.'/'.$opt{Version} if $opt{Version};
396     }
397     if ($opt{URIReference}) {
398     $return{URIReference} = $opt{URIReference};
399     }
400     my $flag = '##';
401     $flag .= 'f' if $opt{IsFragment};
402     $flag .= 'p' if $opt{IsPlaceholder};
403     for (qw/URIReference Type Magic Name/) {
404     $return{$_} .= $flag if $return{$_};
405     }
406     $return{_} = $return{URIReference} || $return{Type}
407     || $return{Magic} || $return{Name};
408     \%return;
409     }
410    
411    
412 wakaba 1.2 sub make_function ($$) {
413     my ($src, $Info) = @_;
414     ## TODO: support of ARGV property
415 wakaba 1.6 my $name;
416 wakaba 1.2 my $r = <<EOH;
417 wakaba 1.5 @{[change_package $Info, $Info->{module_name}]}
418 wakaba 1.6 sub @{[$name = $src->get_attribute_value ('Name')]} {
419     @{[line $Info, node_path => "Function[Name='$name']/Main"]}@{[
420     code $Info, $src->get_attribute_value ('Main')
421 wakaba 1.9 ]}
422 wakaba 1.2 }
423 wakaba 1.9 @{[line $Info, reset => 1]}
424 wakaba 1.2 EOH
425     }
426    
427     sub register_plugin_const ($$) {
428     my ($src, $Info) = @_;
429     for (@{$src->child_nodes}) {
430 wakaba 1.11 $Info->{const}->{$_->local_name} = literal_or_code $Info, $_->value;
431 wakaba 1.2 }
432     }
433    
434     sub make_resdef ($$) {
435     my ($src, $Info) = @_;
436 wakaba 1.5 my $r = change_package $Info, 'SuikaWiki::Plugin::Resource';
437 wakaba 1.13 local $Info->{-message_error_used} = 0;
438 wakaba 1.5 $r .= qq{our \$BaseResource;\n};
439 wakaba 1.2 for (@{$src->child_nodes}) {
440     if ($_->node_type eq '#element') {
441     my $lang = literal ($_->get_attribute_value ('lang') || 'und');
442     my $script = literal $_->get_attribute_value ('script');
443     my $name = literal $_->local_name;
444     my $val = literal n11n $_->value;
445     $r .= qq{\$BaseResource->{$lang}->{$script}->{$name} = $val;\n};
446     }
447     }
448     $r;
449     }
450 wakaba 1.1
451     sub make_viewfragment ($$) {
452     my ($src, $Info) = @_;
453     my $r = '';
454 wakaba 1.4 my $body = <<EOH;
455     {
456 wakaba 1.2 Main => @{[literal $src->get_attribute_value ('Formatting')]},
457     Order => @{[0+$src->get_attribute_value ('Order')]},
458     Description => [@{[m13ed_val_list $src, 'Description']}],
459     };
460     EOH
461 wakaba 1.4 ## Recommended format
462     my $name = $src->get_attribute_value ('Template');
463     if (ref ($name) and @$name > 1) {
464     $r .= qq({my \$def = $body;\n);
465     for (@$name) {
466     my $name = $_; $name =~ tr/-/_/;
467     $r .= qq(push \@{\$SuikaWiki::View::Implementation::TemplateFragment{@{[literal $name]}}}, \$def;\n);
468     push @{$Info->{provide}->{viewfragment}}, {Name => $name};
469     }
470     $r .= qq(}\n);
471     } else { ## Obsoleted format
472     $name = ref $name ? $name->[0] : $src->get_attribute_value ('Name');
473     $name =~ tr/-/_/;
474     $r .= qq(push \@{\$SuikaWiki::View::Implementation::TemplateFragment{@{[literal $name]}}}, $body);
475     push @{$Info->{provide}->{viewfragment}}, {Name => $name};
476     }
477 wakaba 1.1 $r;
478     }
479    
480     sub make_viewdef ($$) {
481     my ($src, $Info) = @_;
482     my $ViewProp = {};
483     my $r = '';
484 wakaba 1.6 $ViewProp->{Name} = n11n $src->get_attribute_value ('Mode');
485 wakaba 1.10 $ViewProp->{Name} =~ s/(?<=.)-/_/g;
486 wakaba 1.1 $ViewProp->{pack_name} = random_module_name ($Info, $ViewProp->{Name});
487    
488 wakaba 1.2 $ViewProp->{condition_stringified} = hash
489 wakaba 1.1 mode => $ViewProp->{Name},
490     map {($_->local_name => $_->value)}
491     @{$src->get_attribute ('Condition',make_new_node=>1)->child_nodes};
492    
493     $r .= <<EOH;
494     push \@SuikaWiki::View::Implementation::CommonViewDefs, {
495     condition => {$ViewProp->{condition_stringified}},
496     object_class => q#$ViewProp->{pack_name}#,
497     };
498 wakaba 1.5 @{[change_package $Info, $ViewProp->{pack_name}]}
499 wakaba 1.1 our \@ISA = q#SuikaWiki::View::template#;
500     EOH
501 wakaba 1.13 local $Info->{-message_error_used} = 0;
502 wakaba 1.11 my $use = $src->get_attribute ('Use');
503     if (ref $use) {
504     $r .= line $Info, node_path => "ViewDefinition[Mode='$ViewProp->{Name}']/Use";
505 wakaba 1.13 $r .= code $Info, $use->inner_text;
506     $r .= "\n\n";
507 wakaba 1.11 }
508    
509 wakaba 1.1 for (@{$src->child_nodes}) {
510 wakaba 1.2 if ($_->local_name eq 'template') {
511 wakaba 1.8 $r .= make_view_template_method ($_, $Info, $ViewProp);
512 wakaba 1.2 } elsif ($_->local_name eq 'method') {
513 wakaba 1.6 my $method_name = $_->get_attribute_value ('Name');
514 wakaba 1.1 $r .= ({
515 wakaba 1.8 main => q(sub main ($$) {)."\n".q(my ($self, $opt, $opt2) = @_;)."\n",
516 wakaba 1.1 main_pre => q(sub main_pre ($$$) {)."\n".q(my ($self, $opt, $opt2) = @_;)."\n",
517     main_post => q(sub main_post ($$$) {)."\n".q(my ($self, $opt, $opt2) = @_;)."\n",
518 wakaba 1.6 }->{$method_name}
519     ||qq(sub @{[$method_name]} {\n))
520     . line ($Info, node_path => "ViewDefinition[Mode='$ViewProp->{Name}']/method[Name='$method_name']")
521 wakaba 1.2 . code ($Info, $_->value)
522 wakaba 1.9 . qq(}\n)
523     . line ($Info, reset => 1);
524 wakaba 1.1 }
525     }
526 wakaba 1.2 my $prop = {Name => $ViewProp->{Name},
527     Description => barecode m13ed_val_list $_, 'Description'};
528     push @{$Info->{provide}->{viewdef}}, $prop;
529     $r;
530     }
531    
532     sub make_view_template_method ($$) {
533 wakaba 1.8 my ($src, $Info, $ViewProp) = @_;
534 wakaba 1.18 my $media_type = $src->get_attribute_value
535     ('media-type',
536     default => q<application/octet-stream>);
537 wakaba 1.2 my $r = <<EOH;
538    
539     sub main (\$\$\$) {
540     my (\$self, \$opt, \$opt2) = \@_;
541     require SuikaWiki::Output::HTTP;
542     \$opt2->{output} = SuikaWiki::Output::HTTP->new
543     (wiki => \$self->{view}->{wiki},
544     view => \$self->{view}, viewobj => \$self);
545     for (\@{\$self->{view}->{wiki}->{var}->{client}->{used_for_negotiate}},
546     'Accept-Language') {
547     \$opt2->{output}->add_negotiate_header_field (\$_);
548     }
549    
550     \$opt2->{template} = @{[literal $src->get_attribute ('body', make_new_node => 1)->inner_text]};
551     \$opt2->{o} = bless {
552     ## SuikaWiki 3 WikiPlugin interface
553     wiki => \$self->{view}->{wiki},
554     plugin => \$self->{view}->{wiki}->{plugin},
555     var => {},
556     }, 'SuikaWiki::Plugin';
557     @{[do{my $x=$src->get_attribute('http-status-code',make_new_node=>1)->inner_text;
558     $x?q{$opt2->{output}->{status_code} = }.(0 + $x).q{;}:q{}}]}
559     @{[do{my $x=$src->get_attribute('http-status-phrase',make_new_node=>1)->inner_text;
560 wakaba 1.8 $x?q{$opt2->{output}->{status_phrase} = }.literal($x).q{;}:q{}}]}
561 wakaba 1.18 \$opt2->{output}->{entity}->{media_type} = @{[literal $media_type]};
562    
563 wakaba 1.2 @{[($src->get_attribute ('use-media-type-charset',make_new_node=>1)
564     ->inner_text || 0) ?
565     q{$opt2->{output}->{entity}->{charset} = $self->{view}->{wiki}->{config}->{charset}->{output};}:
566     q{}]}
567     @{[do{my $x = $src->get_attribute ('expires',make_new_node=>1)->inner_text;
568     if ($x =~ /%%(\w+)%%/) {
569     qq{\$opt2->{output}->set_expires (%{\$self->{view}->{wiki}->{config}->{entity}->{expires}->{@{[literal $1]}}});};
570     } else {
571     qq{\$opt2->{output}->set_expires (delta => @{[0 + $x]});};
572     }
573     }]}
574 wakaba 1.12 \$opt2->{output}->{entity}->{body_is_octet_stream} = @{[
575     $src->get_attribute ('body')->get_attribute_value ('is-octet-stream', default_value => 0)
576     or 0
577     ]};
578 wakaba 1.2
579     \$self->{view}->{wiki}->init_db;
580     \$self->main_pre (\$opt, \$opt2);
581    
582 wakaba 1.18 @{[$media_type eq 'text/html' ? q{require Message::Markup::XML::Serialize::HTML;} : '']}
583 wakaba 1.8 use Message::Util::Error;
584     try {
585     \$opt2->{output}->{entity}->{body}
586 wakaba 1.18 = @{[$media_type eq 'text/html' ? q{Message::Markup::XML::Serialize::HTML::html_simple} : '']}
587     (SuikaWiki::Plugin->formatter ('view')
588     ->replace (\$opt2->{template}, param => \$opt2->{o}));
589 wakaba 1.8 } \$self->{view}->{wiki}->{config}->{catch}->{ @{[
590     $ViewProp->{Name} eq '-error' ? 'formatter_view_error'
591     : 'formatter_view' ]} };
592 wakaba 1.2 \$opt2->{output}->output (output => 'http-cgi');
593    
594     \$self->main_post (\$opt, \$opt2);
595     }
596     EOH
597     }
598    
599     sub make_rule ($$) {
600     my ($src, $Info) = @_;
601     my $type = $src->get_attribute ('Category', make_new_node => 1)->value || [];
602     my $name = $src->get_attribute ('Name', make_new_node => 1)->value;
603 wakaba 1.5 $name =~ s/(?<=.)-/_/g;
604    
605     my $reg_block;
606     $reg_block = qr/[^{}]*(?>[^{}]+|{(??{$reg_block})})*/;
607 wakaba 1.10 my %code;
608     for my $codename ([qw/Formatting main/], [qw/After after/],
609     [qw/Before before/], [qw/Pre pre/], [qw/Post post/],
610     [qw/Attribute attr/]) {
611     my $main = code $Info, $src->get_attribute_value ($codename->[0]);
612     next unless $main;
613     $main = line ($Info, node_path =>
614     "FormattingRule[name()='@{[list $type]}/$name']/".$codename->[0])
615     . $main;
616    
617 wakaba 1.11 if ( $main =~ /\$f\b/
618 wakaba 1.10 or $main =~ /\$rule_name\b/
619     or $main =~ /\$[opr]\b/
620 wakaba 1.11 or $main =~ /[%\$]opt\b/
621     or $main =~ /\$param_(?:name|value)\n/) {
622     if ($codename->[0] ne 'Attribute') {
623     $main = q{my ($f, $rule_name, $p, $o, %opt) = @_;}."\n".$main;
624     } else {
625     $main = q{my ($f, $rule_name, $p, $o, $param_name => $param_value, %opt) = @_;}."\n".$main;
626     }
627     }
628 wakaba 1.10 if ($main =~ /\$r\b/) {
629     warn qq(Rule @{[list $type]}/$name: Use of \$r is deprecated);
630     $main = q{my $r = '';} . "\n" . $main . "\n"
631     . q{$p->{-parent}->append_node ($r, node_or_text => 1);};
632     }
633     $main =~ s{__ATTR(TEXT|NODE)?:%(\w+|{$reg_block})(?:->{($reg_block)})?__;}
634     {($1 eq 'TEXT' ? '$p->{'.literal_or_code($Info, $2)
635     .'} = do { my $r = ' : '')
636     .'$f->parse_attr ($p=>'.literal_or_code($Info, $2).', $o, '
637     .($3?'-parent => '.$3.', ':'')
638     .($1?'-non_parsed_to_node => 1, ':'')
639     .'%opt)'
640     .($1 eq 'TEXT' ? '; ref $r?$r->inner_text:$r}'
641     : '')
642     .';'}ge;
643     $code{$codename->[1]} = barecode "sub {$main}";
644     }
645    
646     my $main = literal {
647     Description => [barecode m13ed_val_list $src, 'Description'],
648     Parameter => {do {
649     my @r;
650     for (@{$src->child_nodes}) {
651     if ($_->local_name eq 'Parameter') {
652     push @r, $_->get_attribute_value ('Name')
653     => {Type => $_->get_attribute_value ('Type'),
654     Default => $_->get_attribute_value ('Default'),
655     Description => [barecode m13ed_val_list $_, 'Description']};
656     }
657     }
658     @r;
659     }},
660     %code,
661     };
662     $main .= line $Info, reset => 1;
663    
664    
665     my $amain = <<EOH;
666 wakaba 1.2 {
667 wakaba 1.5 main => sub {$main},
668 wakaba 1.9 @{[line ($Info, reset => 1)]}
669 wakaba 1.2 Description => [@{[m13ed_val_list $src, 'Description']}],
670     Parameter => {@{[do{
671     }]}},
672     }
673     EOH
674 wakaba 1.5 my $r = change_package $Info, $Info->{module_name};
675 wakaba 1.13 local $Info->{-message_error_used} = 0;
676 wakaba 1.2 if (@$type == 1) {
677     $type->[0] =~ tr/-/_/;
678 wakaba 1.5 $r .= qq{\$SuikaWiki::Plugin::Rule{$type->[0]}->{$name} = $main;\n};
679 wakaba 1.2 push @{$Info->{provide}->{rule}->{$type->[0]}}, $name;
680     } else {
681 wakaba 1.5 $r .= qq({my \$def = $main;\n);
682 wakaba 1.2 for my $type (@$type) {
683     $type =~ tr/-/_/;
684     $r .= qq{\$SuikaWiki::Plugin::Rule{$type}->{$name} = \$def;\n};
685     push @{$Info->{provide}->{rule}->{$type}}, $name;
686     }
687     $r .= qq(};\n);
688     }
689 wakaba 1.1 $r;
690     }
691    
692 wakaba 1.15 =item FormattingRuleAlias
693    
694     Generating an alias name for a formatting rule that is already loaded.
695     Example:
696    
697     FormattingRuleAlias:
698     @Category[list]:
699     category-1
700     category-2
701     ...
702     @Name: new-rule-name
703     @Reference:
704     @@Category: one-of-category
705     @@Name: one-of-name
706    
707     associates C<(I<category-1>, I<new-rule-name>)>,
708     C<(I<category-2>, I<new-rule-name>)>, ...
709     with C<(I<one-of-category>, I<one-of-name>)>.
710    
711     =cut
712    
713     sub make_rule_alias ($$) {
714     my ($src, $Info) = @_;
715     my $type = $src->get_attribute ('Category', make_new_node => 1)->value || [];
716     my $name = $src->get_attribute ('Name', make_new_node => 1)->value;
717    
718     my $ref = $src->get_attribute ('Reference', make_new_node => 1);
719     my $c = $ref->get_attribute_value ('Category');
720     my $n = $ref->get_attribute_value ('Name');
721    
722     s/(?<=.)-/_/g for $n, $name;
723     tr/-/_/ for $c, @$type;
724    
725     my $def = qq{\$SuikaWiki::Plugin::Rule{@{[literal $c]}}->{@{[literal $n]}} or warn qq{Formatting rule "$c/$n" not defined}};
726    
727     my $r = change_package $Info, $Info->{module_name};
728     for my $type (@$type) {
729     $r .= qq{\$SuikaWiki::Plugin::Rule{@{[literal $type]}}->{@{[literal $name]}} = $def;\n};
730     push @{$Info->{provide}->{rule}->{$type}}, $name;
731     }
732     $r;
733     }
734    
735 wakaba 1.1
736     sub random_module_name ($;$) {
737     my ($Info, $subname) = @_;
738     $subname =~ s/[^0-9A-Za-z_:]//g;
739     my @date = gmtime;
740     my @rand = ('A'..'Z','a'..'z',0..9,'_');
741 wakaba 1.2 sprintf '%s::%s%s%s', $Info->{module_name}, $subname,
742 wakaba 1.1 sprintf ('%02d%02d%02d%02d%02d%02d', @date[5,4,3,2,1,0]),
743     join ('', @rand[rand@rand,rand@rand,rand@rand,rand@rand]);
744     }
745 wakaba 1.14
746     =head1 NAME
747    
748     mkplugin2.pl - SuikaWiki: WikiPlugin Generator
749    
750     =head1 SYNOPSIS
751    
752     mkplugin2.pl pluginsrc.wp2 > plugin.pm
753    
754     =head1 DESCRIPTION
755    
756     C<mkplugin2.pl> generates WikiPlugin module as a Perl module file
757     from WikiPlugin source description. WikiPlugin source description
758     is described in SuikaWikiConfig/2.0 format and it contains
759     definitions of wiki constructions (such as formatting rules and
760     WikiView definitions) as both machine understandable code and
761     human readable documentation. For more information, see
762     <http://suika.fam.cx/~wakaba/-temp/wiki/wiki?WikiPlugin>.
763    
764     This script is part of SuikaWiki.
765    
766     =head1 HISTORY AND COMPATIBILITY
767    
768     C<mkplugin2.pl> introduced as part of SuikaWiki 3.
769     It converts SuikaWiki 3 WikiPlugin source descriptions
770     (in SuikaWikiConfig/2.0) into Perl modules with SuikaWiki 3 interface.
771    
772     SuikaWiki 2 has C<mkplugin.pl>. It also converts WikiPlugin
773     source descriptions into Perl modules. But it support
774     SuikaWiki 2 format of WikiPlugin source description that differs from
775     SuikaWiki 3 format. Wiki programming interface (not limited to
776     WikiPlugin related one) of SuikaWiki 3 also incompatible with that
777     of SuikaWiki 2 so that it is impossible to use SuikaWiki 2 WikiPlugin
778     module with SuikaWiki 3 and vice versa.
779    
780     =head1 SEE ALSO
781    
782     C<SuikaWiki::Plugin>, SuikaWiki:WikiPlugin
783     <http://suika.fam.cx/~wakaba/-temp/wiki/wiki?WikiPlugin>.
784    
785     =head1 LICENSE
786    
787     Copyright 2003-2004 Wakaba <w@suika.fam.cx>. All rights reserved.
788    
789     This program is free software; you can redistribute it and/or
790     modify it under the same terms as Perl itself.
791    
792     =cut
793    
794 wakaba 1.21 1; # $Date: 2004/09/21 03:18:21 $

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24