/[suikacvs]/messaging/manakai/bin/daf.pl
Suika

Contents of /messaging/manakai/bin/daf.pl

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.19 - (show annotations) (download)
Sun Nov 5 10:57:29 2006 UTC (18 years, 7 months ago) by wakaba
Branch: MAIN
Changes since 1.18: +4 -28 lines
File MIME type: text/plain
++ manakai/bin/ChangeLog	5 Nov 2006 10:53:44 -0000
2006-11-05  Wakaba  <wakaba@suika.fam.cx>

	* daf.pl: Now redundant |get_feature| calls are
	removed.

++ manakai/lib/Message/Util/DIS/ChangeLog	5 Nov 2006 10:56:18 -0000
	* Perl.dis (DISImplementationPerl): Removed.
	(plLoadDISDatabaseModule): |require|s
	modules |Message::Util::DIS::Value|
	and |Message::Util::DIS::DNLite| since
	loaded objects might implement classes
	defined in these modules.

2006-11-05  Wakaba  <wakaba@suika.fam.cx>

++ manakai/lib/Message/DOM/ChangeLog	5 Nov 2006 10:55:00 -0000
	* DOMFeature.dis (ImplementationList, ImplementationSource,
	ImplementationRegistry): Parameters |features|
	now allow |null| (equivalent to an empty string).

2006-11-05  Wakaba  <wakaba@suika.fam.cx>

1 #!/usr/bin/perl -w
2 use strict;
3 use Message::Util::QName::Filter {
4 dis => q<http://suika.fam.cx/~wakaba/archive/2004/8/18/lang#dis-->,
5 dp => q<http://suika.fam.cx/~wakaba/archive/2005/manakai/Util/DIS#Perl/>,
6 ManakaiDOM => q<http://suika.fam.cx/~wakaba/archive/2004/8/18/manakai-dom#>,
7 swcfg21 => q<http://suika.fam.cx/~wakaba/archive/2005/swcfg21#>,
8 };
9
10 our$VERSION=do{my @r=(q$Revision: 1.18 $=~/\d+/g);sprintf "%d."."%02d" x $#r,@r};
11 use Cwd;
12 use Getopt::Long;
13 use Pod::Usage;
14 our %Opt = (create_module => []);
15 my @target_modules;
16 GetOptions (
17 'create-dtd-driver=s' => sub {
18 shift;
19 my $i = [split /\s+/, shift, 3];
20 $i->[3] = 'dtd-driver';
21 push @{$Opt{create_module}}, $i;
22 },
23 'create-dtd-modules=s' => sub {
24 shift;
25 my $i = [split /\s+/, shift, 3];
26 $i->[3] = 'dtd-modules';
27 push @{$Opt{create_module}}, $i;
28 },
29 'create-perl-module=s' => sub {
30 shift;
31 my $i = [split /\s+/, shift, 3];
32 $i->[3] = 'perl-pm';
33 push @{$Opt{create_module}}, $i;
34 push @target_modules, [$i->[0], $i->[2]];
35 },
36 'create-perl-test=s' => sub {
37 shift;
38 my $i = [split /\s+/, shift, 3];
39 $i->[3] = 'perl-t';
40 push @{$Opt{create_module}}, $i;
41 push @target_modules, [$i->[0], $i->[2]];
42 },
43 'debug' => \$Opt{debug},
44 'dis-file-suffix=s' => \$Opt{dis_suffix},
45 'daem-file-suffix=s' => \$Opt{daem_suffix},
46 'dafs-file-suffix=s' => \$Opt{dafs_suffix},
47 'dafx-file-suffix=s' => \$Opt{dafx_suffix},
48 'dtd-file-suffix=s' => \$Opt{dtd_suffix},
49 'help' => \$Opt{help},
50 'load-module=s' => sub {
51 shift;
52 my $i = [split /\s+/, shift, 2];
53 push @target_modules, [$i->[0], $i->[1]];
54 },
55 'mod-file-suffix=s' => \$Opt{mod_suffix},
56 'search-path|I=s' => sub {
57 shift;
58 my @value = split /\s+/, shift;
59 while (my ($ns, $path) = splice @value, 0, 2, ()) {
60 unless (defined $path) {
61 die qq[$0: Search-path parameter without path: "$ns"];
62 }
63 push @{$Opt{input_search_path}->{$ns} ||= []}, $path;
64 }
65 },
66 'search-path-catalog-file-name=s' => sub {
67 shift;
68 require File::Spec;
69 my $path = my $path_base = shift;
70 $path_base =~ s#[^/]+$##;
71 $Opt{search_path_base} = $path_base;
72 open my $file, '<', $path or die "$0: $path: $!";
73 while (<$file>) {
74 if (s/^\s*\@//) { ## Processing instruction
75 my ($target, $data) = split /\s+/;
76 if ($target eq 'base') {
77 $Opt{search_path_base} = File::Spec->rel2abs ($data, $path_base);
78 } else {
79 die "$0: $target: Unknown target";
80 }
81 } elsif (/^\s*\#/) { ## Comment
82 #
83 } elsif (/\S/) { ## Catalog entry
84 s/^\s+//;
85 my ($ns, $path) = split /\s+/;
86 push @{$Opt{input_search_path}->{$ns} ||= []},
87 File::Spec->rel2abs ($path, $Opt{search_path_base});
88 }
89 }
90 ## NOTE: File paths with SPACEs are not supported
91 ## NOTE: Future version might use file: URI instead of file path.
92 },
93 'undef-check!' => \$Opt{no_undef_check},
94 'verbose!' => \$Opt{verbose},
95 ) or pod2usage (2);
96 pod2usage ({-exitval => 0, -verbose => 1}) if $Opt{help};
97 $Opt{no_undef_check} = defined $Opt{no_undef_check}
98 ? $Opt{no_undef_check} ? 0 : 1 : 0;
99 $Opt{dis_suffix} = '.dis' unless defined $Opt{dis_suffix};
100 $Opt{daem_suffix} = '.dafm' unless defined $Opt{daem_suffix};
101 $Opt{dafx_suffix} = '.dafx' unless defined $Opt{dafx_suffix};
102 $Opt{dafs_suffix} = '.dafs' unless defined $Opt{dafs_suffix};
103 $Opt{dtd_suffix} = '.dtd' unless defined $Opt{dtd_suffix};
104 $Opt{mod_suffix} = '.mod' unless defined $Opt{mod_suffix};
105 require Error;
106 $Error::Debug = 1 if $Opt{debug};
107 $Message::Util::Error::VERBOSE = 1 if $Opt{verbose};
108
109 sub status_msg ($) {
110 my $s = shift;
111 $s .= "\n" unless $s =~ /\n$/;
112 print STDERR $s;
113 }
114
115 sub status_msg_ ($) {
116 my $s = shift;
117 print STDERR $s;
118 }
119
120 sub verbose_msg ($) {
121 my $s = shift;
122 $s .= "\n" unless $s =~ /\n$/;
123 print STDERR $s if $Opt{verbose};
124 }
125
126 sub verbose_msg_ ($) {
127 my $s = shift;
128 print STDERR $s if $Opt{verbose};
129 }
130
131 ## ---- The MAIN Program
132
133 my $start_time;
134 BEGIN { $start_time = time }
135
136 use Message::DOM::DOMCore;
137
138 for (@{$Opt{create_module}}) {
139 my (undef, undef, undef, $out_type) = @$_;
140
141 if ($out_type eq 'perl-pm') {
142 require 'manakai/daf-perl-pm.pl';
143 } elsif ($out_type eq 'perl-t') {
144 require 'manakai/daf-perl-t.pl';
145 } elsif ($out_type eq 'dtd-modules') {
146 require 'manakai/daf-dtd-modules.pl';
147 } elsif ($out_type eq 'dtd-driver') {
148 require 'manakai/daf-dtd-modules.pl';
149 }
150 }
151
152 our $impl = $Message::DOM::ImplementationRegistry->get_implementation;
153
154 ## --- Loading and Updating the Database
155
156 my $HasError;
157 our $db = $impl->create_dis_database;
158 $db->pl_database_module_resolver (\&daf_db_module_resolver);
159 $db->dom_config->set_parameter ('error-handler' => \&daf_on_error);
160
161 my $parser = $impl->create_dis_parser;
162 my %ModuleSourceDISDocument;
163 my %ModuleSourceDNLDocument;
164 my %ModuleNameNamespaceBinding = (
165 DISCore => q<http://suika.fam.cx/~wakaba/archive/2004/dis/Core#>,
166 ## This builtin binding is required since
167 ## some module has |DISCore:author| property before |dis:Require|
168 ## property.
169 );
170
171 my $ResourceCount = 0;
172 $db->pl_update_module (\@target_modules,
173 get_module_index_file_name => sub {
174 shift; # $db
175 daf_get_module_index_file_name (@_);
176 },
177 get_module_source_document_from_uri => sub {
178 my ($db, $module_uri, $module_for) = @_;
179 status_msg '';
180 status_msg qq<Loading module <$module_uri> for <$module_for>...>;
181 $ResourceCount = 0;
182
183 unless (defined $ModuleSourceDNLDocument{$module_uri}) {
184 unless (defined $ModuleSourceDISDocument{$module_uri}) {
185 daf_open_source_dis_document ($module_uri);
186 }
187 daf_convert_dis_document_to_dnl_document ();
188 }
189 return $ModuleSourceDNLDocument{$module_uri};
190 },
191 get_module_source_document_from_resource => sub ($$$$$$) {
192 my ($self, $db, $uri, $ns, $ln, $for) = @_;
193 status_msg '';
194 status_msg qq<Loading module "$ln" for <$for>...>;
195 $ResourceCount = 0;
196
197 my $module_uri = $ns.$ln;
198 unless (defined $ModuleSourceDNLDocument{$module_uri}) {
199 unless (defined $ModuleSourceDISDocument{$module_uri}) {
200 daf_open_source_dis_document ($module_uri);
201 }
202 daf_convert_dis_document_to_dnl_document ();
203 }
204 return $ModuleSourceDNLDocument{$module_uri};
205 },
206 get_module_source_revision => sub {
207 my ($db, $module_uri) = @_;
208 my $ns = $module_uri;
209 $ns =~ s/(\w+)\z//;
210 my $ln = $1;
211
212 my $name = dac_search_file_path_stem ($ns, $ln, $Opt{dis_suffix});
213 if (defined $name) {
214 return [stat $name.$Opt{dis_suffix}]->[9];
215 } else {
216 return 0;
217 }
218 },
219 get_referring_module_uri_list => sub {
220 my ($db, $module_uri) = @_;
221 unless (defined $ModuleSourceDNLDocument{$module_uri}) {
222 unless (defined $ModuleSourceDISDocument{$module_uri}) {
223 daf_open_source_dis_document ($module_uri);
224 }
225 }
226 return daf_get_referring_module_uri_list ($module_uri);
227 },
228 on_resource_read => sub ($$) {
229 if ((++$ResourceCount % 10) == 0) {
230 status_msg_ "*";
231 status_msg_ " " if ($ResourceCount % (10 * 10)) == 0;
232 status_msg '' if ($ResourceCount % (10 * 50)) == 0;
233 }
234 });
235
236
237 ## Removes reference from document to database
238 our @Document;
239 for my $dis (@Document) {
240 $dis->unlink_from_document;
241 $dis->dis_database (undef);
242 }
243
244 status_msg '';
245
246 status_msg qq<Reading properties...>;
247 $ResourceCount = 0;
248 $db->read_properties (on_resource_read => sub ($$) {
249 if ((++$ResourceCount % 10) == 0) {
250 status_msg_ "*";
251 status_msg_ " " if ($ResourceCount % (10 * 10)) == 0;
252 status_msg '' if ($ResourceCount % (10 * 50)) == 0;
253 }
254 }, implementation => $impl);
255 status_msg '';
256 status_msg "done";
257
258 status_msg_ qq<Writing database files...>;
259 $db->pl_store ('dummy', sub ($$) {
260 my ($db, $mod, $type) = @_;
261 my $ns = $mod->namespace_uri;
262 my $ln = $mod->local_name;
263 my $suffix = $type eq ExpandedURI q<dp:ModuleIndexFile>
264 ? $Opt{dafx_suffix} : $Opt{daem_suffix};
265 my $name = dac_search_file_path_stem ($ns, $ln, $suffix);
266 if (defined $name) {
267 $name .= $suffix;
268 } elsif (defined ($name = dac_search_file_path_stem
269 ($ns, $ln, $Opt{dis_suffix}))) {
270 $name .= $suffix;
271 } else {
272 $name = Cwd::abs_path
273 (File::Spec->canonpath
274 (File::Spec->catfile
275 (defined $Opt{input_search_path}->{$ns}->[0]
276 ? $Opt{input_search_path}->{$ns}->[0] : '.',
277 $ln.$suffix)));
278 }
279 verbose_msg qq<Database >.
280 ($type eq <Q::dp|ModuleIndexFile> ? 'index' : 'module').
281 qq< <$ns$ln> is written to "$name">;
282 return $name;
283 }, no_main_database => 1);
284 status_msg "done";
285
286 daf_check_undefined ();
287
288 undef %ModuleSourceDNLDocument;
289 exit $HasError if $HasError;
290
291 ## --- Creating Files
292
293 for (@{$Opt{create_module}}) {
294 my ($mod_uri, $out_file_path, $mod_for, $out_type) = @$_;
295
296 if ($out_type eq 'perl-pm') {
297 daf_perl_pm ($mod_uri, $out_file_path, $mod_for);
298 } elsif ($out_type eq 'perl-t') {
299 daf_perl_t ($mod_uri, $out_file_path, $mod_for);
300 } elsif ($out_type eq 'dtd-modules') {
301 daf_dtd_modules ($mod_uri, $out_file_path, $mod_for);
302 } elsif ($out_type eq 'dtd-driver') {
303 daf_dtd_driver ($mod_uri, $out_file_path, $mod_for);
304 }
305 }
306
307 daf_check_undefined ();
308
309 ## --- The END
310
311 status_msg_ "Closing the database...";
312 $db->free;
313 undef $db;
314 status_msg "done";
315
316 undef $impl;
317
318 {
319 use integer;
320 my $time = time - $start_time;
321 status_msg sprintf qq<%d'%02d''>, $time / 60, $time % 60;
322 }
323 exit $HasError;
324
325 END {
326 $db->free if $db;
327 }
328
329 ## ---- Subroutines
330
331 sub daf_open_source_dis_document ($) {
332 my ($module_uri) = @_;
333
334 ## -- Finds |dis| source file
335 my $ns = $module_uri;
336 $ns =~ s/(\w+)\z//;
337 my $ln = $1;
338 my $file_name = dac_search_file_path_stem ($ns, $ln, $Opt{dis_suffix});
339 unless (defined $file_name) {
340 die "$0: Source file for <$ns$ln> is not found";
341 }
342 $file_name .= $Opt{dis_suffix};
343
344 status_msg_ qq<Opening dis source file "$file_name"...>;
345
346 ## -- Opens |dis| file and construct |DISDocument| tree
347 open my $file, '<', $file_name or die "$0: $file_name: $!";
348 my $dis = $parser->parse ({character_stream => $file});
349 require File::Spec;
350 $dis->flag (ExpandedURI q<swcfg21:fileName> =>
351 File::Spec->abs2rel ($file_name));
352 $dis->dis_namespace_resolver (\&daf_module_name_namespace_resolver);
353 close $file;
354
355 ## -- Registers namespace URI
356 my $mod = $dis->module_element;
357 if ($mod) {
358 my $qn = $mod->get_attribute_ns (ExpandedURI q<dis:>, 'QName');
359 if ($qn) {
360 my $prefix = $qn->value;
361 $prefix =~ s/^[^:|]*[:|]\s*//;
362 $prefix =~ s/\s+$//;
363 unless (defined $ModuleNameNamespaceBinding{$prefix}) {
364 $ModuleNameNamespaceBinding{$prefix} = $mod->defining_namespace_uri;
365 }
366 }
367 }
368
369 $ModuleSourceDISDocument{$module_uri} = $dis;
370 status_msg q<done>;
371
372 R: for (@{daf_get_referring_module_uri_list ($module_uri)}) {
373 next R if defined $db->{modDef}->{$_};
374 next R if defined $ModuleSourceDNLDocument{$_};
375 next R if defined $ModuleSourceDISDocument{$_};
376 my $idx_file_name = daf_get_module_index_file_name ($_);
377 if (-f $idx_file_name) {
378 daf_open_current_module_index ($_, $idx_file_name);
379 } else {
380 daf_open_source_dis_document ($_);
381 }
382 }
383 } # daf_open_source_dis_document
384
385 sub daf_open_current_module_index ($$) {
386 my ($module_uri, $file_name) = @_;
387 $db->pl_load_dis_database_index ($file_name);
388
389 R: for (@{$db->get_module ($module_uri)
390 ->get_referring_module_uri_list}) {
391 next R if defined $db->{modDef}->{$_};
392 next R if defined $ModuleSourceDNLDocument{$_};
393 next R if defined $ModuleSourceDISDocument{$_};
394 my $idx_file_name = daf_get_module_index_file_name ($_);
395 if (-f $idx_file_name) {
396 daf_open_current_module_index ($_, $idx_file_name);
397 } else {
398 daf_open_source_dis_document ($_);
399 }
400 }
401 } # daf_open_current_module_index
402
403 sub daf_convert_dis_document_to_dnl_document () {
404 M: for my $module_uri (keys %ModuleSourceDISDocument) {
405 my $dis_doc = $ModuleSourceDISDocument{$module_uri};
406 next M unless $dis_doc;
407 verbose_msg_ qq<Converting <$module_uri>...>;
408 my $dnl_doc = $impl->convert_dis_document_to_dnl_document
409 ($dis_doc, database_arg => $db,
410 base_namespace_binding =>
411 {(map {$_->local_name => $_->target_namespace_uri}
412 grep {$_} values %{$db->{modDef}}),
413 %ModuleNameNamespaceBinding});
414 push @Document, $dnl_doc;
415 $ModuleSourceDNLDocument{$module_uri} = $dnl_doc;
416 $dis_doc->free;
417 delete $ModuleSourceDISDocument{$module_uri};
418 verbose_msg q<done>;
419 }
420 } # daf_convert_dis_document_to_dnl_document
421
422 sub daf_get_referring_module_uri_list ($) {
423 my $module_uri = shift;
424 my $ns = $module_uri;
425 $ns =~ s/\w+\z//;
426 my $src = $ModuleSourceDNLDocument{$module_uri};
427 $src = $ModuleSourceDISDocument{$module_uri} unless defined $src;
428 my $mod_el = $src->module_element;
429 my $r = [];
430 if ($mod_el) {
431 my $req_el = $mod_el->require_element;
432 if ($req_el) {
433 M: for my $m_el (@{$req_el->child_nodes}) {
434 next M unless $m_el->node_type eq '#element';
435 next M unless $m_el->expanded_uri eq ExpandedURI q<dis:Module>;
436 my $qn_el = $m_el->get_attribute_ns (ExpandedURI q<dis:>, 'QName');
437 if ($qn_el) {
438 push @$r, $qn_el->qname_value_uri;
439 } else {
440 my $n_el = $m_el->get_attribute_ns (ExpandedURI q<dis:>, 'Name');
441 if ($n_el) {
442 push @$r, $ns.$n_el->value;
443 } else {
444 # The module itself
445 }
446 }
447 }
448 }
449 }
450 return $r;
451 } # daf_get_referring_module_uri_list
452
453 sub dac_search_file_path_stem ($$$) {
454 my ($ns, $ln, $suffix) = @_;
455 require File::Spec;
456 for my $dir (@{$Opt{input_search_path}->{$ns}||[]}) {
457 my $name = Cwd::abs_path
458 (File::Spec->canonpath
459 (File::Spec->catfile ($dir, $ln)));
460 if (-f $name.$suffix) {
461 return $name;
462 }
463 }
464 return undef;
465 } # dac_search_file_path_stem;
466
467 sub daf_get_module_index_file_name ($$) {
468 my ($module_uri) = @_;
469 my $ns = $module_uri;
470 $ns =~ s/(\w+)\z//;
471 my $ln = $1;
472
473 verbose_msg qq<Database module index <$module_uri> is requested>;
474 my $suffix = $Opt{dafx_suffix};
475 my $name = dac_search_file_path_stem ($ns, $ln, $suffix);
476 if (defined $name) {
477 $name .= $suffix;
478 } elsif (defined ($name = dac_search_file_path_stem
479 ($ns, $ln, $Opt{dis_suffix}))) {
480 $name .= $suffix;
481 } else {
482 $name = Cwd::abs_path
483 (File::Spec->canonpath
484 (File::Spec->catfile
485 (defined $Opt{input_search_path}->{$ns}->[0]
486 ? $Opt{input_search_path}->{$ns}->[0] : '.',
487 $ln.$suffix)));
488 }
489 return $name;
490 } # daf_get_module_index_file_name
491
492 sub daf_module_name_namespace_resolver ($) {
493 my $prefix = shift;
494
495 ## -- From modules in database
496 M: for (values %{$db->{modDef}}) {
497 my $mod = $_;
498 next M unless defined $mod;
499 if ($mod->local_name eq $prefix) {
500 return $mod->target_namespace_uri;
501 }
502 }
503
504 ## -- From not-in-database-yet module list
505 if (defined $ModuleNameNamespaceBinding{$prefix}) {
506 return $ModuleNameNamespaceBinding{$prefix};
507 }
508 return undef;
509 } # daf_module_name_namespace_resolver
510
511 sub daf_db_module_resolver ($$$) {
512 my ($db, $mod, $type) = @_;
513 my $ns = $mod->namespace_uri;
514 my $ln = $mod->local_name;
515 my $suffix = {
516 ExpandedURI q<dp:ModuleIndexFile> => $Opt{dafx_suffix},
517 ExpandedURI q<dp:ModuleResourceFile> => $Opt{daem_suffix},
518 ExpandedURI q<dp:ModuleNodeStorageFile> => $Opt{dafs_suffix},
519 }->{$type} or die "Unsupported type: <$type>";
520 verbose_msg qq<Database module <$ns$ln> is requested>;
521 my $name = dac_search_file_path_stem ($ns, $ln, $suffix);
522 if (defined $name) {
523 return $name.$suffix;
524 } else {
525 return undef;
526 }
527 } # daf_db_module_resolver
528
529 sub daf_on_error ($$) {
530 my ($self, $err) = @_;
531 if ($err->severity == $err->SEVERITY_WARNING) {
532 my $info = ExpandedURI q<dp:info>;
533 if ($err->type =~ /\Q$info\E/) {
534 my $msg = $err->text;
535 if ($msg =~ /\.\.\.\z/) {
536 verbose_msg_ $msg;
537 } else {
538 verbose_msg $msg;
539 }
540 } else {
541 my $msg = $err->text;
542 if ($msg =~ /\.\.\.\z/) {
543 status_msg_ $msg;
544 } else {
545 status_msg $msg;
546 }
547 }
548 } else {
549 warn $err;
550 $HasError = 1;
551 }
552 } # daf_on_error
553
554 sub daf_check_undefined () {
555 unless ($Opt{no_undef_check}) {
556 status_msg_ "Checking undefined resources...";
557 $db->check_undefined_resource;
558 print STDERR "done\n";
559 }
560 } # daf_check_undefined
561
562 __END__
563
564 =head1 NAME
565
566 dac.pl - Creating "dac" Database File from "dis" Source Files
567
568 =head1 SYNOPSIS
569
570 perl path/to/dac.pl [--input-db-file-name=input.dac] \
571 --output-file-name=out.dac [options...] \
572 input.dis
573 perl path/to/dac.pl --help
574
575 =head1 DESCRIPTION
576
577 This script, C<dac.pl>, compiles "dis" source files into "dac"
578 database file. The generated database file can be used
579 in turn to generate Perl module file, for example, by another
580 script C<dac2pm.pl> or can be used to create larger database
581 by specifying its file name as the C<--input-db-file-name>
582 argument of another C<dac.pl> execution.
583
584 This script is part of manakai.
585
586 =head1 OPTIONS
587
588 =over 4
589
590 =item I<input.dis> (Required)
591
592 The unnamed option specifies a file name path of the source "dis" file
593 from which a database is created. This option is required.
594
595 =item C<--input-db-file-name=I<file-name>> (Default: none)
596
597 A file path of the base database. This option is optional; if this
598 option is specified, the database file is loaded first
599 and then I<input.dis> file is loaded in the context of it.
600 Otherwise, a new database is created.
601
602 =back
603
604 =head1 SEE ALSO
605
606 L<lib/Message/Util/DIS.dis> - The actual implementation
607 of the "dis" interpretation.
608
609 =head1 LICENSE
610
611 Copyright 2004-2006 Wakaba <w@suika.fam.cx>. All rights reserved.
612
613 This program is free software; you can redistribute it and/or
614 modify it under the same terms as Perl itself.
615
616 =cut

admin@suikawiki.org
ViewVC Help
Powered by ViewVC 1.1.24