| 1 | # ----------------------------------------------------------------------------- |
|---|
| 2 | # $Id$ |
|---|
| 3 | # ----------------------------------------------------------------------------- |
|---|
| 4 | package Configuration::Block; |
|---|
| 5 | use strict; |
|---|
| 6 | use warnings; |
|---|
| 7 | use vars qw($AUTOLOAD); |
|---|
| 8 | use UNIVERSAL; |
|---|
| 9 | use Tiarra::Encoding; |
|---|
| 10 | use Tiarra::DefineEnumMixin qw(BLOCK_NAME TABLE); |
|---|
| 11 | use Tiarra::Utils; |
|---|
| 12 | # 値を取得するにはgetメソッドを用いる他、エントリ名をそのままメソッドとして呼ぶ事も出来ます。 |
|---|
| 13 | # |
|---|
| 14 | # $block->hoge; |
|---|
| 15 | # これでパラメータhogeの値を返す。hogeが未定義ならundef値を返す。 |
|---|
| 16 | # hogeの値が一つだけだったらそれを返すが、複数の値が存在したらその先頭の値だけを返す。 |
|---|
| 17 | # 値もブロックだったら、そのブロックを返す。 |
|---|
| 18 | # |
|---|
| 19 | # $block->hoge('all'); |
|---|
| 20 | # パラメータhogeの全ての値を配列で返す。hogeが未定義なら空の配列を返す。 |
|---|
| 21 | # 値が一つしか無ければ値が一つの配列を返す。 |
|---|
| 22 | # |
|---|
| 23 | # $block->foo_bar; |
|---|
| 24 | # $block->foo_bar('all'); |
|---|
| 25 | # パラメータ"foo-bar"の値を返す。"foo_bar"ではない! |
|---|
| 26 | # |
|---|
| 27 | # $block->foo('random'); |
|---|
| 28 | # パラメータfooに複数の定義があれば、そのうちの一つをランダムに返す。 |
|---|
| 29 | # 一つも無ければundefを返す。 |
|---|
| 30 | # |
|---|
| 31 | # $block->foo_bar('block'); |
|---|
| 32 | # $block->get('foo-bar', 'block'); |
|---|
| 33 | # パラメータ"foo-bar"の値が未定義である場合、undef値の代わりに |
|---|
| 34 | # 空のConfiguration::Blockを返す。 |
|---|
| 35 | # 定義されている場合、その値がブロックであればそれを返すが、 |
|---|
| 36 | # そうでなければ "foo-bar: その値" の要素を持ったブロックを生成し、それを返す。 |
|---|
| 37 | # |
|---|
| 38 | # $block->get('foo_bar'); |
|---|
| 39 | # $block->get('foo_bar','all'); |
|---|
| 40 | # パラメータ"foo_bar"の値を返す。 |
|---|
| 41 | # |
|---|
| 42 | # 以上の事から、Configuration::Blockはnew,block_name,table,set,get, |
|---|
| 43 | # reinterpret-encoding,AUTOLOADといった属性はget()でしか読めない。 |
|---|
| 44 | # また、属性名にアンダースコアを持つ属性もget()でしか読めない。 |
|---|
| 45 | |
|---|
| 46 | sub new { |
|---|
| 47 | my ($class,$block_name) = @_; |
|---|
| 48 | my $obj = bless [] => $class; |
|---|
| 49 | $obj->[BLOCK_NAME] = $block_name; |
|---|
| 50 | $obj->[TABLE] = {}; # ラベル -> 値(配列リファもしくはスカラー) |
|---|
| 51 | $obj; |
|---|
| 52 | } |
|---|
| 53 | |
|---|
| 54 | Tiarra::Utils->define_array_attr_accessor(0, qw(block_name table)); |
|---|
| 55 | |
|---|
| 56 | sub equals { |
|---|
| 57 | # 二つのConfiguration::Blockが完全に等価なら1を返す。 |
|---|
| 58 | my ($this,$that) = @_; |
|---|
| 59 | # ブロック名 |
|---|
| 60 | if ($this->[BLOCK_NAME] ne $that->[BLOCK_NAME]) { |
|---|
| 61 | return undef; |
|---|
| 62 | } |
|---|
| 63 | # キーの数 |
|---|
| 64 | my @this_keys = sort keys %{$this->[TABLE]}; |
|---|
| 65 | my @that_keys = sort keys %{$that->[TABLE]}; |
|---|
| 66 | if (@this_keys != @that_keys) { |
|---|
| 67 | return undef; |
|---|
| 68 | } |
|---|
| 69 | my $walk; |
|---|
| 70 | $walk = sub { |
|---|
| 71 | my ($this_value, $that_value) = @_; |
|---|
| 72 | |
|---|
| 73 | # 値の型 |
|---|
| 74 | if (ref($this_value) ne ref($that_value)) { |
|---|
| 75 | return undef; |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | # 値 |
|---|
| 79 | if (ref($this_value) eq 'ARRAY') { |
|---|
| 80 | # 配列なので要素数と全要素を比較。 |
|---|
| 81 | if (@$this_value != @$that_value) { |
|---|
| 82 | return undef; |
|---|
| 83 | } |
|---|
| 84 | my $valsize = @$this_value; |
|---|
| 85 | for (my $j = 0; $j < $valsize; $j++) { |
|---|
| 86 | if (!$walk->($this_value->[$j], $that_value->[$j])) { |
|---|
| 87 | return undef; |
|---|
| 88 | } |
|---|
| 89 | } |
|---|
| 90 | } |
|---|
| 91 | elsif (UNIVERSAL::isa($this_value,'Configuration::Block')) { |
|---|
| 92 | # ブロックなので再帰的に比較。 |
|---|
| 93 | if (!$this_value->equals($that_value)) { |
|---|
| 94 | return undef; |
|---|
| 95 | } |
|---|
| 96 | } |
|---|
| 97 | else { |
|---|
| 98 | if ($this_value ne $that_value) { |
|---|
| 99 | return undef; |
|---|
| 100 | } |
|---|
| 101 | } |
|---|
| 102 | 1; |
|---|
| 103 | }; |
|---|
| 104 | |
|---|
| 105 | # 各要素 |
|---|
| 106 | my $size = @this_keys; |
|---|
| 107 | for (my $i = 0; $i < $size; $i++) { |
|---|
| 108 | # キー |
|---|
| 109 | if ($this_keys[$i] ne $that_keys[$i]) { |
|---|
| 110 | return undef; |
|---|
| 111 | } |
|---|
| 112 | # 値の型 |
|---|
| 113 | my $this_value = $this->[TABLE]->{$this_keys[$i]}; |
|---|
| 114 | my $that_value = $that->[TABLE]->{$that_keys[$i]}; |
|---|
| 115 | if (!$walk->($this_value, $that_value)) { |
|---|
| 116 | return undef; |
|---|
| 117 | } |
|---|
| 118 | } |
|---|
| 119 | return 1; |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | sub _eval_code { |
|---|
| 123 | # 渡された文字列中の、全ての%CODE{ ... }EDOC%を評価して返す。 |
|---|
| 124 | my ($this,$str) = @_; |
|---|
| 125 | |
|---|
| 126 | if (!defined($str) || ref($str)) { |
|---|
| 127 | return $str; # 文字列でなかったらそのまま返す。 |
|---|
| 128 | } |
|---|
| 129 | |
|---|
| 130 | my $eval = sub { |
|---|
| 131 | my $script = shift; |
|---|
| 132 | no strict; no warnings; |
|---|
| 133 | my $result = eval "package Configuration::Implanted; $script"; |
|---|
| 134 | use warnings; use strict; |
|---|
| 135 | if ($@) { |
|---|
| 136 | die "\%CODE{ }EDOC\% interpretation error.\n". |
|---|
| 137 | "block: ".$this->[BLOCK_NAME]."\n". |
|---|
| 138 | "original: $str\n". |
|---|
| 139 | "$@\n"; |
|---|
| 140 | } |
|---|
| 141 | $result; |
|---|
| 142 | }; |
|---|
| 143 | (my $evaluated = $str) =~ s/\%CODE{(.*?)}EDOC\%/$eval->($1)/eg; |
|---|
| 144 | $evaluated; |
|---|
| 145 | } |
|---|
| 146 | |
|---|
| 147 | sub _coerce_to_block { |
|---|
| 148 | my ($this, $key, $value) = @_; |
|---|
| 149 | |
|---|
| 150 | if (ref($value) and UNIVERSAL::isa($value, 'Configuration::Block')) { |
|---|
| 151 | return $value; |
|---|
| 152 | } |
|---|
| 153 | else { |
|---|
| 154 | my $tmp_block = Configuration::Block->new($key); |
|---|
| 155 | $tmp_block->set($key, $value); |
|---|
| 156 | return $tmp_block; |
|---|
| 157 | } |
|---|
| 158 | } |
|---|
| 159 | |
|---|
| 160 | sub get { |
|---|
| 161 | my $this = shift; |
|---|
| 162 | my $key = shift; |
|---|
| 163 | my %option; |
|---|
| 164 | if (@_) { |
|---|
| 165 | @option{@_} = (1) x @_; |
|---|
| 166 | } |
|---|
| 167 | |
|---|
| 168 | if ($option{all}) { |
|---|
| 169 | # list context |
|---|
| 170 | my @values = $this->_get($key, %option); |
|---|
| 171 | return map { |
|---|
| 172 | $option{block} ? $this->_coerce_to_block($key, $_) : $_; |
|---|
| 173 | } map { |
|---|
| 174 | $this->_eval_code($_); |
|---|
| 175 | } @values; |
|---|
| 176 | } else { |
|---|
| 177 | # scalar context |
|---|
| 178 | my $value = $this->_eval_code($this->_get($key, %option)); |
|---|
| 179 | if ($option{block}) { |
|---|
| 180 | $value = $this->_coerce_to_block($key, $value); |
|---|
| 181 | } |
|---|
| 182 | return $value; |
|---|
| 183 | } |
|---|
| 184 | } |
|---|
| 185 | |
|---|
| 186 | sub _get { |
|---|
| 187 | my ($this, $key, %option) = @_; |
|---|
| 188 | |
|---|
| 189 | unless (exists $this->[TABLE]->{$key}) { |
|---|
| 190 | # そのような値は定義されていない。 |
|---|
| 191 | if ($option{all}) { |
|---|
| 192 | return (); |
|---|
| 193 | } |
|---|
| 194 | elsif ($option{block}) { |
|---|
| 195 | return Configuration::Block->new($key); |
|---|
| 196 | } |
|---|
| 197 | else { |
|---|
| 198 | return undef; |
|---|
| 199 | } |
|---|
| 200 | } |
|---|
| 201 | |
|---|
| 202 | my $value = $this->[TABLE]->{$key}; |
|---|
| 203 | if (ref($value) ne 'ARRAY') { |
|---|
| 204 | # 配列のリファレンスでなければそのまま返す。 |
|---|
| 205 | return $value; |
|---|
| 206 | } elsif ($option{all}) { |
|---|
| 207 | # 逆参照して返す。 |
|---|
| 208 | return @{$value}; |
|---|
| 209 | } |
|---|
| 210 | elsif ($option{random}) { |
|---|
| 211 | # ランダムに選んで返す |
|---|
| 212 | return $value->[int(rand(0xffffffff)) % @$value]; |
|---|
| 213 | } |
|---|
| 214 | else { |
|---|
| 215 | # 先頭の値を返す。 |
|---|
| 216 | return $value->[0]; |
|---|
| 217 | } |
|---|
| 218 | } |
|---|
| 219 | |
|---|
| 220 | sub set { |
|---|
| 221 | # 古い値があれば上書きする。 |
|---|
| 222 | my ($this,$key,$value) = @_; |
|---|
| 223 | $this->[TABLE]->{$key} = $value; |
|---|
| 224 | $this; |
|---|
| 225 | } |
|---|
| 226 | |
|---|
| 227 | sub add { |
|---|
| 228 | # 古い値があればそれに追加する。 |
|---|
| 229 | my ($this,$key,$value) = @_; |
|---|
| 230 | if (defined $this->[TABLE]->{$key}) { |
|---|
| 231 | # 定義済み。 |
|---|
| 232 | if (ref($this->[TABLE]->{$key}) eq 'ARRAY') { |
|---|
| 233 | # 既に複数の値を持っているのでただ追加する。 |
|---|
| 234 | push @{$this->[TABLE]->{$key}},$value; |
|---|
| 235 | } |
|---|
| 236 | else { |
|---|
| 237 | # 配列に変更する。 |
|---|
| 238 | $this->[TABLE]->{$key} = [$this->[TABLE]->{$key},$value]; |
|---|
| 239 | } |
|---|
| 240 | } |
|---|
| 241 | else { |
|---|
| 242 | # 定義済みでない。 |
|---|
| 243 | $this->[TABLE]->{$key} = $value; |
|---|
| 244 | } |
|---|
| 245 | } |
|---|
| 246 | |
|---|
| 247 | sub reinterpret_encoding { |
|---|
| 248 | # このブロックの全ての要素を指定された文字エンコーディングで再解釈する。 |
|---|
| 249 | # 再解釈後はUTF-8になる。 |
|---|
| 250 | my ($this,$encoding) = @_; |
|---|
| 251 | |
|---|
| 252 | my $unicode = Tiarra::Encoding->new; |
|---|
| 253 | my $walk; |
|---|
| 254 | $walk = sub { |
|---|
| 255 | my $value = shift; |
|---|
| 256 | |
|---|
| 257 | if (ref($value) eq 'ARRAY') { |
|---|
| 258 | # 配列なので中身を全て変換。 |
|---|
| 259 | my @newarray = map { |
|---|
| 260 | $walk->($_); |
|---|
| 261 | } @$value; |
|---|
| 262 | \@newarray; |
|---|
| 263 | } |
|---|
| 264 | elsif (UNIVERSAL::isa($value, 'Configuration::Block')) { |
|---|
| 265 | # ブロックなので再帰的にコード変換。 |
|---|
| 266 | $value->reinterpret_encoding($encoding); |
|---|
| 267 | } |
|---|
| 268 | else { |
|---|
| 269 | $unicode->set($value, $encoding)->utf8 |
|---|
| 270 | } |
|---|
| 271 | }; |
|---|
| 272 | |
|---|
| 273 | my $newtable = {}; |
|---|
| 274 | while (my ($key,$value) = each %{$this->[TABLE]}) { |
|---|
| 275 | my $newkey = $unicode->set($key,$encoding)->utf8; |
|---|
| 276 | $newtable->{$newkey} = $walk->($value);; |
|---|
| 277 | } |
|---|
| 278 | |
|---|
| 279 | $this->[TABLE] = $newtable; |
|---|
| 280 | $this; |
|---|
| 281 | } |
|---|
| 282 | |
|---|
| 283 | sub AUTOLOAD { |
|---|
| 284 | my ($this,@options) = @_; |
|---|
| 285 | |
|---|
| 286 | if ($AUTOLOAD =~ /::DESTROY$/) { |
|---|
| 287 | # DESTROYは伝達させない。 |
|---|
| 288 | return; |
|---|
| 289 | } |
|---|
| 290 | |
|---|
| 291 | (my $key = $AUTOLOAD) =~ s/.+?:://g; |
|---|
| 292 | $key =~ s/_/-/g; |
|---|
| 293 | return $this->get($key,@options); |
|---|
| 294 | } |
|---|
| 295 | |
|---|
| 296 | 1; |
|---|