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: 2009-28-01
4             #Copyright (c) 2009 Marc-Seabstian "Maluku" Lucksch
5             #Version 0.3
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   23 use strict;
  3         6  
  3         145  
43 3     3   34 use warnings;
  3         7  
  3         155  
44             require Data::Sofu::Object;
45             require Data::Sofu;
46 3     3   17 use Carp;
  3         5  
  3         10280  
47             our $VERSION="0.3";
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 254 my $self={};
66 148         453 bless $self,shift;
67 148         450 $self->{Map}={};
68 148         318 $self->{Order}=[];
69 148 100       407 if (@_) {
70 12         45 $self->set(@_);
71             }
72 148         606 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         23 local $_;
86 20         24 my $temp=shift;
87 20         33 my $order=shift;
88 20         83 foreach (values %$temp) {
89 45         170 $_=Data::Sofu::Object->new($_);
90             }
91 20         47 $self->{Order}=$order;
92 20         50 $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 483 my $self=shift;
113 348         472 my $k=shift;
114 348 50       1450 if (exists $self->{Map}->{$k}) {
115 348         1988 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 318 my $self=shift;
147 160         245 my $k=shift;
148 160 50       528 push @{$self->{Order}},$k unless $self->{Map}->{$k};
  160         446  
149 160         813 $self->{Map}->{$k}=Data::Sofu::Object->new(shift);
150 160         701 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 7 my $self=shift;
164 3         6 my $k=shift;
165 3         18 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 5 my $self=shift;
180 3         5 my $k=shift;
181 3 50       26 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         17 my $k=shift;
198 9 50       105 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 34 my $self=shift;
214 9         17 my $k=shift;
215 9 50       232 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 132 my $self=shift;
231 63         144 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 43 my $self=shift;
246 21         59 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 7 my $self=shift;
261 3         11 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 123 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 3335 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 110 my $self=shift;
356 78         107 return scalar keys %{$self->{Map}};
  78         413  
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 storeComment(TREE,COMMENT)
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 21 my $self=shift;
420 14         17 my $tree=shift;
421 14         15 my $comment=shift;
422             #print "Tree = $tree, Comment = @{$comment}\n";
423 14 100 66     47 if ($tree eq "" or $tree eq "=") {
424             #print "Setting to $comment\n";
425 4         14 $self->{Comment}=$comment;
426             }
427             else {
428             #print "Setting to $comment on $tree\n";
429 10         30 my ($key,$tree) = split(/\-\>/,$tree,2);
430 10 100       18 $tree="" unless $tree;
431 10         23 $key=Data::Sofu::Sofukeyunescape($key);
432 10 50       65 $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 219 my $self=shift;
445 148         176 local $_;
446 148         196 my @order = grep {exists $self->{Map}->{$_}} @{$self->{Order}}; #Check if all keys are still there.
  324         1452  
  148         398  
447 148         290 my %seen=();
448 148         635 @seen{@order}=(1) x @order;
449 148         264 return (@order,grep !$seen{$_},keys %{$self->{Map}});
  148         1386  
450             }
451              
452             =head2 stringify(LEVEL, TREE)
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 16 my $self=shift;
462 12         13 my $level=shift;
463 12         10 my $tree=shift;
464 12 100       29 my $str="{" if $level;
465 12 100       26 $level-=1 if $level < 0;
466 12         41 $str.=$self->stringComment();
467 12         18 $str.="\n";
468             #foreach my $key (keys %{$self->{Map}}) {
469 12         30 foreach my $key ($self->orderedKeys()) {
470 27         75 $str.=$self->indent($level);
471 27         75 $str.=Data::Sofu::Sofukeyescape($key);
472 27         41 $str.=" = ";
473 27         110 $str.=$self->{Map}->{$key}->string($level+1,$tree."->".Data::Sofu::Sofukeyescape($key));
474             }
475 12 100       42 $str.=$self->indent($level-1) if $level > 1;
476 12 100       35 $str.="}\n" if $level;
477 12         162 return $str;
478             }
479              
480              
481              
482             =head2 binaryPack(ENCODING, BYTEORDER, SOFUMARK)
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 84 require Data::Sofu::Binary;
494 6         15 my $self = shift;
495 6         75 my $bin=Data::Sofu::Binary->new("000_002_000_000"); #Use this Version, the next Version will
496 6         41 my $str=$bin->packHeader(@_);
497 6         47 $str.=$self->packComment($bin);
498 6         42 %Data::Sofu::Object::OBJ=($self=>"->");
499             #foreach my $key (keys %{$self->{Map}}) {
500 6         26 foreach my $key ($self->orderedKeys()) {
501 30         88 $str.=$bin->packText($key);
502             #$str.=$bin->packData($self->{Map}->{$key},Data::Sofu::Sofukeyescape($key));
503 30         146 $str.=$self->{Map}->{$key}->binary("->".Data::Sofu::Sofukeyescape($key),$bin);
504             }
505 6         433 return $str;
506             }
507              
508             =head2 binarify(TREE,BINARY DRIVER)
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 25 my $self=shift;
516 18         23 my $tree=shift;
517 18         25 my $bin=shift;
518 18         55 my $str=$bin->packType(3);
519 18         58 $str.=$self->packComment($bin);
520 18         35 $str.=$bin->packLong(scalar keys %{$self->{Map}});
  18         79  
521             #foreach my $key (keys %{$self->{Map}}) {
522 18         51 foreach my $key ($self->orderedKeys()) {
523 24         68 my $kkey = Data::Sofu::Sofukeyescape($key);
524 24         76 $str.=$bin->packText($key);
525 24         164 $str.=$self->{Map}->{$key}->binary("$tree->$kkey",$bin);
526             }
527 18         89 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;