#!/usr/bin/perl use lib "../lib"; use CGI::Carp 'fatalsToBrowser'; use Algorithm::Diff qw(traverse_sequences); # use strict; # # yukiwiki.cgi - Yet another WikiWikiWeb clone. # # Copyright (C) 2000,2001 by Hiroshi Yuki. # # http://www.hyuki.com/yukiwiki/ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # $Id: wiki.cgi,v 1.13 2002/04/02 00:28:14 wakaba Exp $ ############################## my $version = "1.6.6"; ############################## # 単独テストのときには 1 にする。 my $testing = 0; ############################## # 漢字ライブラリ my $jcodelib = 'jcode.pl'; ############################## # 保存・表示の漢字コード my $kanjicode = 'euc'; # 'sjis' 'euc' my $charset = 'euc-jisx0213'; # 'Shift_JIS' 'EUC-JP' ############################## # dbmopenが使えるなら1、使えないなら0 my $dbmopen = 0; ############################## # データベース名(.pag, .dir, .dbなどは不要) # $dbmopen = 1のときはデータベース名、 # $dbmopen = 0のときはディレクトリ名になる。 my $dbname = './wikidata'; my $diffdbname = './wikidiff'; ############################## # 修正者の氏名(自由に変更してください) my $modifier = 'suika'; ############################## # 修正者のWebページ(自由に変更してください) my $modifierlink = 'http://suika.fam.cx/'; ############################## # このページのURL my $thisurl = 'wiki'; ############################## # 開始ページ名 my $toppage = 'HomePage'; ############################## # 最終更新ページ名 my $whatsnew = 'RecentChanges'; ############################## # 最終更新に掲載するページ数 my $maxnew = 50; ############################## # アイコンファイル名(カラー版) my $iconfile = ''; ############################## # アイコンファイル名(モノクロ版) # my $iconfile = ''; ############################## # ページを変更したときにtouchするファイル(''なら何もしない) my $touchfile = 'touch.txt'; ############################## # プレビュー用の背景色 my $preview_color = '#FFCCCC'; ############################## # 全ページのスタイル my $style = <<'EOD'; @import '/s/simpledoc'; pre, dl, ul, ol, p, blockquote { line-height:120%; } a.wiki .mark {vertical-align: sub, color: GrayText} a { text-decoration: none; } a:link { color: #0000FF; background-color: #FFFFFF; } a:visited { color: #9900CC; background-color: #FFFFFF; } a:hover { text-decoration: underline; } EOD ############################## # テキスト入力部分の大きさ my $cols = 80; my $rows = 20; ############################## my %form = (); my %database = (); my %diffbase = (); my $diff_text = ''; my @diff_added = (); my @diff_deleted = (); my $msgrefA; my $msgrefB; ############################## # 編集不可ページ名一覧 my @uneditable = ( $whatsnew ); ############################## # リンク用の正規表現 # YukiWikiのリンクは2種類ある。 # # (1) WikiName (RecentChangesとかFrontPageのようなもの) # (2) BracketName ([[結城浩]]とか[[トラブルシュート]]のようなもの) # # ※シフトJISの2バイト目には ']' が来うるので、 # 文字']'を1つ多くとるようにしている。 # my $WikiName = '([A-Z][a-z]+([A-Z][a-z]+)+)'; my $BracketName = '\[\[([^>\x09]+?\]?)\]\]'; # アイコン部分のタグ my $IconTag = ''; #<<"EOD"; #[YukiWiki] #EOD require "$jcodelib"; &init_form($kanjicode); if ($testing) { %form = ( # 'mycmd' => 'write', 'mycmd' => 'read', #'mycmd' => 'search', #'mycmd' => 'edit', 'mymsg' => <<"EOD", はじめまして。 これからいろいろ書き込みますね。 LinkPageも見てください。 TestPageはどうでしょうか。 どうぞよろしく。 http://www.hyuki.com/ [[結城浩]] EOD 'mypage' => '<結城浩>', 'myword' => '結', # '3C8C8B8FE98D5F3E' => '', # 'TestPage' => '', ); } &main; exit(0); # メイン sub main { &normalize_form; if ($dbmopen) { if (!dbmopen(%database, $dbname, 0666)) { &print_error("(dbmopen) $dbname が作れません。"); } } else { if (!tie(%database, "YukiWikiDB", $dbname)) { &print_error("(tie error)"); } } # myspecial対応 foreach (keys %form) { if (/^myspecial_(.*)/) { $form{mycmd} = $1; last; } } if ($form{mycmd} eq 'read') { &do_read; } elsif ($form{mycmd} eq 'preview') { &do_preview; } elsif ($form{mycmd} eq 'write') { &do_write; } elsif ($form{mycmd} eq 'edit') { &do_edit; } elsif ($form{mycmd} eq 'reedit') { &do_reedit; } elsif ($form{mycmd} eq 'search') { &do_search; } elsif ($form{mycmd} eq 'list') { &do_list; } elsif ($form{mycmd} eq 'diff') { &do_diff; } else { $form{mypage} = $toppage; &do_read; } if ($dbmopen) { dbmclose(%database); } else { untie(%database); } } # ページの表示 sub do_read { my $page_name = $form{mypage}; my $percent_name = &encode_percent($page_name); &print_header($page_name); print qq|

$IconTag$page_name

\n|; &print_toolbar($page_name); print &convert_html(&get_page($page_name)); &print_footer; } # ページの編集 sub do_edit { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; return; } &editpage(&get_page($form{mypage})); } # ページの再編集 sub do_reedit { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; } else { &editpage($form{mymsg}); } } sub editpage { my $page_msg = shift; my $page_name = $form{mypage}; my $digest = &calc_message_digest($page_msg); &print_header($page_name); print qq|

$IconTag${page_name}の編集

\n|; &print_toolbar($page_name); $page_msg = &escape($page_msg); print <<"EOD";


テキスト整形のルール

通常は入力した文字がそのまま出力されますが、 以下のルールに従ってテキスト整形を行うことができます。

EOD &print_footer; } # ページの検索 sub do_search { if ($form{myword}) { &print_header('検索結果'); print qq|

$IconTag$form{myword}の検索結果

\n|; &print_toolbar(); print qq|\n|; if ($count > 0) { print qq|

$form{myword}を含むページは、上に示す$count ページです。

\n|; } else { print qq|

$form{myword}を含むページは見つかりません。

\n|; } } else { &print_header('単語検索'); print qq|

$IconTag単語検索

\n|; &print_toolbar(); } print <<"EOD";

EOD &print_footer; } # ページの一覧 sub do_list { &print_header('ページ一覧'); print qq|

$IconTag ページ一覧

\n|; &print_toolbar(); print qq|\n|; &print_footer; } # プレビュー sub do_preview { my $page_name = $form{mypage}; my $escapedmsg = &escape($form{mymsg}); &print_header($page_name); print qq|

$IconTag${page_name}のプレビュー

\n|; &print_toolbar($page_name); # local $percent_name = &encode_percent($page_name); print qq|

以下のプレビューを確認して、よければページ下部のボタンで更新してください。

\n|; if ($form{mymsg}) { print qq|
\n|; # print &convert_html($escapedmsg); print &convert_html($form{mymsg}); print qq|
\n|; } else { print qq|

(ページの内容は空です。更新するとこのページは削除されます。)

\n|; } &print_preview_buttons($page_name, $escapedmsg, $form{mydigest}); &print_footer; } sub print_preview_buttons { my ($page_name, $escapedmsg, $digest) = @_; print <<"EOD";

EOD } # 書き込む sub do_write { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; return; } my $page_name = $form{mypage}; # digestを使って、更新の衝突チェック my $original_digest = &calc_message_digest(&get_page($page_name)); if ($form{mydigest} ne $original_digest) { &print_header($page_name); print qq|

$IconTag${page_name}で【更新の衝突】が起きました

\n|; print <<"EOD";

あなたがこのページを編集している間に、 他の人が同じページを更新してしまったようです。

以下に、あなたの編集したテキストがありますので、 あなたの編集内容が失われないように、 いますぐ、メモ帳などにコピー&ペーストしてください。

コピー&ペーストが済んでから、 最新の内容を見て再度編集し直してください。 最新の内容は $form{mypage} で見ることができます。

EOD # &print_toolbar($page_name); &print_preview_buttons($page_name, &escape($form{mymsg}), $form{mydigest}); &print_footer; return; } # diff生成 { &opendiff; my @msg1 = split(/\n/, &get_page($page_name)); my @msg2 = split(/\n/, $form{mymsg}); $msgrefA = \@msg1; $msgrefB = \@msg2; &diff_check; $diffbase{$form{mypage}} = $diff_text; $diff_text = ''; &closediff; } &print_header($page_name); &set_page($page_name, $form{mymsg}); if ($form{mymsg}) { print qq|

$IconTag${page_name}を更新しました

\n|; &print_toolbar($page_name); print &convert_html(&get_page($page_name)); } else { print qq|

$IconTag${page_name}を削除しました

\n|; &print_toolbar($page_name); print qq|

${page_name}を削除しました。

\n|; } &print_footer; # 更新されたのでタッチしておく。 if ($touchfile) { open(FILE, "> $touchfile"); print FILE "\n"; close(FILE); } } # ページの変更点 sub do_diff { if (not &is_editable($form{mypage})) { # 編集不可ページは表示のみ &do_read; return; } &opendiff; &print_header($form{mypage} . 'の変更点'); print qq|

$IconTag $form{mypage}の変更点

\n|; &print_toolbar(); $_ = &escape($diffbase{$form{mypage}}); print <<"EOD";
EOD print qq|
\n|;
    foreach (split(/\n/, $_)) {
        if (/^\+(.*)/) {
            print qq|$1\n|;
        } elsif (/^\-(.*)/) {
            print qq|$1\n|;
        } elsif (/^\=(.*)/) {
            print qq|$1\n|;
        } else {
            print qq|??? $_\n|;
        }
    }
    print qq|
\n|; &print_footer; &closediff; } sub opendiff { if ($dbmopen) { if (!dbmopen(%diffbase, $diffdbname, 0666)) { &print_error("(dbmopen) $diffdbname が作れません。"); } } else { if (!tie(%diffbase, "YukiWikiDB", $diffdbname)) { &print_error("(tie error)"); } } } sub closediff { if ($dbmopen) { dbmclose(%diffbase); } else { untie(%diffbase); } } # フォームからの情報を連想配列 %form に入れる # &init_form('euc'); sub init_form { my ($charcode) = @_; my $query; if ($ENV{REQUEST_METHOD} =~ /^post$/i) { read(STDIN, $query, $ENV{CONTENT_LENGTH}); } else { $query = $ENV{QUERY_STRING}; } my @assocarray = split(/[&;]/, $query); foreach my $assoc (@assocarray) { my ($property, $value) = split(/=/, $assoc); $value =~ tr/+/ /; $value =~ s/%([A-Fa-f0-9][A-Fa-f0-9])/pack("C", hex($1))/eg; &jcode::convert(\$value, $charcode); $form{$property} = $value; } } # エラーページを出力する sub print_error { my ($msg) = @_; &print_header('Error'); print "

$IconTag$msg

"; exit(0); } sub escape { my ($line) = shift; $line =~ s|<|<|g; $line =~ s|>|>|g; $line =~ s|"|"|g; # $line =~ s|\&|&|g; return $line; } sub inline { my ($line) = shift; $line = &escape($line); $line =~ s|'''([^']+?)'''|$1|g; $line =~ s|''([^']+?)''|$1|g; $line =~ s! ( (?:<(?:mailto|http|https|ftp|urn):[\x21-\x7E]*)> #| (?:$WikiName) # LocalLinkLikeThis | (?:$BracketName) # [[日本語リンク]] ) ! &make_link($1) !gex; return $line; } # ページのタイトルからページの内容を得る sub get_page { my $page_name = shift; return $database{$page_name} || $database{'[['.$page_name.']]'}; } # ページの内容を与える # &set_page($title, $txt) sub set_page { # ページを更新する my $title = $_[0]; $database{$title} = $_[1]; # 空ページなら削除する unless ($database{$title}) { delete $database{$title}; } # RecentChangesを更新する my $delim = ' - '; my @pages = split(/\n/, $database{$whatsnew}); my $datestr = &get_current_datestr; unshift(@pages, qq|-$datestr$delim$title|); # 同一ページの更新は最新のもののみにし、 # 存在しないページはスキップする。 my %count; my @newpages; foreach my $line (@pages) { my ($prefix, $title) = split(/$delim/, $line); $count{$title}++; if ($count{$title} == 1 and exists($database{$title})) { push(@newpages, qq|$prefix - $title|); } } # ここで本当に更新 $database{$whatsnew} = join("\n", splice(@newpages, 0, $maxnew)); } # ページのヘッダを出力 sub print_header { my $title = shift; print <<"EOD"; Content-type: text/html; charset=${charset} $title EOD } # ツールバーを出力 sub print_toolbar { my $page_name = shift; my $percent_name = &encode_percent($page_name); my $editlink = ''; if ($page_name ne '' and &is_editable($page_name)) { $editlink = <<"EOD"; 編集 | 差分 | EOD } print <<"EOD";

[ トップ | 一覧 | $editlink 単語検索 | 最近の更新 ]

EOD } # ページのフッタを出力 sub print_footer { print <<"EOD"; EOD print <<'EOH';
YukiWiki 1.6.6 Copyright (C) 2000,2001 by Hiroshi Yuki. ${version} + $modifier. $Revision: 1.13 $ $Date: 2002/04/02 00:28:14 $
EOH } # URLやページの名前からリンクを作る sub make_link { my $name = shift; $name =~ s/^<(.*)>$/$1/; $name =~ s/^\[\[(.*)\]\]$/$1/; if ($name =~ /^(http|https|ftp).*?(\.png|\.jpeg|\.jpg)?$/) { if ($2) { return qq||; } else { return qq|<$name>|; } } elsif ($name =~ /^mailto:(.*)/) { my $address = $1; return qq|<$address>|; } elsif ($name =~ m#^urn:[0-9A-Za-z_:;/.-]+#) { return qq|<$name>|; } else { my $name2 = $name; $name2 =~ tr/\x20/-/; if ($database{$name2}) { my $percent_name = &encode_percent($name2); return qq|$name|; } elsif ($database{'[['.$name2.']]'}) { my $percent_name = &encode_percent('[['.$name2.']]'); return qq|$name|; } else { my $percent_name = &encode_percent($name2); return qq|$name?|; } } } # %xx の形式にエンコードする # これは、 # http://www.hyuki.com/yukiwiki/yukiwiki.cgi?mycmd=read&mypage=%3C%8C%8B%8F%E9%8D_%3E # という形式のために使われる。 # '<結城浩>' → '%3C%8C%8B%8F%E9%8D_%3E' sub encode_percent { my $name = shift; my $encoded = ''; foreach my $ch (split(//, $name)) { if ($ch =~ /[A-Za-z0-9_]/) { $encoded .= $ch; } else { $encoded .= '%' . sprintf("%02X", ord($ch)); } } return $encoded; } # テキスト本体をHTMLに変換する sub convert_html { my ($txt) = shift; my (@txt) = split(/\n/, $txt); foreach (@txt) { chomp; if (/^\*\*\*\*\*(.*)/) { push(@result, splice(@saved), '
' . &inline($1) . '
'); } elsif (/^\*\*\*\*(.*)/) { push(@result, splice(@saved), '
' . &inline($1) . '
'); } elsif (/^\*\*\*(.*)/) { push(@result, splice(@saved), '

' . &inline($1) . '

'); } elsif (/^\*\*(.*)/) { push(@result, splice(@saved), '

' . &inline($1) . '

'); } elsif (/^\*(.*)/) { push(@result, splice(@saved), '

' . &inline($1) . '

'); } elsif (/^----/) { push(@result, splice(@saved), '
'); } elsif (/^(-{1,3})(.*)/) { &back_push('ul', length($1)); push(@result, '
  • ' . &inline($2) . '
  • '); } elsif (/^(={1,3})(.*)/) { &back_push('ol', length($1)); push(@result, '
  • ' . &inline($2) . '
  • '); } elsif (/^:([^:]+):(.*)/) { &back_push('dl', 1); push(@result, '
    ' . &inline($1) . '
    ', '
    ' . &inline($2) . '
    '); } elsif (/^(>{1,3})(.*)/) { &back_push('blockquote', length($1)); push(@result, &inline($2)); } elsif (/^\s*$/) { push(@result, splice(@saved)); unshift(@saved, "

    "); push(@result, "

    "); } elsif (/^(\s+.*)$/) { &back_push('pre', 1); #push(@result, &escape($1)); # Not &inline, but &escape push(@result, &inline($1)); # Not &inline, but &escape } else { push(@result, &inline($_)); } } push(@result, splice(@saved)); return join("\n", @result); } # &back_push($tag, $count) # $tagのタグを$levelレベルまで詰める。 sub back_push { my ($tag, $level) = @_; while (@saved > $level) { push(@result, shift(@saved)); } if ($saved[0] ne "") { push(@result, splice(@saved)); } while (@saved < $level) { unshift(@saved, ""); push(@result, "<$tag>"); } } # 編集可能ページか? sub is_editable {return 1; ## TODO: ... my ($pagename) = @_; foreach (@uneditable) { if ($pagename eq $_) { return 0; } } if (&is_valid_name($pagename)) { return 1; } return 0; } # Validな名前か? sub is_valid_name { my ($pagename) = @_; if ($pagename =~ /^$WikiName$/) { return 1; } elsif ($pagename =~ /^$BracketName$/) { return 1; } else { return 0; } } # 現在時刻の文字列を得る sub get_current_datestr { my (@wdays) = ( "日", "月", "火", "水", "木", "金", "土" ); my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime(time); return sprintf("%4d-%02d-%02d (%s) %02d:%02d:%02d", $year + 1900, $mon + 1, $mday, $wdays[$wday], $hour, $min, $sec); } # URL?SomePageや、 # URL?[[結城浩]]の形式だった場合、(not yet) # 強制的にmycmdをreadにして$formの内容を設定する。 sub normalize_form { foreach my $key (keys %form) { if ($key =~ /^$WikiName$/) { $form{mycmd} = 'read'; $form{mypage} = $1; last; } elsif ($key =~ /^$BracketName$/) { $form{mycmd} = 'read'; $form{mypage} = $1; last; } } } # 変換テストを行なうときのサンプル sub print_sample { my $txt = &convert_html(<<"EOD"); *大見出し1 **小見出し1-1 -項目1 -項目2 -項目3 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落''強調''1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 **小見出し1-2 :用語1:いろいろ書いた解説文1と''強調単語'' 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 :用語2:いろいろ書いた解説文2 :用語3:いろいろ書いた解説文3 ---- *大見出し2 **小見出し2-1 <http://suika.fam.cx/> **小見出し2-2 [[結城浩]] 段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落'''強調'''1段落1段落1段落1段落1段落1段落1段落1 段落1段落1段落1段落'''''強い強調'''''1段落1段落1段落1段落1段落1段落1段落1段落1 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 >段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2段落2 レベル0レベル0レベル0レベル0レベル0レベル0 >レベル1 >レベル1 >レベル1 >>レベル2 >>レベル2 >>レベル2 >>>レベル3 -はろ1 --はろ2 ろろろろ2 ---はろ3 --はろ2 ---はろ3 --はろ2 ---はろ3 >>>レベル3 >>>レベル3 >>>レベル3 EOD print $txt; exit; } sub diff_check { traverse_sequences( $msgrefA, $msgrefB, { MATCH => \&df_match, DISCARD_A => \&df_delete, DISCARD_B => \&df_add, } ); &diff_flush; } sub diff_flush { $diff_text .= join('', map { "-$_\n" } splice(@diff_deleted)); $diff_text .= join('', map { "+$_\n" } splice(@diff_added)); } sub df_match { my ($a, $b) = @_; &diff_flush; $diff_text .= "=$msgrefA->[$a]\n"; } sub df_delete { my ($a, $b) = @_; push(@diff_deleted, $msgrefA->[$a]); } sub df_add { my ($a, $b) = @_; push(@diff_added, $msgrefB->[$b]); } sub calc_message_digest { # You have to use MD5... my $text = shift; my @text = split(//, $text); my $len = length($text); my $checksum = 0; foreach (@text) { $checksum += ord($_); $checksum = ($checksum * 2) % 65536 + (($checksum & 32768) ? 1 : 0); # 16bit rotate } return "$len:$checksum"; } # Definition of YukiWikiDB package YukiWikiDB; my $debug = 1; # Constructor sub new { return shift->TIEHASH(@_); } # tying sub TIEHASH { my ($class, $dbname) = @_; my $self = { dir => $dbname, keys => [], }; if (not -d $self->{dir}) { if (!mkdir($self->{dir}, 0777)) { print "mkdir(" . $self->{dir} . ") fail\n" if ($debug); return undef; } } return bless($self, $class); } # Store sub STORE { my ($self, $key, $val) = @_; my $file = &make_filename($self, $key); if (open(FILE,"> $file")) { binmode(FILE); print FILE $val; close(FILE); return $self->{$key} = $val; } else { print "$file create error."; } } # Fetch sub FETCH { my ($self, $key) = @_; my $file = &make_filename($self, $key); if (open(FILE, $file)) { local $/; $self->{$key} = ; close(FILE); } return $self->{$key}; } # Exists sub EXISTS { my ($self, $key) = @_; my $file = &make_filename($self, $key); return -e($file); } # Delete sub DELETE { my ($self, $key) = @_; my $file = &make_filename($self, $key); unlink $file; return delete $self->{$key}; } sub FIRSTKEY { my ($self) = @_; opendir(DIR, $self->{dir}) or die $self->{dir}; @{$self->{keys}} = grep /\.txt$/, readdir(DIR); foreach my $name (@{$self->{keys}}) { $name =~ s/\.txt$//; $name =~ s/[0-9A-F][0-9A-F]/pack("C", hex($&))/eg; } return shift @{$self->{keys}}; } sub NEXTKEY { my ($self) = @_; return shift @{$self->{keys}}; } sub make_filename { my ($self, $key) = @_; my $enkey = ''; foreach my $ch (split(//, $key)) { $enkey .= sprintf("%02X", ord($ch)); } return $self->{dir} . "/$enkey.txt"; } __END__ =head1 NAME YukiWiki - 自由にページを追加・削除・編集できるWebページ構築CGI Copyright (C) 2000,2001 by Hiroshi Yuki. 結城浩 http://www.hyuki.com/ http://www.hyuki.com/yukiwiki/ =head1 SYNOPSIS http://www.hyuki.com/yukiwiki/yukiwiki.cgi =head1 DESCRIPTION YukiWiki(結城ウィキィ)は参加者が自由にページを追加・削除・編集できる 不思議なWebページ群を作るCGIです。 Webで動作する掲示板とちょっと似ていますが、 Web掲示板が単にメッセージを追加するだけなのに対して、 YukiWikiは、Webページ全体を自由に変更することができます。 YukiWikiは、Cunningham & CunninghamのWikiWikiWebの 仕様を参考にして独自に作られました。 YukiWikiはPerlで書かれたCGIスクリプトとして実現されていますので、 Perlが動作するWebサーバならば比較的容易に設置できます。 あとはdbmopenが使える環境ならば設置できます(Version 1.5.0以降ならdbmopenが使えなくても設置できます)。 YukiWikiはフリーソフトです。 ご自由にお使いください。 =head1 設置方法 =head2 入手 YukiWikiの最新版は、 http://www.hyuki.com/yukiwiki/ から入手できます。 =head2 ファイル一覧 readme.txt ドキュメント yukiwiki.cgi YukiWiki本体 yukiwiki.gif ロゴ(カラー版) yukimono.gif ロゴ(モノクロ版) jcode.pl 漢字コードライブラリ =head2 インストール =over 4 =item 1. アーカイブを解く。 =item 2. yukiwiki.cgiのはじめの方にある設定を確認します。 通常は何もしなくてよいが、 はじめは$touchfileを''にした方がよいでしょう。 =item 3. yukiwiki.cgiとjcode.plを同じところに設置します。 =item 4. サイズ0のyukiwiki.dbというファイルを設置します。 (Perlシステムによってはyukiwiki.pag, yukiwiki.dir) =item 5. yukiwiki.cgiにブラウザからアクセスします。 =back =head2 パーミッション ファイル パーミッション 転送モード yukiwiki.cgi 755 ASCII yukiwiki.gif 644 BINARY yukimono.gif 644 BINARY jcode.pl 644 ASCII $dbmopen = 1; にした場合: yukiwiki.db 666 BINARY (yukiwiki.pag, yukiwiki.dirの場合もあり) $dbmopen = 0; にした場合: (カレントディレクトリを777にしておく) . 777 (転送しない) =head1 データのバックアップ方法 $dbmopen = 1;の場合は、 データはすべてyukiwiki.db(.dir, .pag)に入る。 これをバックアップすればよい。 $dbmopen = 0;の場合は、 yukiwikiというディレクトリができるので、 これ以下をバックアップすればよい。 =head1 新しいページの作り方 =over 4 =item 1. まず、適当なページ(例えばFrontPage)を選び、 ページの下にある「編集」リンクをたどります。 =item 2. するとテキスト入力ができる状態になるので、 そこにNewPageのような単語 (大文字小文字混在している英文字列) を書いて「保存」します。 =item 3. 保存すると、FrontPageのページが書き換わり、 あなたが書いたNewPageという文字列の後ろに ? というリンクが表示されます。 この ? はそのページがまだ存在しないことを示す印です。 =item 4. その ? をクリックすると新しいページNewPageができますので、 あなたの好きな文章をその新しいページに書いて保存します。 =item 5. NewPageページができるとFrontPageの ? は消えて、リンクとなります。 =back =head1 テキスト整形のルール =over 4 =item * 連続した複数行はフィルされて表示されます。 =item * 空行は段落C<<

    >>の区切りとなります。 =item * HTMLのタグは書けません。 =item * B<''ボールド''>のようにシングルクォート二つではさむと、 ボールドC<< >>になります。 =item * B<'''イタリック'''>のようにシングルクォート三つではさむと、 イタリックC<< >>になります。 =item * B<---->のようにマイナス4つがあると、 水平線C<<


    >>になります。 =item * 行をB<*>ではじめると、 大見出しC<<

    >>になります。 =item * 行をB<**>ではじめると、 小見出しC<<

    >>になります。 =item * 行をマイナス-ではじめると、 箇条書きC<<
      >>になります。 マイナスの数が増えるとレベルが下がります(3レベルまで) -項目1 --項目1-1 --項目1-2 -項目2 -項目3 --項目3-1 ---項目3-1-1 ---項目3-1-2 --項目3-2 =item * コロンを使うと、 用語と解説文のリストC<<
      >>が書けます。 :用語1:いろいろ書いた解説文1 :用語2:いろいろ書いた解説文2 :用語3:いろいろ書いた解説文3 =item * リンク =over 4 =item * LinkToSomePageやFrontPageのように、 英単語の最初の一文字を大文字にしたものが 二つ以上連続したものはYukiWikiのページ名となり、 それが文章中に含まれるとリンクになります。 =item * http://www.hyuki.com/ のようなURLは自動的にリンクになります。 =item * 二重の大かっこ[[ ]]でくくった文字列も、 YukiWikiのページ名になります。 大かっこの中にはスペースを含めてはいけません。 日本語も使えます。 =back =item * 行頭がスペースやタブで始まっていると、 それは整形済みの段落C<<
       >>として扱われます。
      プログラムの表示などに使うと便利です。
      
      
      =item *
      
      行を > ではじめると、
      引用文C<< 
      >>が書けます。 >の数が多いとインデントが深くなります(3レベルまで)。 >文章 >文章 >>さらなる引用 >文章 =back =head1 更新履歴 =over 4 =item * 2001年10月20日、Version 1.6.6。 更新の衝突対策。 元ページの簡単なチェックサムを取っておき、 更新前にチェックサムを比較する。 修正個所はdigestという文字列を検索すれば分かる。 本来はMD5などでちゃんとやった方がいいのだけれど。 衝突時に表示されるメッセージなどは「極悪」さんのページを参考にした。 =item * 2001年10月17日、Version 1.6.5。 プレビュー画面で、更新ボタンを押したときに送信される メッセージの内容をinput要素のtype="hidden"を使って埋め込むのをやめる。 代わりに、textarea要素を使う。 再プレビュー用にmyspecial_を導入。でもきれいな対策ではない。 =item * 2001年8月30日、Version 1.6.4。 URLでダイレクトにページ名を指定しても、 $WikiNameと$BracketName以外のページを作れないようにした。 (is_valid_nameとis_editable参照)。 =item * 2001年8月30日、Version 1.6.3。 RecentChangesを編集・再編集不可とした。 編集不可ページは@uneditableにページ名を入れる。 =item * 2001年2月25日、Version 1.6.1, 1.6.2。 差分機能のバグ修正。 do_previewで'>'が扱えないバグを修正 (ユーザからの指摘)。 =item * 2001年2月22日、Version 1.6.0。 差分機能を実装した。 =item * 2001年2月19日、Version 1.5.4。 画像ファイルへのリンクは画像にしてみた。 =item * 2001年2月19日、Version 1.5.3。 RecentChangesの中に削除したページがあるのをやめた。 use strict;で引っかかる部分を少し整理(完全ではない)。 =item * 2001年2月16日、Version 1.5.2。 textareaに表示およびプレビューする前に < や > を < や > に変換した (do_preview, editpage, print_preview_buttons)。 =item * 2000年12月27日、Version 1.5.1。 プレビュー画面を整理した。 =item * 2000年12月22日、Version 1.5.0。 全体的にずいぶん書き直した。 一覧を別途作成するようにした(do_list)。 書き込む前に確認画面を出すようにした(do_preview)。 テキストの書き方を編集画面に入れた(do_edit, do_reedit)。 WhatsNew→RecentChanges、TopPage→FrontPageに変更した。 =item * 2000年12月20日、Version 1.1.0。 tieを利用して、dbmopenが使えない場合でも動作するように修正。 利用者の1人である「極悪」さんから 送っていただいたコードを元にしています。 =item * 2000年9月5日、Version 1.0.2。 → 利用者からの指摘による。感謝。 =item * 2000年8月6日、Version 1.0.1を公開。 C MAGAZINE(ソフトバンクパブリッシング) 2000年10月号連載記事向け公開版。 [[ ]] の最後が「望」のようにシフトJISで 0x5Dになる場合の回避を行なった。 =item * 2000年8月5日、Version 1.0.0を公開。 =item * 2000年7月23日、Version 0.82を公開。 編集時のリンクミス。