File Coverage

blib/lib/Mac/Glue/Apps/AddressBookExport.pm
Criterion Covered Total %
statement 19 92 20.6
branch 1 42 2.3
condition 1 15 6.6
subroutine 6 11 54.5
pod 5 5 100.0
total 32 165 19.3


line stmt bran cond sub pod time code
1             package Mac::Glue::Apps::AddressBookExport;
2              
3 1     1   55649 use strict;
  1         3  
  1         52  
4 1     1   1103 use Encode qw(decode);
  1         12294  
  1         117  
5 1     1   10 use Carp;
  1         8  
  1         95  
6 1     1   10158 use Template;
  1         37053  
  1         74  
7 1     1   1259 use Text::vCard::Addressbook;
  1         95368  
  1         16  
8              
9             our $VERSION = 0.2;
10              
11             my $is_mac = 0;
12             $is_mac = 1 if $^O eq 'darwin';
13              
14             require Mac::Glue if $is_mac;
15              
16             =head1 NAME
17              
18             Mac::Glue::Apps::AddressBookExport - Export from Address Book and publish
19              
20             =head1 SYNOPSIS
21              
22             use Mac::Glue::Apps::AddressBookExport;
23             my $exporter = Mac::Glue::Apps::AddressBookExport->new({
24             'glue_name' => 'Address Book',
25             'template_dir' => '/path/to/template_dir/',
26             'out_dir' => '/where/to/write/out/pages_dir/',
27             'skip_with_image' => 1,
28             });
29             $exporter->export_address_book();
30              
31             This will generate an index page (with names and phone numbers)
32             in your 'out_dir'. It will also create 'out_dir/people/.html'
33             which will be all those people who's name starts with that letter,
34             showing name, phone numbers, emails and addresses.
35            
36             =head1 DESCRIPTION
37              
38             This package uses Mac::Glue to export all vCards from your
39             Apple Address Book. I can write this file to disk, or process
40             the results using Text::vCard and Template Toolkit to generate
41             web pages.
42              
43             =head2 new()
44              
45             my $exporter = Mac::Glue::Apps::AddressBookExport->new({
46             'glue_name' => 'Address Book',
47             'template_dir' => '/path/to/template_dir/',
48             'out_dir' => '/where/to/write/out/pages_dir/',
49             'skip_with_image' => 1,
50             });
51              
52             All options can be set here, rather than having to be
53             passed to each method, but equally options can be set
54             as each method is called (overwriting what is already in
55             the object). This constructor doesn't actually do anything
56             other than store the options submitted to it and return
57             the object.
58              
59             See the examples directory for template examples.
60              
61             =cut
62              
63             sub new {
64 1     1 1 18 my ($class,$self) = @_;
65            
66             # Looks like they didn't supply params
67 1 50 33     5 $self = {} unless $self && ref($self) eq 'HASH';
68 1         3 bless($self, $class);
69 1         3 return $self;
70             }
71              
72             =head2 export_address_book()
73              
74             $exporter->export_address_book({
75             'glue_name' => 'Address Book', # Default
76              
77             # Optional dump of vcard file
78             'out_file' => '/full/path/to/outfile.vcf',
79            
80             # Generate templates
81             'template_dir' => '/path/to/template_dir/',
82             'out_dir' => '/where/to/write/out/pages_dir/',
83             });
84              
85             The out_file will be version 3.0 of the vCard spec, you
86             can use Text::vCard::Addressbook to parse this.
87              
88             If 'template_dir' and 'out_dir' are specified then the
89             generate_web_pages() will be called after the address book
90             has been exported.
91              
92             =cut
93              
94             sub export_address_book {
95 0     0 1   my $self = shift;
96 0           $self->_pop_from_conf(shift);
97            
98 0 0         croak "You can not export unless running on a mac" unless $is_mac;
99            
100             # Get from address book
101 0           $self->get_vcards_from_addressbook();
102              
103 0 0         if(defined $self->{out_file}) {
104             # User wanted to write to disk
105 0 0         open(FH,'>' . $self->{out_file}) or carp "Could not print $!";
106 0           print FH $self->{vcards};
107 0           close(FH);
108             }
109             # See if they want to process locally
110 0 0 0       if(defined $self->{template_root} && defined $self->{out_dir}) {
111 0           $self->generate_web_pages();
112             }
113             }
114              
115             =head2 generate_web_pages()
116              
117             $exporter->generate_web_pages({
118             'template_root' => '/path/to/template/dir',
119             'out_dir' => '/path/results/printed/to',
120             'vcards' => $vcard_data,
121             });
122              
123             This method is called automatically from export_address_book()
124             if 'template_root' and 'out_dir' have been specified.
125              
126             The template_dir should contain 'index.html' and 'by_name.html'
127             template (see examples directory of this package for a starting point),
128             but it will check and just not process if they are missing.
129              
130             'vcards' will have already been populated if export_address_book()
131             has been called. Alternativly you can supply the vcard data which
132             is submitted to Text::vCard::Addressbook as source_text.
133              
134             =cut
135              
136             sub generate_web_pages {
137 0     0 1   my $self = shift;
138 0           $self->_pop_from_conf(shift);
139              
140 0 0         croak "'template_root' not a directory or does not exist" unless -d $self->{template_root};
141 0 0         croak "'out_dir' not a directory or does not exist" unless -d $self->{out_dir};
142 0 0         croak "No 'vcards' data defined" unless defined $self->{vcards};
143            
144 0           my %config = (
145             INCLUDE_PATH => $self->{template_root},
146             # Add cache options ?
147             );
148 0           my $tt = Template->new(\%config);
149            
150             # Get the Text::vCard object
151 0           $self->{address_book} = Text::vCard::Addressbook->new({
152             'source_text' => $self->{vcards},
153             });
154              
155             # Sort
156 0           my $vcards = $self->{address_book}->vcards;
157            
158 0 0         if(-r $self->{template_root} . '/index.html') {
159             # use TT for output
160 0           my %vals = (
161             'people' => $vcards,
162             'first_letter' => \&get_first_letter_from_vcard,
163             );
164 0           my $file;
165 0           my $template_response = $tt->process('index.html',\%vals, \$file);
166 0 0         carp $tt->error if $tt->error;
167 0           open(FH,'>' . $self->{out_dir} . '/index.html');
168 0           print FH $file;
169 0           close(FH);
170             }
171 0 0         if(-r $self->{template_root} . '/by_name.html') {
172             # Print each person
173 0           my $people_dir = $self->{out_dir} . '/people';
174 0 0         mkdir $people_dir unless -d $people_dir;
175            
176             # sort out who goes where
177 0           my %letters;
178 0           foreach my $vcard (@{$vcards}) {
  0            
179 0           my $letter = get_first_letter_from_vcard($vcard);
180 0 0         unless(defined $letters{$letter}) {
181 0           my @store;
182 0           $letters{$letter} = \@store;
183             }
184 0           push(@{$letters{$letter}}, $vcard);
  0            
185            
186             }
187              
188             # Generate the pages
189 0           while(my($letter,$vcards) = each %letters) {
190 0           my %vals = (
191             'people' => $vcards,
192             'letter' => $letter,
193             'letters' => \%letters,
194             'first_letter' => \&get_first_letter_from_vcard,
195             );
196              
197 0           my $file;
198 0           my $template_response = $tt->process('by_name.html',\%vals, \$file);
199 0 0         carp $tt->error() if $tt->error();
200            
201 0 0         open(FH,">$people_dir/$letter.html") or carp "Could not open file $people_dir/$letter.html $!";
202 0           print FH $file;
203 0           close(FH);
204             }
205             }
206             }
207              
208             =head2 get_vcards_from_addressbook()
209            
210             my $vcards = $exporter->get_vcards_from_addressbook({
211             'skip_with_image' => 1,
212             'glue_name' => 'Address Book',
213             });
214              
215             This is the method which calls Mac::Glue and extracts
216             the vcard information from Apples Address Book.
217              
218             'skip_with_image' is there so that and person with an
219             image associated can be skipped on the export. This is because
220             Text::vCard can not support it currently.
221              
222             The glue_name defaults to 'Address Book', but as you can
223             set it when you create your glue (see Mac::Glue) this option
224             allows you to overwrite the default.
225              
226             =cut
227              
228             sub get_vcards_from_addressbook {
229 0 0   0 1   croak "You can not export from Apple Address Book unless on a mac" unless $is_mac;
230 0           my $self = shift;
231 0           $self->_pop_from_conf(shift);
232            
233 0   0       my $glue_name = $self->{'glue_name'} || 'Address Book';
234              
235 0           my $address = new Mac::Glue $glue_name;
236            
237             # get everyone
238 0           my @people = $address->prop('people')->get;
239              
240 0           my $vcards;
241 0           for my $person (@people) {
242             # Loop and get each persons card
243             # Skip if they have a photo as Text::vFile::asData can't cope
244 0 0 0       next if $person->prop('image')->get && defined $self->{'skip_with_image'};
245 0           $vcards .= $person->prop('vcard')->get;
246             }
247              
248 0           $address->quit();
249              
250 0           return $self->{vcards} = $vcards;
251             }
252              
253             =head2 get_first_letter_from_vcard()
254              
255             This is a function, NOT a method.
256              
257             my $first_letter = get_first_letter_from_vcard($vcard,'fullname');
258              
259             This function can take two arguments. The First must be a Text::vCard
260             object. The second is optional (defaulting to 'fullname') which is
261             the method to call on the object. The first letter of the return
262             value from the method is then returned upper cased.
263              
264             This is used to sort which page each person is put on and in the
265             template to generate the links for each person and which letters
266             are active.
267            
268             =cut
269              
270             sub get_first_letter_from_vcard {
271 0     0 1   my $vcard = shift;
272 0 0         return '?' unless $vcard;
273            
274 0   0       my $method = shift || 'fullname';
275            
276 0   0       my $name = $vcard->$method() || 'x';
277 0           my $l = '';
278 0 0         $l = decode('utf-8',substr($name,0,1)) if $name ne '';
279 0 0         if($l eq '') {
280 0           $l = 'other';
281             }
282 0           return uc($l);
283             }
284              
285             # So they can use each method seperatly, or from previous configs
286             sub _pop_from_conf {
287 0     0     my $self = shift;
288            
289 0 0         if(my $conf = shift) {
290             # Take everything out of conf and stick in self
291 0           map { $self->{$_} = $conf->{$_} } keys %{$conf};
  0            
  0            
292             }
293             }
294              
295             =head1 NOTES
296              
297             One could extend this package to upload the vCard file to
298             a server and have that then generate webpages, but for now
299             that's an exercise for the user! I'm just going to rsync
300             my pages up. If anyone wants to extend this to inserting info
301             into a DB or something else please let me know.
302              
303             =head1 AUTHOR
304              
305             Leo Lapworth, LLAP@cuckoo.org
306              
307             =head1 COPYRIGHT
308              
309             Copyright (c) 2005 Leo Lapworth. All rights reserved.
310             This program is free software; you can redistribute
311             it and/or modify it under the same terms as Perl itself.
312              
313             =head1 SEE ALSO
314              
315             Mac::Glue Text::vCard Template
316              
317             =cut
318              
319             1;