root/lang/perl/tiarra/trunk/main/Configuration/Block.pm @ 14151

Revision 14151, 7.6 kB (checked in by topia, 5 years ago)

support multiple nested block.
* add tiny test for Configuration::Parser.
* support multiple nested block.

  • Property svn:mime-type set to text/x-perl; charset=UTF-8
  • Property svn:eol-style set to LF
  • Property svn:keywords set to Id URL Date Rev Author
Line 
1# -----------------------------------------------------------------------------
2# $Id$
3# -----------------------------------------------------------------------------
4package Configuration::Block;
5use strict;
6use warnings;
7use vars qw($AUTOLOAD);
8use UNIVERSAL;
9use Tiarra::Encoding;
10use Tiarra::DefineEnumMixin qw(BLOCK_NAME TABLE);
11use 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
46sub new {
47    my ($class,$block_name) = @_;
48    my $obj = bless [] => $class;
49    $obj->[BLOCK_NAME] = $block_name;
50    $obj->[TABLE]      = {}; # ラベル -> 値(配列リファもしくはスカラー)
51    $obj;
52}
53
54Tiarra::Utils->define_array_attr_accessor(0, qw(block_name table));
55
56sub 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
122sub _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
147sub _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
160sub 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
186sub _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
220sub set {
221    # 古い値があれば上書きする。
222    my ($this,$key,$value) = @_;
223    $this->[TABLE]->{$key} = $value;
224    $this;
225}
226
227sub 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
247sub 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
283sub 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
2961;
Note: See TracBrowser for help on using the browser.