File Coverage

blib/lib/Data/Sofu/Map.pm
Criterion Covered Total %
statement 112 154 72.7
branch 20 38 52.6
condition 2 9 22.2
subroutine 22 28 78.5
pod 25 25 100.0
total 181 254 71.2


line stmt bran cond sub pod time code
1             ###############################################################################
2             #Map.pm
3             #Last Change: 2006-11-01
4             #Copyright (c) 2006 Marc-Seabstian "Maluku" Lucksch
5             #Version 0.28
6             ####################
7             #This file is part of the sofu.pm project, a parser library for an all-purpose
8             #ASCII file format. More information can be found on the project web site
9             #at http://sofu.sourceforge.net/ .
10             #
11             #sofu.pm is published under the terms of the MIT license, which basically means
12             #"Do with it whatever you want". For more information, see the license.txt
13             #file that should be enclosed with libsofu distributions. A copy of the license
14             #is (at the time of this writing) also available at
15             #http://www.opensource.org/licenses/mit-license.php .
16             ###############################################################################
17             =head1 NAME
18              
19             Data::Sofu::Map - A Sofu Map
20              
21             =head1 DESCRIPTION
22              
23             Provides a interface similar to the original SofuD (sofu.sf.net)
24              
25             =head1 Synopsis
26              
27             require Data::Sofu::Map;
28             my $map = Data::Sofu::Map->new();
29             $map->setAttribute("foo","bar");
30             print $map->value("foo")->toString();
31             $tree = Data::Sofu::loadfile("1.sofu");
32             $tree->opApply(sub {print $_[0],"\n"}); #Prints all keys
33              
34             =head1 SYNTAX
35              
36             This Module is pure OO, exports nothing
37              
38             =cut
39              
40             package Data::Sofu::Map;
41              
42 3     3   18 use strict;
  3         8  
  3         123  
43 3     3   15 use warnings;
  3         7  
  3         140  
44             require Data::Sofu::Object;
45             require Data::Sofu;
46 3     3   15 use Carp;
  3         6  
  3         5981  
47             our $VERSION="0.29";
48             our @ISA = qw/Data::Sofu::Object/;
49              
50             =head1 METHODS
51              
52             Also look at C for methods, cause Map inherits from it
53              
54             =head2 new([DATA])
55             Creates a new C and returns it
56              
57             Converts DATA to appropriate Objects if DATA is given. DATA has to be a Hash or a hashlike structure.
58              
59             $env = Data::Sofu::Map->new(%ENV);
60              
61             =cut
62              
63              
64             sub new {
65 148     148 1 395 my $self={};
66 148         409 bless $self,shift;
67 148         389 $self->{Map}={};
68 148         334 $self->{Order}=[];
69 148 100       441 if (@_) {
70 12         37 $self->set(@_);
71             }
72 148         585 return $self;
73             }
74              
75             =head2 set(DATA)
76              
77             Sets the contents of the Map to match a Hash.
78              
79             $map->set(%ENV);
80              
81             =cut
82             #use Data::Dumper;
83             sub set {
84 20     20 1 31 my $self=shift;
85 20         24 local $_;
86 20         24 my $temp=shift;
87 20         22 my $order=shift;
88 20         65 foreach (values %$temp) {
89 45         143 $_=Data::Sofu::Object->new($_);
90             }
91 20         38 $self->{Order}=$order;
92 20         48 $self->{Map}=$temp;
93             #print (Data::Dumper->Dump([$temp]));
94             }
95              
96             =head2 object(KEY)
97              
98             Return an attribute identified by KEY of this Map.
99              
100             $o = $env->object("PATH");
101             if ($o->isList()) {
102             ...
103             }
104             elsif ($o->isValue()) {
105             ...
106              
107             Note: Changing the returned Object will change the Map as well. (OO 101)
108              
109             =cut
110              
111             sub object {
112 348     348 1 1915 my $self=shift;
113 348         464 my $k=shift;
114 348 50       1850 if (exists $self->{Map}->{$k}) {
115 348         2257 return $self->{Map}->{$k};
116             }
117 0         0 die "Requested object $k doesn't exists in this Map";
118             }
119              
120             =head2 remAttribute(KEY)
121              
122             Deletes an Attribute from this Map.
123              
124             $env->remAttribute("OSTYPE");
125              
126             =cut
127              
128             sub remAttribute {
129 0     0 1 0 my $self=shift;
130 0         0 my $k=shift;
131 0         0 local $_;
132             #@{$self->{Order}} = grep {$_ ne $k} @{$self->{Order}}; #Not needed, orderedKeys does that for all keys at once.
133 0         0 delete $self->{Map}->{$k};
134 0         0 return;
135             }
136              
137             =head2 setAttribute(KEY, OBJECT)
138              
139             Replaces/creates an Attribute in this Map identified by KEY and sets it to OBJECT.
140              
141             $env->setAttribute("PATH", Data::Sofu::List->new(split/:/,$env->value("PATH")->toString()));
142              
143             =cut
144              
145             sub setAttribute {
146 160     160 1 211 my $self=shift;
147 160         418 my $k=shift;
148 160 50       1387 push @{$self->{Order}},$k unless $self->{Map}->{$k};
  160         484  
149 160         1025 $self->{Map}->{$k}=Data::Sofu::Object->new(shift);
150 160         871 return;
151             }
152              
153             =head2 hasAttribute(KEY)
154              
155             Return a true value if this Map has an Attribute identified by KEY
156              
157             if ($env->hasAttribute("Lines")) {
158             print "X" x $env->value("Lines")->toInt();
159              
160             =cut
161              
162             sub hasAttribute {
163 3     3 1 9 my $self=shift;
164 3         5 my $k=shift;
165 3         21 return exists $self->{Map}->{$k};
166             }
167              
168             =head2 hasValue(KEY)
169              
170             Returns 1 if this Map has an Attribute called KEY and this Attribute is a C.
171              
172             $env->hasValue("PATH") === $env->hasAttribute("PATH") and $env->object("PATH")->isValue();
173              
174             Note: Return 0 if the Object is not a Value and under if the Element doesn't exist at all.
175              
176             =cut
177              
178             sub hasValue {
179 3     3 1 8 my $self=shift;
180 3         7 my $k=shift;
181 3 50       102 return $self->{Map}->{$k}->isValue() if exists $self->{Map}->{$k};
182 0         0 return undef;
183             }
184              
185             =head2 hasMap(KEY)
186              
187             Returns 1 if this Map has an Attribute called KEY and this Attribute is a C.
188              
189             $env->hasMap("PATH") === $env->hasAttribute("PATH") and $env->object("PATH")->isMap();
190              
191             Note: Return 0 if the Object is not a Value and under if the Element doesn't exist at all.
192              
193             =cut
194              
195             sub hasMap {
196 9     9 1 32 my $self=shift;
197 9         18 my $k=shift;
198 9 50       68 return $self->{Map}->{$k}->isMap() if exists $self->{Map}->{$k};
199 0         0 return undef;
200             }
201              
202             =head2 hasList(KEY)
203              
204             Returns 1 if this Map has an Attribute called KEY and this Attribute is a C.
205              
206             $env->hasList("PATH") === $env->hasAttribute("PATH") and $env->object("PATH")->isList();
207              
208             Note: Return 0 if the Object is not a Value and under if the Element doesn't exist at all.
209              
210             =cut
211              
212             sub hasList {
213 9     9 1 36 my $self=shift;
214 9         18 my $k=shift;
215 9 50       72 return $self->{Map}->{$k}->isList() if exists $self->{Map}->{$k};
216 0         0 return undef;
217             }
218              
219             =head2 list(KEY)
220              
221             Returns the Object at the key called KEY as a C.
222              
223             Dies if the Object is not a Data::Sofu::List.
224            
225             $env->list("PATH") === $env->object("PATH")->asList()
226              
227             =cut
228              
229             sub list {
230 63     63 1 174 my $self=shift;
231 63         246 return $self->object(shift(@_))->asList();
232             }
233              
234             =head2 map(KEY)
235              
236             Returns the Object at the key called KEY as a C.
237              
238             Dies if the Object is not a Data::Sofu::Map.
239            
240             $env->map("PATH") === $env->object("PATH")->asMap()
241              
242             =cut
243              
244             sub map {
245 21     21 1 42 my $self=shift;
246 21         74 return $self->object(shift(@_))->asMap();
247             }
248              
249             =head2 value(KEY)
250              
251             Returns the Object at the key called KEY as a C.
252              
253             Dies if the Object is not a Data::Sofu::Value.
254            
255             $env->value("PATH") === $env->object("PATH")->asValue()
256              
257             =cut
258              
259             sub value {
260 3     3 1 9 my $self=shift;
261 3         12 return $self->object(shift(@_))->asValue();
262             }
263              
264             =head2 asMap()
265              
266             Returns itself, used to make sure this Map is really a Map (C and C will die if called with this method)
267              
268             =cut
269              
270             sub asMap {
271 24     24 1 115 return shift;
272             }
273              
274             =head2 asHash()
275              
276             Perl only
277              
278             Returns this Map as a real perl Hash.
279              
280             =cut
281              
282             sub asHash {
283 0     0 1 0 my $self=shift;
284 0         0 return %{$$self{Map}};
  0         0  
285             }
286              
287             =head2 isMap()
288              
289             Returns 1
290              
291             =cut
292              
293             sub isMap {
294 130     130 1 2681 return 1;
295             }
296              
297             =head2 next()
298              
299             Returns the next Key, Value pair in no specific order. Used to iterate over the Map.
300              
301             If called in list context it returns the (Key, Value) as a list, in scalar context it returns [Key, Value] as an Arrayref and in Void Context it resets the Iterator.
302              
303             while (my ($k,$v) = $env->next()) {
304             last if $k eq "PATH";
305             print "$k = ".$v->asValue()->ToString()."\n";
306             }
307             $env->next() #Reset Iterator
308              
309             =cut
310              
311             sub next {
312 0     0 1 0 my $self=shift;
313 0 0       0 if (defined wantarray) {
314 0 0       0 return CORE::each(%{$self->{Map}}) if wantarray;
  0         0  
315 0         0 return [CORE::each(%{$self->{Map}})];
  0         0  
316             }
317 0         0 keys(%{$self->{Map}});
  0         0  
318 0         0 return;
319             }
320              
321             =head2 each()
322              
323             Returns the next Key, Value pair in no specific order. Used to iterate over the Map.
324              
325             If called in list context it returns the (Key, Value) as a list, in scalar context it returns [Key, Value] as an Arrayref and in Void Context it resets the Iterator.
326              
327             while (my ($k,$v) = $env->each()) {
328             last if $k eq "PATH";
329             print "$k = ".$v->asValue()->ToString()."\n";
330             }
331             $env->each() #Reset Iterator
332              
333             =cut
334              
335             sub each {
336 0     0 1 0 my $self=shift;
337 0 0       0 if (defined wantarray) {
338 0 0       0 return CORE::each(%{$self->{Map}}) if wantarray;
  0         0  
339 0         0 return [CORE::each(%{$self->{Map}})];
  0         0  
340             }
341 0         0 keys(%{$self->{Map}});
  0         0  
342 0         0 return;
343             }
344              
345             =head2 length()
346              
347             Returns the length of this Map
348              
349             Warning: Resets the Iterator.
350              
351             =cut
352              
353              
354             sub length {
355 78     78 1 146 my $self=shift;
356 78         96 return scalar keys %{$self->{Map}};
  78         630  
357             }
358              
359             =head2 opApply(CODE)
360              
361             Takes a Subroutine and iterates with it over the Map. Values and Keys can't be modified.
362              
363             The Subroutine takes two Arguments: first is the Key and second is the Value.
364              
365             $env->opApply(sub {
366             print "Key = $_[0], Value = ",$_[1]->asValue->toString(),"\n";
367             });
368              
369             Note: The Values are Objects, so they still can be changed, but not replaced.
370              
371             =cut
372              
373             sub opApply {
374 0     0 1 0 my $self=shift;
375 0         0 my $code=shift;
376 0 0 0     0 croak("opApply needs a Code Reference") unless ref $code and lc ref $code eq "code";
377 0         0 while (my ($k,$v) = CORE::each(%{$self->{Map}})) {
  0         0  
378 0         0 $code->($k,$v);
379             }
380             }
381              
382              
383             =head2 opApplyDeluxe(CODE)
384              
385             Perl only.
386              
387             Takes a Subroutine and iterates with it over the Map. Keys can't be modified, but Values can.
388              
389             The Subroutine takes two Arguments: first is the Key and second is the Value.
390              
391             my $i=0;
392             $env->opApplyDeluxe(sub {
393             $_[1]=new Data::Sofu::Value($i++);
394             });
395              
396             Note: Please make sure every replaced Value is a C or inherits from it.
397              
398             =cut
399              
400              
401             sub opApplyDeluxe {
402 0     0 1 0 my $self=shift;
403 0         0 my $code=shift;
404 0 0 0     0 croak("opApplyDeluxe needs a Code Reference") unless ref $code and lc ref $code eq "code";
405 0         0 while (my $k = CORE::each(%{$self->{Map}})) {
  0         0  
406 0         0 $code->($k,$self->{Map}->{$k}); #Aliasing the Value of the Map, so it can be changed....
407             }
408             }
409              
410             =head2 C
411              
412             Stores a comment in the Object if TREE is empty, otherwise it propagades the Comment to all its Values
413              
414             Should not be called directly, use importComments() instead.
415              
416             =cut
417              
418             sub storeComment {
419 14     14 1 16 my $self=shift;
420 14         18 my $tree=shift;
421 14         15 my $comment=shift;
422             #print "Tree = $tree, Comment = @{$comment}\n";
423 14 100 66     54 if ($tree eq "" or $tree eq "=") {
424             #print "Setting to $comment\n";
425 4         1390 $self->{Comment}=$comment;
426             }
427             else {
428             #print "Setting to $comment on $tree\n";
429 10         31 my ($key,$tree) = split(/\-\>/,$tree,2);
430 10 100       19 $tree="" unless $tree;
431 10         28 $key=Data::Sofu::Sofukeyunescape($key);
432 10 50       68 $self->{Map}->{$key}->storeComment($tree,$comment) if $self->{Map}->{$key};
433             }
434              
435             }
436              
437             =head2 orderedKeys()
438              
439             Return all Keys of the Map in insertion Order
440              
441             =cut
442              
443             sub orderedKeys {
444 148     148 1 228 my $self=shift;
445 148         254 local $_;
446 148         191 my @order = grep {exists $self->{Map}->{$_}} @{$self->{Order}}; #Check if all keys are still there.
  324         1307  
  148         409  
447 148         316 my %seen=();
448 148         2433 @seen{@order}=(1) x @order;
449 148         296 return (@order,grep !$seen{$_},keys %{$self->{Map}});
  148         1297  
450             }
451              
452             =head2 C
453              
454             Returns a string representing this Map and all its children.
455              
456             Runs string(LEVEL+1,TREE+keyname) on all its values.
457              
458             =cut
459              
460             sub stringify {
461 12     12 1 24 my $self=shift;
462 12         19 my $level=shift;
463 12         16 my $tree=shift;
464 12 100       29 my $str="{" if $level;
465 12 100       28 $level-=1 if $level < 0;
466 12         50 $str.=$self->stringComment();
467 12         26 $str.="\n";
468             #foreach my $key (keys %{$self->{Map}}) {
469 12         32 foreach my $key ($self->orderedKeys()) {
470 27         102 $str.=$self->indent($level);
471 27         94 $str.=Data::Sofu::Sofukeyescape($key);
472 27         51 $str.=" = ";
473 27         131 $str.=$self->{Map}->{$key}->string($level+1,$tree."->".Data::Sofu::Sofukeyescape($key));
474             }
475 12 100       49 $str.=$self->indent($level-1) if $level > 1;
476 12 100       36 $str.="}\n" if $level;
477 12         224 return $str;
478             }
479              
480              
481              
482             =head2 C
483              
484             Returns a String containing the binary representaion of this Map (according the Sofu Binary File Format)
485              
486             Look at C for the Parameters.
487              
488             Note: This uses C as a only packer.
489              
490             =cut
491              
492             sub binaryPack {
493 6     6 1 89 require Data::Sofu::Binary;
494 6         16 my $self = shift;
495 6         70 my $bin=Data::Sofu::Binary->new("000_002_000_000"); #Use this Version, the next Version will
496 6         322 my $str=$bin->packHeader(@_);
497 6         45 $str.=$self->packComment($bin);
498 6         49 %Data::Sofu::Object::OBJ=($self=>"->");
499             #foreach my $key (keys %{$self->{Map}}) {
500 6         30 foreach my $key ($self->orderedKeys()) {
501 30         99 $str.=$bin->packText($key);
502             #$str.=$bin->packData($self->{Map}->{$key},Data::Sofu::Sofukeyescape($key));
503 30         196 $str.=$self->{Map}->{$key}->binary("->".Data::Sofu::Sofukeyescape($key),$bin);
504             }
505 6         550 return $str;
506             }
507              
508             =head2 C
509              
510             Returns the binary version of this Map and all its children using the BINARY DRIVER. Don't call this one, use binaryPack instead
511              
512             =cut
513              
514             sub binarify {
515 18     18 1 30 my $self=shift;
516 18         27 my $tree=shift;
517 18         25 my $bin=shift;
518 18         57 my $str=$bin->packType(3);
519 18         52 $str.=$self->packComment($bin);
520 18         53 $str.=$bin->packLong(scalar keys %{$self->{Map}});
  18         109  
521             #foreach my $key (keys %{$self->{Map}}) {
522 18         51 foreach my $key ($self->orderedKeys()) {
523 24         85 my $kkey = Data::Sofu::Sofukeyescape($key);
524 24         70 $str.=$bin->packText($key);
525 24         188 $str.=$self->{Map}->{$key}->binary("$tree->$kkey",$bin);
526             }
527 18         111 return $str;
528             }
529              
530             =head1 BUGS
531              
532             This only supports the 2 Argument version of opApply, I have no idea how to find out if a codereference takes 2 or 1 Arguments.
533              
534             =head1 SEE ALSO
535              
536             L, L, L, L, L, L, L
537              
538             =cut
539              
540             1;