File Coverage

blib/lib/BBDB.pm
Criterion Covered Total %
statement 185 237 78.0
branch 33 74 44.5
condition 7 29 24.1
subroutine 17 20 85.0
pod 12 17 70.5
total 254 377 67.3


line stmt bran cond sub pod time code
1             package BBDB;
2              
3             =head1 NAME
4              
5             BBDB - Read and Write BBDB files
6              
7             =head1 VERSION
8              
9             Version 1.40
10              
11             =cut
12              
13             our $VERSION = '1.40';
14              
15              
16 1     1   1066 use strict;
  1         2  
  1         53  
17 1     1   6 use vars qw(@ISA @EXPORT @EXPORT_OK);
  1         2  
  1         5473  
18              
19             require Exporter;
20              
21             @ISA = qw(Exporter);
22             @EXPORT_OK = qw(decode encode part simple);
23              
24             # Sorry, but I'm an American, change this if you're not!
25             $BBDB::default_country = 'USA'; # used only for printing
26              
27             $BBDB::debug = 0;
28              
29             my $quoted_string_pat =<<'END';
30             " # First a quote
31             ([^\"\\]* # Doesn't contain a \ or "
32             (?:
33             \\.[^\"\\]* # A backslash and any char and not another
34             # backslash or quote
35             )* # as many of these as we like
36             )
37             " # And the final quote
38             END
39              
40             my $int_or_list=<
41             (?: # Either just a plain
42             \\d+ # integer
43             | # OR
44             \\( # A list of (
45             \\d+(?:\\ \\d+)* # space separated plain integers
46             \\) # followed by a closing )
47             | # OR
48             \\( # A list of (
49             (?: $quoted_string_pat # space separated
50             (?:\\ | \\) )? # quoted strings followed by
51             )* # a closing )
52             )
53             END
54              
55             my $nil_or_string_pat = <
56             (?:nil|$quoted_string_pat)
57             END
58              
59             my $list_of_strings = <
60             (?:nil| # Might be nil
61             \\( # Starts with an open (
62             (?:
63             $quoted_string_pat # And a quoted string
64             (?:\\ | \\) )? # followed by a space or )
65             )+ # at least one
66             )
67             END
68              
69             my $aka_pat = $list_of_strings;
70              
71             my $single_phone_pat = <
72             \\[
73             (?:$quoted_string_pat) # The type of phone
74             \\ (
75             (?:$quoted_string_pat) # An international number is quoted
76             | # BUT
77             ([(\\d|nil\\ ]+) # An american number is a list of integers
78             (?:nil)? # Maybe followed by nil
79             )
80             \\]
81             END
82              
83             my $phone_pat = <
84             (?:nil| # Might be nil
85             \\( # Starts with an open (
86             (?:
87             $single_phone_pat # And a single phone pattern
88             (?:\\ | \\) )? # followed by a space or )
89             )+ # at least one
90             )
91             END
92              
93             my $street_pat = $list_of_strings;
94              
95             my $single_address_pat = <
96             \\[ # The opening [
97             $quoted_string_pat \\ # The address location
98             ($street_pat) \\ # The street list
99             $quoted_string_pat \\ # The city
100             $quoted_string_pat \\ # The state
101             $quoted_string_pat \\ # The zip code
102             $nil_or_string_pat # The country
103             \\] # The closing ]
104             END
105              
106             my $address_pat = <
107             (?:nil| # Might be nil
108             \\( # Starts with an open (
109             (?:
110             $single_address_pat # And a single address pattern
111             (?:\\ | \\) )? # followed by a space or )
112             )+ # at least one
113             )
114             END
115              
116             my $net_pat =<
117             (?:nil| # Might be nil
118             \\( # Starts with an open (
119             (?:
120             $quoted_string_pat # And a quoted string
121             (?:\\ | \\) )? # followed by a space or )
122             )+ # at least one
123             )
124             END
125              
126             my $lisp_symbol_pat = '[\w\-]+';
127              
128             my $alist_pat = <
129             (
130             \\( # An instance of an Alist - open (
131             ($lisp_symbol_pat) # A lisp Symbol
132             \\ \\. \\ # followed by space . space
133             $quoted_string_pat # followed by a quoted string
134             \\)\\ ? # and a closed ) and maybe a space
135             ) # at least one of these
136             END
137              
138             my $notes_pat = <
139             (?:nil| # Might be nil
140             \\( # An open (
141             $alist_pat+ # and at least one Alist
142             \\) # And a closed )
143             )
144             END
145              
146             my $bbdb_entry_pat = <
147             \\[ # Opening vector [
148             $nil_or_string_pat \\ # First name
149             $nil_or_string_pat \\ # Last name
150             ($aka_pat) \\ # Also Known As
151             $nil_or_string_pat \\ # Company name
152             ($phone_pat) \\ # Phone list
153             ($address_pat) \\ # Address list
154             ($net_pat) \\ # Net names list
155             ($notes_pat) \\ # Notes Alist
156             (?:nil\\ *)+ # Always nil as far as I can tell
157             \\] # Closing vector ]
158             END
159              
160             ########################################################################
161             # I added some ()s inside the patterns above, to make it possible to
162             # break out the sub fields of a bbdb record. Once consequence of this
163             # is to make it very difficult to figure out at what position the top
164             # level fields are, so the subroutine _figure_out_indices does exactly
165             # that. It uses the sample data below, and searches the fields that are
166             # matched by the $bbdb_entry_pat pattern above. The results are stored
167             # in the %field_index hash so we can reference them by name, and perhaps
168             # change the ()s in the patterns above without breaking everything.
169             ########################################################################
170              
171             my @field_names =
172             qw (first last aka company phone address net notes);
173             my %field_names;
174             @field_names{@field_names} = (0..$#field_names);
175              
176             my $sample_data = <
177             ["first" "last" ("aka") "company" (["phone with integer" 123 456 789] ["phone with quotes" "123-456-789"]) (["address" ("street1" "street2" "street3") "city" "state" "zip" "country"] ["address2" ("street1" "street2" "street3") "city" "state" "12345" "country"]) ("net") ((notes . "data")) nil]
178             END
179              
180             my %field_index;
181             sub _figure_out_indices {
182 1     1   391 my @fields = ($sample_data =~ m/^$bbdb_entry_pat$/ox);
183 1         5 my @names = @field_names;
184 1         1 my $i;
185 1         6 for ($i=0; $i < @fields; $i++) {
186 21 100       89 if ($fields[$i] =~ $names[0]) {
187 8         11 $field_index{shift @names} = $i;
188 8 100       36 last unless @names;
189             }
190             }
191             }
192             _figure_out_indices();
193              
194              
195             ########################################################################
196             sub un_escape {
197 92     92 0 105 my $s = shift;
198 92         107 $s =~ s/\\n/\n/g;
199 92         113 $s =~ s/\\([0-3][0-7]{2})/sprintf("%c", oct($1))/eg; # umlauts etc.
  0         0  
200 92         98 $s =~ s/\\(.)/$1/g; # should just be " or \ "emacs
201 92         335 return $s;
202             }
203              
204             ########################################################################
205              
206             sub decode {
207 4     4 1 8 my ($self,$str) = @_;
208 4         9 my @fields = ();
209 4 50       671 unless (@fields = ($str =~ m/^$bbdb_entry_pat$/ox)) {
210 0 0       0 if ($BBDB::debug) {
211 0         0 my $pat = '';
212 0         0 my @subpats = (
213             [ '\[', 'opening ['],
214             [ $nil_or_string_pat, 'First name'],
215             [ $nil_or_string_pat, 'Last name'],
216             [ $aka_pat, 'Also known as'],
217             [ $nil_or_string_pat, 'Company name'],
218             [ $phone_pat, 'Phone'],
219             [ $address_pat, 'Address'],
220             [ $net_pat, 'Net names' ],
221             [ $notes_pat, 'Notes'],
222             [ 'nil', 'Last nil'],
223             [ '\]', 'closing ]']
224             );
225 0         0 my $i;
226 0         0 foreach $i (@subpats) {
227 0         0 $pat .= $i->[0];
228 0 0 0     0 printf STDERR "No match at %s\n", $i->[1] and last unless
229             $str =~ m/^$pat/x;
230 0 0 0     0 $pat .= '\ ' unless $i->[0] eq '\[' or $i->[0] eq 'nil' ;
231             }
232             }
233 0         0 return undef;
234             }
235              
236              
237 4         14 my $i;
238 4         6 local($_);
239              
240 4         7 foreach $i (@field_names) {
241 32 50 33     154 $fields[$field_index{$i}] = ''
242             if (!defined $fields[$field_index{$i}] or
243             $fields[$field_index{$i}] eq 'nil');
244             }
245              
246 4         52 my @aka = split(/$quoted_string_pat/ox,$fields[$field_index{aka}]);
247             # print "AKA=\n<",join(">\n<",@aka),">\nEND AKA\n";
248 4         8 my $aka = [];
249 4         13 for ($i=0; $i < @aka - 1; $i+=2) {
250 4         14 push @$aka, un_escape($aka[$i+1]);
251             }
252              
253 4         99 my @phone = split(/$single_phone_pat/ox,$fields[$field_index{phone}]);
254             # print "PHONE=\n<",join(">\n<",@phone),">\nEND PHONE\n";
255 4         11 my $phone = [];
256 4         11 for ($i=0; $i < @phone - 1; $i+=5) {
257 10         11 my $numbers;
258 10 100       20 if ($phone[$i+4]) { # just digits and white space
259 6         9 $numbers = [];
260 6         32 @$numbers = split(/ /,$phone[$i+4]);
261 6 50       15 unshift(@$numbers, 0) if @$numbers == 2;
262 6         15 push(@$numbers, (0,0,0));
263 6         12 splice(@$numbers, 4); # area code and ext might be 0
264             } else {
265 4         9 $numbers = un_escape($phone[$i+3]);
266             }
267 10         21 push @$phone,[
268             un_escape($phone[$i+1]),
269             $numbers
270             ];
271             }
272              
273 4         186 my @address = split(/$single_address_pat/ox,$fields[$field_index{address}]);
274             # print "ADDRESS=\n<",join(">\n<",@address),">\nEND ADDRESS\n";
275 4         14 my $address = [];
276 4         14 for ($i=0; $i < @address - 1; $i+=8) {
277 6         62 my @streets = split(/$quoted_string_pat/ox,$address[$i+2]);
278 6         12 my $streets = [];
279 6         7 my $j;
280 6         19 for ($j=0; $j < @streets - 1; $j+=2) {
281 14         25 push @$streets, un_escape($streets[$j+1]);
282             }
283 6         12 push @$address,[
284             un_escape($address[$i+1]),
285             $streets,
286             un_escape($address[$i+4]),
287             un_escape($address[$i+5]),
288             un_escape($address[$i+6]),
289             un_escape($address[$i+7]),
290             ];
291             }
292              
293 4         55 my @net = split(/$quoted_string_pat/ox,$fields[$field_index{net}]);
294             # print "NET=\n<",join(">\n<",@net),">\nEND NET\n";
295 4         9 my $net = [];
296 4         13 for ($i=0; $i < @net - 1; $i+=2) {
297 8         18 push @$net, un_escape($net[$i+1]);
298             }
299              
300              
301 4         179 my @notes = split(/$alist_pat/ox,$fields[$field_index{notes}]);
302             # print "NOTES=\n<",join(">\n<",@notes),">\nEND NOTES\n";
303 4         9 my $notes = [];
304 4         14 for ($i=0; $i < @notes - 1; $i+=4) {
305 10         23 push @$notes, [
306             $notes[$i+2],
307             un_escape($notes[$i+3])
308             ]
309             }
310              
311 4         11 $self->{'data'} = [
312             un_escape($fields[$field_index{first}]),
313             un_escape($fields[$field_index{last}]),
314             $aka,
315             un_escape($fields[$field_index{company}]),
316             $phone,
317             $address,
318             $net,
319             $notes
320             ];
321 4         42 return 1;
322             }
323              
324             ########################################################################
325              
326             sub quoted_stringify { # escape \ and " in a string"
327 138     138 0 157 my $s = shift; # and return it surrounded by
328 138         260 $s =~ s/(\\|")/\\$1/g; # quotes
329 138         169 $s =~ s/([\200-\377])/sprintf("\\%o",ord($1))/eg; # fix umlauts
  0         0  
330 138         155 $s =~ s/\n/\\n/g; # put back newlines
331 138         363 return "\"$s\"";
332             }
333              
334             sub nil_or_string { # return nil if empty string
335 27 50 33 27 0 119 return 'nil' if !defined $_[0] or $_[0] eq ''; # otherwise quote it
336 27         51 return quoted_stringify(@_);
337             }
338              
339             sub nil_or_list { # return nil if empty string
340 0 0   0 0 0 return 'nil' if $_[0] eq ''; # otherwise quote it and add ()s
341 0         0 return '(' . quoted_stringify(@_) . ')' ;
342             }
343              
344             ########################################################################
345              
346             sub encode {
347 6     6 1 14 my $self = shift;
348 6         19 my ($first, $last, $aka, $company,
349 6         7 $phone, $address, $net, $notes) = @{$self->{'data'}};
350 6         8 my ($i,@result,$s);
351 6         12 push @result,nil_or_string($first);
352 6         12 push @result,nil_or_string($last);
353              
354 6 50 33     31 if (ref($aka) and @$aka) {
355 6         7 my @aka;
356 6         10 foreach $i (@$aka) {
357 6         10 push @aka, quoted_stringify($i);
358             }
359 6         19 push @result, "(@aka)";
360             } else {
361 0         0 push @result, 'nil';
362             }
363              
364 6         12 push @result,nil_or_string($company);
365              
366 6 50 33     82 if (ref($phone) and @$phone) {
367 6         9 my @phone;
368 6         8 foreach $i (@$phone) {
369 15         15 my $number;
370 15 100       31 if ( ref($i->[1]) ) {
371 9         11 $number = join(' ',@{$i->[1]});
  9         22  
372             } else {
373 6         10 $number = quoted_stringify($i->[1]);
374             }
375 15         30 push @phone,"[" . quoted_stringify($i->[0]) . " $number]";
376             ;
377             }
378 6         22 push @result, "(@phone)";
379             } else {
380 0         0 push @result, 'nil';
381             }
382 6 50 33     27 if (ref($address) and @$address) {
383 6         6 my @address;
384 6         8 foreach $i (@$address) {
385 9         10 local($_);
386 9         9 my $j;
387             my @streets;
388 9         10 foreach $j (@{$i->[1]}) {
  9         15  
389 21         33 push @streets, quoted_stringify($j);
390             }
391 9         20 my @fields = map {quoted_stringify($_)} @$i[0,2,3,4];
  36         50  
392 9         19 push @fields, nil_or_string(@$i[5]);
393 9         28 splice(@fields,1,0,"(@streets)");
394 9 50       22 $fields[1] = 'nil' unless @streets;
395 9         44 push @address, "[@fields]";
396             }
397 6         21 push @result, "(@address)";
398             } else {
399 0         0 push @result, 'nil';
400             }
401              
402 6 50 33     26 if (ref($net) and @$net) {
403 6         6 my @net;
404 6         8 foreach $i (@$net) {
405 12         20 push @net, quoted_stringify($i);
406             }
407 6         20 push @result, "(@net)";
408             } else {
409 0         0 push @result, 'nil';
410             }
411              
412 6 50       9 if ($notes) {
413 6         6 my @notes;
414 6         9 foreach $i (@$notes) {
415 15         36 push @notes, "(" . $i->[0] . " . " . quoted_stringify($i->[1]) . ")";
416             }
417 6         21 push @result, "(@notes)";
418             } else {
419 0         0 push @result, 'nil';
420             }
421 6         40 return "[@result nil]";
422             }
423              
424             ########################################################################
425              
426             sub new {
427 4     4 1 82 my $class = shift;
428 4         6 my $self = {};
429 4         8 bless $self, $class;
430 4         9 return $self;
431             };
432              
433             ########################################################################
434              
435             sub part {
436 8     8 1 58 my ($self,$name,$data) = @_;
437 8         9 my $result;
438 8 50       18 if ($name eq 'all') {
439 0         0 $result = $self->{data};
440 0 0       0 $self->{data} = $data if @_ == 3;
441             } else {
442 8 50       45 die "No such field $name" unless exists $field_names{$name};
443 8         19 $result = $self->{data}->[$field_names{$name}];
444 8 50       19 $self->{data}->[$field_names{$name}] = $data if @_ == 3;
445             }
446 8         23 return $result;
447             }
448              
449             ########################################################################
450              
451             # Return the name part of the notes
452             sub note_names {
453 0     0 0 0 my $self = shift;
454 0         0 my $notes = $self->part('notes');
455 0 0 0     0 return () unless ref($notes) and @$notes;
456 0         0 local ($_);
457 0         0 my @fields = map { $_->[0] } @$notes;
  0         0  
458 0         0 return @fields;
459             }
460              
461             sub note_by_name {
462 1     1 1 11 my ($self,$key) = @_;
463 1         4 my $notes = $self->part('notes');
464 1 50 33     9 return unless ref($notes) and @$notes;
465 1         3 local ($_);
466 1         2 my @result = grep { $_->[0] eq $key } @$notes;
  4         10  
467 1 50       4 return unless @result == 1;
468 1         4 return $result[0]->[1];
469             }
470              
471             sub simple {
472 1     1 1 51 my ($file,$bbdb) = @_;
473 1         3 local ($_);
474 1 50       5 if (@_ == 1) { #we're reading'
475 1 50       58 open(INFILE,$file) or die $!;
476 1         3 my $count = 0;
477 1         2 my @results;
478 1         37 while () {
479 4 100       21 next if m/^;/; # skip comments
480 2         4 $count++;
481 2         4 chomp;
482             # print STDERR "$count ";
483 2         12 $bbdb = new BBDB();
484 2 50       8 if ($bbdb->decode($_)) {
485 2         20 push @results,$bbdb;
486             } else {
487 0         0 print STDERR "No match at record $count in $file\nData = $_\n";
488             }
489             }
490 1         14 close INFILE;
491 1         6 return \@results;
492             } else { # we're writing'
493 0 0       0 open(OUTFILE,">$file") or die $!;
494 0         0 my $rec;
495 0         0 my ($notes,@notes,%notes);
496 0         0 foreach $rec (@$bbdb) {
497 0         0 @notes{note_names($rec)} = 1;
498             }
499 0         0 local($_);
500 0         0 @notes = grep !/^(creation-date|timestamp|notes)$/, keys %notes;
501 0         0 print OUTFILE ";;; file-version: 6\n";
502 0         0 print OUTFILE ";;; user-fields: ";
503 0 0       0 print OUTFILE "(",join(' ',@notes),")" if @notes;
504 0         0 print OUTFILE "\n";
505 0         0 foreach $rec (@$bbdb) {
506 0         0 print OUTFILE $rec->encode,"\n";
507             }
508 0         0 close OUTFILE;
509             }
510             }
511              
512             ########################################################################
513              
514             sub aka_as_text {
515 1     1 1 11 my $bbdb = shift;
516 1         2 return join("\n",@{$bbdb->part('aka')});
  1         27  
517             }
518              
519             sub phone_as_text {
520 1     1 1 87 my $bbdb = shift;
521 1         4 my $phones = $bbdb->part('phone');
522 1         2 my @lines;
523 1         4 foreach my $phone (@$phones) {
524 3         7 my ($where,$number) = @$phone;
525 3 100       7 if (ref $number) {
526 2         6 my @numbers = @$number;
527 2         4 my $last = pop @numbers;
528 2 50       6 if (@numbers == 3) {
529 2         8 $number = sprintf("(%s)-%s-%s",@numbers);
530             }
531 2 50       8 $number .= " x $last" unless $last eq '0';
532             }
533 3         8 push @lines, "$where: $number";
534             }
535 1         6 return join("\n",@lines);
536             }
537              
538             sub address_as_text {
539 1     1 1 30 my $bbdb = shift;
540 1         4 my $addresses = $bbdb->part('address');
541 1         3 my @lines;
542 1         3 foreach my $address (@$addresses) {
543 2         6 my ($where,$streetR,$city,$state,$zip,$country) = @$address;
544 2         5 my @streets = @$streetR;
545 2 50       4 $country = $BBDB::default_country unless $country;
546 2         6 my $prefix = ' ' x (length($where) + 2);
547 2         14 push @lines,
548             join("\n$prefix",
549             "$where: " . shift @streets,
550             @streets,
551             "$city, $state",
552             "$zip $country");
553             }
554 1         4 return join("\n",@lines);
555             }
556              
557             sub net_as_text {
558 1     1 1 36 my $bbdb = shift;
559 1         3 return join("\n",@{$bbdb->part('net')});
  1         3  
560             }
561              
562             sub notes_as_text {
563 1     1 1 27 my $bbdb = shift;
564 1         2 my @notes = @{$bbdb->part('notes')};
  1         3  
565 1         3 my @lines;
566 1         2 foreach my $note (@notes) {
567 4         9 push @lines, join(": ",@$note);
568             }
569 1         6 return join("\n",@lines);
570             }
571              
572             sub all_as_text {
573 0     0 1   my $bbdb = shift;
574 0           $DB::single = 1;
575 0           my @subs = ( \&aka_as_text, \&net_as_text,
576             \&phone_as_text, \&address_as_text,
577             \¬es_as_text);
578 0           my @lines;
579 0           push @lines, $bbdb->part('first') . ' ' . $bbdb->part('last');
580 0 0         push @lines, $bbdb->part('company') if $bbdb->part('company');
581 0           foreach my $sub (@subs) {
582 0           my $result = $bbdb->$sub;
583 0 0         push @lines, $result if $result;
584             }
585 0           return join("\n", @lines);
586            
587             }
588              
589              
590             1;
591              
592             =head1 SYNOPSIS
593              
594             use BBDB;
595             my $x = new BBDB();
596             $x->decode($string);
597             my $str = $x->encode();
598             # At this point, subject to the BUGS below
599             # $str is the same as $string
600              
601             my $allR = BBDB::simple('/home/henry/.bbdb');
602             map { print $_->part('first')} @$allR; # print out all the first names
603              
604              
605             =head1 DESCRIPTION
606              
607              
608             =head2 Data Format
609              
610             The following is the data layout for a BBDB record. I have created a
611             sample record with my own data. Each field is just separated by a
612             space. I have added comments to the right
613              
614             ["Henry" The first name - a string
615             "Laxen" The last name - a string
616             ("Henry, Enrique") Also Known As - comma separated list
617             "Elegant Solution" Business name - a string
618             (["home" 415 789 1159 0] Phone number field - US style
619             ["fax" 415 789 1156 0] Phone number field - US style
620             ["mazatlan" "011-5269-164195"] Phone number field - International style
621             )
622             (["mailing" The address location, then a list
623             ("PMB 141" "524 San Anselmo Ave.") for the street address, then one each
624             "San Anselmo" "CA" "94960" "USA" for City, State, Zip Code, and country
625             ]
626             ["mazatlan" another Address field
627             ("Reino de Navarra #757" "Frac. El Cid") The street list
628             "Mazatlan" "Sinaloa" City State
629             "82110" "Mexico" Zip and country
630             ]
631             )
632             ("nadine.and.henry@pobox.com" The net addresses - a list of strings
633             "maztravel@maztravel.com")
634             ((creation-date . "1999-09-02") The notes field - a list of alists
635             (timestamp . "1999-10-17")
636             (notes . "Always split aces and eights")
637             (birthday "6/15")
638             )
639             nil The cache vector - always nil
640             ]
641              
642             After this is decoded it will be returned as a reference to a BBDB
643             object. The internal structure of the BBDB object mimics the lisp
644             structure of the BBDB string. It consists of a reference to an array
645             with 9 elements The Data::Dumper output of the above BBDB string would
646             just replaces all of the ()s with []s. It can be accessed by using
647             the C<$bbdb->part('all')> method. For completeness, here is the output
648             of Data::Dumper for the above record:
649              
650             $VAR1 = bless( {
651             'data' => [
652             'Henry',
653             'Laxen',
654             [
655             'Henry, Enrique'
656             ],
657             'Elegant Solutions',
658             [
659             [
660             'home',
661             [
662             '415',
663             '789',
664             '1159',
665             '0'
666             ]
667             ],
668             [
669             'fax',
670             [
671             '415',
672             '789',
673             '1156',
674             '0'
675             ]
676             ],
677             [
678             'mazatlan',
679             '011-5269-164195'
680             ]
681             ],
682             [
683             [
684             'mailing',
685             [
686             'PMB 141',
687             '524 San Anselmo Ave.'
688             ],
689             'San Anselmo',
690             'CA',
691             '94960',
692             'USA'
693             ],
694             [
695             'mazatlan',
696             [
697             'Reino de Navarra #757',
698             'Frac. El Cid'
699             ],
700             'Mazatlan',
701             'Sinaloa',
702             'CP-82110',
703             'Mexico'
704             ]
705             ],
706             [
707             'nadine.and.henry@pobox.com',
708             'maztravel@maztravel.com'
709             ],
710             [
711             [
712             'creation-date',
713             '1999-09-02'
714             ],
715             [
716             'timestamp',
717             '1999-10-17'
718             ],
719             [
720             'notes',
721             'Always split aces and eights'
722             ],
723             [
724             'birthday',
725             '6/15'
726             ]
727             ]
728             ]
729             }, 'BBDB' );
730              
731             =head2 Methods
732              
733             =over 4
734              
735             =item new()
736              
737             called whenever you want to create a new BBDB object.
738             my $bbdb = new BBDB();
739              
740             =item part(name [value])
741              
742             Called to get or set all or part of a BBDB object. The parts of the
743             object are:
744              
745             all first last aka company phone address net notes
746              
747             any other value in the name argument results in death. Some of these
748             parts, namely phone, address, net, and notes have an internal
749             structure and are returned as references to arrays. The others are
750             returned just as strings. The optional second argument sets the part
751             of this BBDB object to the value you provided. There is no
752             consistency checking at this point, so be sure the value you are
753             setting this to is correct.
754              
755             my $first = $bbdb->part('first'); # get the value of the first field
756             $bbdb->part('last','Laxen'); # set the value of the last field
757             my $everything = $bbdb->part('all'); # get the whole record
758              
759             =item BBDB::simple(file_name,[array_ref_of_bbdb])
760              
761             This is a "simple" interface for reading or writing an entire BBDB
762             file. If called with one argument, it returns a reference to an array of BBDB
763             objects. Each object contains the data from the file. Thus the
764             number of BBDB entries equals C if you use:
765              
766             $bbdb = BBDB::simple('/home/henry/.bbdb');
767              
768             If called with two arguments, the first is the filename to create, and
769             the second is a reference to an array of BBDB objects, such as was
770             returned in the one argument version. The objects are scanned for
771             unique user defined fields, which are written out as the 2nd line in
772             the BBDB file, and then the individual records are written out.
773              
774             =item decode(string)
775              
776             Takes a string as written in a BBDB file of a single BBDB record
777             and decodes it into its PERL representation. Returns undef if
778             it couldn't decode the record for some reason, otherwise returns
779             true.
780              
781             $bbdb->decode($entry);
782              
783             =item encode()
784              
785             This is the inverse of decode. Takes an internal PERL version of
786             a BBDB records and returns a string which is a lisp version of the
787             data that BBDB understands. There are some ambiguities, noted in
788             BUGS below.
789              
790             my $string = $bbdb->encode();
791              
792             =item note_by_name('key')
793              
794             Returns the value associated the the specified key in the notes part.
795             Returns undef if the key is not found or is not unique (which I don't
796             think can happen)
797              
798             =head2 Printing methods
799              
800             The parts first, last, and company, are all stored as strings, so you
801             can print them out with a simple C<< print $bbdb->part('name') >> if you like.
802             The rest of the parts have an internal structure, and a method is provided
803             to return them as a string, suitable for printing. The method names are the
804             same as the part names, with a C<_as_text> appended to the part name.
805             For example to print out all of the addresses for a particular BBDB object,
806             just say C<< $bbdb->address_as_text >> Just for completeness, the methods are
807             named here:
808              
809             =item aka_as_text()
810              
811             =item net_as_text()
812              
813             =item phone_as_text()
814              
815             =item address_as_text()
816              
817             =item notes_as_text()
818              
819             =item all_as_text()
820              
821             This returns as a string the entire BBDB object using the methods
822             defined above. For example, the sample record will print out as
823             follows:
824              
825             Henry Laxen
826             Elegant Solutions
827             Henry, Enrique
828             nadine.and.henry@pobox.com
829             maztravel@maztravel.com
830             home: (415)-789-1159
831             fax: (415)-789-1156
832             mazatlan: 011-5269-164195
833             mailing: PMB 141
834             524 San Anselmo Ave.
835             San Anselmo, CA
836             94960 USA
837             mazatlan: Reino de Navarra #757
838             Frac. El Cid
839             Mazatlan, Sinaloa
840             CP-82110 Mexico
841             creation-date: 1999-09-02
842             timestamp: 1999-10-17
843             notes: Always split aces and eights
844             birthday: 6/15
845              
846              
847             =back
848              
849             =head2 Debugging
850              
851             If you find that some records in your BBDB file are failing to be
852             recognized, trying setting C<$BBDB::debug = 1;> to turn on debugging.
853             We will then print out to STDERR the first field of the record that we
854             were unable to recognize. Very handy for complicated BBDB records.
855              
856             =head1 AUTHOR
857              
858             Henry Laxen
859             http://www.maztravel.com/perl
860              
861             =head1 SEE ALSO
862              
863             BBDB texinfo documentation
864              
865             =cut
866              
867              
868             =head1 BUGS
869              
870             In version 2.32 of BBDB, despite what the documentation says, it seems
871             that zip codes are always stored quoted strings, even though it seems
872             to be impossible to enter anything other than an integer.
873              
874             Phone numbers may be converted from strings to integers if they are
875             decoded and encoded. This should not affect the operation of BBDB.
876             Also a null last name is converted from "" to nil, which also doesn't
877             hurt anything.
878              
879             You might ask why I use arrays instead of hashes to encode the data in
880             the BBDB file. The answer is that order matters in the bbdb file, and
881             order isn't well defined in hashes. Also, if you use hashes, at least
882             in the simple minded way, you can easily find yourself with legitimate
883             duplicate keys.
884              
885              
886             =cut
887