第二回『設定ファイルで誰にでも使いやすく』
ソースを読めない知人にスクリプトを配布する時、設定ファイルの書き換えだけで、変
数をカスタマイズ

 まだフリー・ウェアで満足してい
ますか? ”楽ちん”にPerlスクリ
プトを作りましょう。今回は、ソー
スを読めない知人にスクリプトを配
布する時、設定ファイルの書き換え
だけで、変数をカスタマイズできる
テクニックの紹介です。


●めぐみちゃんとアニメ(ゲーム)脚本
 25歳の時、好きな娘がいました。
名前はF川めぐみちゃん、可愛い目
をしているのに、ひねくれていて生
意気で。ある日届いたハガキには、
宮崎県K市に嫁いだと書かれていま
した。お互い、連絡も取らなくなり、
今では、彼女は、子供もいないまま、
離婚してしまい、実家の造り酒屋に
戻ったと聞きます。めぐみちゃん、
アニメ脚本は、やめちゃったのかな
……?
 ……私的なお話を、大変、失礼し
ました(*^.^*)。昔、TV脚本家の
先生について勉強していためぐみち
ゃんに頼まれて、アニメ脚本入力支
援ソフトを作ったことがあります。
今回は、その時のことを思い出して、
Perlで組んで見ました。
 脚本のフォーマットをご存じでし
ょうか? 非常に特殊なインデント
で、細かな直しが頻発する業界のこ
と、直しの度に、インデントをいち
いち、手打ちでやっていたら、とて
も大変です。

 脚本形式のサンプルです。

−−−−−−−−−−−−−
<サンプル1>
「銀河伝説オイレーン」
       第三稿 2001.5.21
           F川めぐみ
○ 宇宙
  円盤の大群が、地球にゆっくり
  近づいてくる。

○ 商店街(平和な日曜日で、通行
 人が大勢いる)
  さくらとイシューがソフトクリ
  ームを食べて歩いている。
さくら「ふがふが……」
  ちゅどーん! と、絨毯爆撃が
  始まる。一瞬にして商店街は戦
  場となる。
イシュー「(腕の無線)大森くん!
 大森くん! 第一次戦闘配備よ!」
−−−−−−−−−−−−−

 脚本を、手動で、ちょっと更新し
ようとすると、インデントのやり直
しが実に煩わしそうです。キーボー
ドの苦手なめぐみちゃんは、これが
大変だったのです。

−−−−−−−−−−−−−
<サンプル2>
N=改行を意味します。
「銀河伝説オイレーン」N
       第三稿 2001.5.21N
           F川めぐみN
○宇宙N
円盤の大群が、地球にゆっくり近づ
いてくる。N
N
○商店街(平和な日曜日で、通行人
が大勢いる)N
さくらとイシューがソフトクリーム
を食べて歩いている。N
さくら「ふがふが……」N
ちゅどーん!と、絨毯爆撃が始まる。
一瞬にして商店街は戦場となる。N
イシュー「(腕の無線)大森くん!
大森くん!第一次戦闘配備よ!」N
−−−−−−−−−−−−−−

 こんな、ベタ打ちテキストなら、
更新は楽です。これが、一発で、自
動的に変換されたら……。でも、簡
単そうですが、よーく見て下さい。
このインデントは、ちょっとやそっ
とでは、自動化できません。
 脚本には、シーン起こし、ト書き、
台詞、改行のみの、四つのブロック
があり、インデント必要なしの、特
殊行もあります。
 例えば、台詞などは、開始行はイ
ンデントされず、二行目から全角1
マス、空白が開けられます。ト書き
は、2マス。納品先によっては、3
マスの場合などもあるそうです。同
時に、ぶら下げ禁則位はサポートし
ていないと使い物になりません。ブ
ロックの判定は、どうやりましょう?
 ゲーム台本も、こうした形式を取
る場面もあるそうですから、アニメ・
ゲーム業界に興味がある人は、今回
のスクリプトは、必見です。そうで
ない人も、プログラム的に、どうや
って実現するか、興味を持ちません
か? しかも、Perlを使えば、あっ
けない程、簡単に書けるのです。

●スクリプトを他人に配る
 「自分だけが使える動けばいいス
クリプト」。この連載は、それを目
指して始められました。でも、小さ
なスクリプトといえども、せっかく、
プログラムを自作できるのです。例
えば、「女の子に配って尊敬された
い」って思いませんか? もちろん、
必要としていない女の子にあげても、
「やっぱり辻クンて……」と、オタ
ク扱いされてしまうだけですが(^ ^;)。
 今回のように、大多数の悩みでは
ない、わりと特殊な場面にこそ、自
作スクリプトの価値があります。
 今回は、スクリプトを触れないユ
ーザーも、設定ファイルを更新する
だけで、変数のカスタマイズができ
るようにしました。

●プログラム
 色々、悩んだ末、リスト1のよう
になりました。こんな短いスクリプ
トで、禁則まで処理しているのは、
テキスト処理に優れているPerlの実
力発揮と言えるでしょう。
 使用方法は、設定ファイルで指定
したベタ打ち脚本と同じディレクト
リにスクリプトと設定ファイルをコ
ピーした上で、スクリプトを実行す
るだけです。
 少し悩んだブロック判定は、スク
リプトに注釈したような条件で判定
するようにしました。元ファイルを
ベタ打ちする際、なるべく違和感な
く打てるようにする為、ト書きの判
定に幅をもたせ、ユーザーに自由度
を与えています。
 &mvichimoji2は、$_の左辺一文字
を$tsugimojiに入れ、$_の左辺文字
をカットします。
 禁則は、"?!"、"。」"等は、
二文字までぶら下げ、"」"や")"等は、
一文字のみぶら下げられます。尚、
このスクリプトでは、行末の"("を
次行に送る、送り出し禁則は行われ
ません。
 length ($_)は、改行も一文字に
カウントすることに注意して下さい。
また、untilループで、元ファイルの
一行を一文字一文字処理して、複数
行に分割しているアルゴリズムも注
目してみて下さい。
 台詞ブロックの一行目、二行目で
インデントが異なる部分の処理は、
フラグ変数$kakidashiにより、判定
しています。
 尚、http://www.geocities.co.jp
/SiliconValley-Cupertino/6103/に、
この連載の補足情報のHPを作りま
したので、参照して下さい。スクリ
プトなども、こちらから直接ゲット
できます。

●<今月のワンポイント>
●設定ファイルの読み込み
 今月のワンポイントです。

−−−−−−−−−−−−−
#daihon-cfg.txt
#daihon.plの定義ファイル
  ↓入力ファイル
$daihon = "daihon.txt";
  ↓出力ファイル
$outtxt = "daihon2.txt";
  ↓一行の文字数(全角換算)
$oneline = 24;(普通40か60)
  ↓シーン起こし一行目冒頭文字
$okoshi1 = "○□";
  ↓シーン起こし二行目冒頭文字
$okoshi2 = "□";
  ↓台詞ブロック一行目冒頭文字
$serifu1 = "";
  ↓台詞ブロック二行目冒頭文字
$serifu2 = "□";
  ↓ト書き一行目冒頭の文字
$togaki1 = "□□";
  ↓ト書き二行目冒頭の文字
$togaki2 = "□□";
#注。派閥などにより(?)、
#togaki1と2は、"□□□"、
#okoshi2は、"□□"の場合もある
#ようです。
−−−−−−−−−−−−−

※↑上記、紙面で表現するため、全
角空白を□で表してあります。入力
する際は、注意。

 上記を"daihon-cfg.txt"のファイ
ル名で、スクリプトと一緒に保存し
て下さい。これを更新することによ
り、一行を何文字で改行するか、シ
ーン起こし、ト書き、台詞の各ブロ
ックのインデント文字を簡単に変更
できるようにしました。これなら、
スクリプトを読めないユーザーにも、
設定ファイルだけをカスタマイズす
れば、その時々に合ったインデント
を自由に設定することができます。
 設定ファイルの読み込みは、サブ
ルーチン&cfgreadにより行っていま
す。ここでは、

|$daihon = "daihon.txt";
|$outtxt = "daihon2.txt";
|$oneline = 40;
|$okoshi1 = "○ ";

 などのように、最初に、デフォル
ト値を与えています。これは、万一、
カレントディレクトリにdaihon-cfg
.txtが見当たらない場合、デフォル
ト値で強制的に処理を行うためです。
設定ファイルが見当たらない場合、
処理を中止しても良いのですが、そ
れより、デフォルト動作を強行した
方が、ユーザーが誤った使用をした
場合、原因の特定にもつながり、良
いことが多いと思います。
 次の、

|if (/^\$oneline *=/){
|  $oneline = $_;
|$oneline =~ s/^.*= *(.*)\;.*$/$1/;
|}

 は、設定ファイルの行に、"$onel
ine ="と言う文字が含まれていれば、
「= "」〜「";」までの間の文字を変
数$onelineに入れる処理をしていま
す。
 このような手法で、知人に配布す
るスクリプトは、設定ファイルを別
ファイルに分けると、スクリプト使
用の敷居が低くなるでしょう。
 今回のスクリプトと定義ファイル
を参考にすれば、脚本以外にも、例
えば、学会論文など、特殊インデン
トが必要なテキストの入力支援スク
リプトも作れるでしょう。応用して
見てください。
 追伸。めぐみちゃんは、部外者の
私が、二次会を断って帰ろうとした
ら、腕を引いて次の店に引きずり込
んでくれた女の子なんです。このス
クリプトをめぐみちゃんの思い出に
捧げます。


#daihon.pl
#ベタ打ちテキストを脚本の形式に置換(自動インデント)する
#jperl動作用スクリプト
#s-jis保存
#cgiではなく、ダブル・クリックにより実行、または、MS-DOS窓より、実行。

&cfgread;#変数初期値
open (out, "> $outtxt");open (infile, "< $daihon");#ループ
while( <infile> ) { $sumi = 0;$nigyome = "";
  $_ =~ s/  / /g;$_ =~ s/ / /g;
  #↑半角空白二文字は、全角1文字に直す。半角空白が奇数の場合、最後の
  #半角空白1ケは、全角空白に置換。

  $_ =~ s/([!?!?])([^ !?!?」』。、))>>])/$1 $2/g;
  #↑"?"+" "処理。通常、"?"の後には、" "が入る。但し、
  #"?」"等のように、"」"等とセットの場合は、例外となる。

  #行判定(台詞・ト書き・シーン起こし)
  ( ( $_ =~ /^([^ ]).*」$/ ) && ($sumi == 0) ) && &serifu;
  ( ( $_ =~ /^○.*$/ ) && ($sumi == 0) ) && &okoshi;
  ( ( $_ =~ /^([^ ]).*([^」])$/ ) && ($sumi == 0) ) && &togaki;
  ( ( $_ =~ /^  [^ ].*$/ ) && ($sumi == 0) ) && &togaki;
  ( ( $_ =~ /^ [^ ].*$/ ) && ($sumi == 0) ) && &togaki;
  #" "以外で始まり、"」"で終わる→台詞
  #"○"で始まる→シーン起こし
  #" "以外で始まり、"」"以外で終わる→ト書き
  #"  "か" "で始まる→ト書き("   "で始まる場合は含めない)
  #"   "で始まる行は、どれにも属さず、二行目以降は、そのまま改行される。
  
  $kakidashi = 1; $newgyo = ""; &print ; }
close (out); close (infile);

#サブルーチン
sub cfgread {
  $daihon = "daihon.txt"; $outtxt = "daihon2.txt";$oneline = 40;
  $okoshi1 = "○ "; $serifu1 = ""; $togaki1 = "  ";
  $okoshi2 = "  "; $serifu2 = " "; $togaki2 = "  ";
  # ↑daihon-cfg.txtが存在しない場合のデフォルト値
  open (infile, "< daihon-cfg.txt");
  while( <infile> ) {
    if (/^\$oneline *=/){ $oneline = $_; $oneline =~ s/^.*= *(.*)\;.*$/$1/; }
    if (/^\$daihon *=/){ $daihon = $_; $daihon =~ s/^.*= *"(.*)"\;.*$/$1/; }
    if (/^\$outtxt *=/){ $outtxt = $_; $outtxt =~ s/^.*= *"(.*)"\;.*$/$1/; }
    if (/^\$okoshi1 *=/){ $okoshi1 = $_; $okoshi1 =~ s/^.*= *"(.*)"\;.*$/$1/;}
    if (/^\$serifu1 *=/){ $serifu1 = $_; $serifu1 =~ s/^.*= *"(.*)"\;.*$/$1/;}
    if (/^\$togaki1 *=/){ $togaki1 = $_; $togaki1 =~ s/^.*= *"(.*)"\;.*$/$1/;}
    if (/^\$okoshi2 *=/){ $okoshi2 = $_; $okoshi2 =~ s/^.*= *"(.*)"\;.*$/$1/;}
    if (/^\$serifu2 *=/){ $serifu2 = $_; $serifu2 =~ s/^.*= *"(.*)"\;.*$/$1/;}
    if (/^\$togaki2 *=/){ $togaki2 = $_; $togaki2 =~ s/^.*= *"(.*)"\;.*$/$1/;}
  }
  close (infile); chomp $okoshi1; chomp $serifu1; chomp $togaki1;
  chomp $okoshi2; chomp $serifu2; chomp $togaki2;
}

sub okoshi { $nigyome = $okoshi2; $_ =~ s/^○ *(.*)$/$okoshi1$1/;$sumi = 1; } 
  #↑○以降に、 が続く場合は、無視される。
sub serifu { $nigyome = $serifu2; $_ =~ s/^(.*)$/$serifu1$1/; $sumi = 1; }
sub togaki { $nigyome = $togaki2; $_ =~ s/^ *(.*)$/$togaki1$1/;$sumi = 1; }
sub print {
  until ( length ($_) < $oneline ) { &ichigyo;print out $newgyo , "\n"; }
  #↑$_が、規定文字数($oneline)未満になるまで、&ichigyoを繰返しファイル出力

  $_ =~ s/[  ][  ]*$//;
  ( ( length ($_) >= 1 ) && ( $kakidashi == 1 )) &&  print out $_;
  #↑ブロックが一行だけだった場合の処理。length ($_)=1は、改行のみの行。
  #(length ($_) > 1)に条件を変えると、改行のみの行が削除される。

  ( ( length ($_) > 1 ) && ( $kakidashi == 0 )) && print out $nigyome , $_;
  #↑ブロックが二行以上だった場合、上のuntilループで余った$_が処理される。
  #length ($_)=1の場合、残りの処理文字が「改行」だけである。既に直前の処理
  #で改行されているので、この場合、この改行コードは、棄てられる。
  #length ($_) >= 1 にすると、タイミングにより、無駄な改行が入る場合がある。
}

#↓$_から、$newgyoに、文字列を移動して行き、一行が規定文字数($oneline)に
#なった場合、禁則処理後に、一行ファイル出力される。一行目の処理と二行目
#以降の処理は、フラグ($kakidashi)により、判定され、別処理が施される。
sub ichigyo {
  if ( $kakidashi == 1 ) { $newgyo = ""; $kakidashi = 0; }
  else { $newgyo = $nigyome; }
  until ( ( length ($_) <= 1 ) || ( length ($newgyo) >= $oneline ) ) {
    &mvichimoji2; }
  &kinsoku; $newgyo =~ s/[  ][  ]*$//; }

#↓二種類のぶら下げ禁則を設けた。"?!"、"。」"等は、二文字までぶら下げ
#される。"」"や")"等は、一文字のみ禁則される。
#尚、"("等の送り出し(次の行頭に送ってしまう)は処理していない。
sub kinsoku { &tsugi;
  if ( $tsugimoji =~ /、|。|?|!|\?|\!/ ) {
    &mvichimoji1; &tsugi;
    $tsugimoji =~ / |?|!|\?|\!|」|』|>|\>|)|\)/ && &mvichimoji1; return; }  if ( $tsugimoji =~ / |」|』|>|\>|)|\)|・/ ) { &mvichimoji1; } }

sub mvichimoji2 { &tsugi; &mvichimoji1; }
sub mvichimoji1 { $newgyo = $newgyo . $tsugimoji; $_ =~ s/^.(.*)$/$1/; }
sub tsugi { $tsugimoji = $_; $tsugimoji =~ s/^(.).*$/$1/; chomp $tsugimoji; }
#↑chompで、$tsugimojiに付与されてしまう、末尾の改行をカットします