File Coverage

blib/lib/App/Addex/AddressBook/AppleScript.pm
Criterion Covered Total %
statement 24 105 22.8
branch 0 36 0.0
condition 0 18 0.0
subroutine 8 14 57.1
pod 1 1 100.0
total 33 174 18.9


line stmt bran cond sub pod time code
1 1     1   20796 use 5.10.1;
  1         5  
  1         40  
2 1     1   5 use strict;
  1         1  
  1         32  
3 1     1   3 use warnings;
  1         2  
  1         51  
4             package App::Addex::AddressBook::AppleScript;
5             {
6             $App::Addex::AddressBook::AppleScript::VERSION = '0.007';
7             }
8 1     1   5 use base qw(App::Addex::AddressBook);
  1         1  
  1         859  
9             # ABSTRACT: Mac::Glue-less Addex adapter for Apple Address Book and Addex
10              
11              
12 1     1   25498 use App::Addex::Entry;
  1         4  
  1         19  
13 1     1   962 use App::Addex::Entry::EmailAddress;
  1         400  
  1         25  
14 1     1   1023 use Encode ();
  1         13203  
  1         27  
15              
16 1     1   1190 use File::Temp ();
  1         29911  
  1         1103  
17              
18             sub _produce_applescript {
19 0     0     my @fields = (
20             'company', # true / false
21             'organization',
22             'first name',
23             'middle name',
24             'last name',
25             'nickname',
26             'suffix',
27             'note',
28             );
29              
30 0           my $dumper = '';
31 0           for my $field (@fields) {
32 0           $dumper .= <<"END_FIELD_DUMPER";
33             set _this to get $field of _person
34             if $field of _person is not missing value then
35             set _answer to _answer & "- BEGIN $field\n"
36             set _answer to _answer & ($field of _person) & "\n"
37             set _answer to _answer & "- END $field\n"
38             end if
39             END_FIELD_DUMPER
40             }
41              
42 0           my $osascript = <<'END_APPLESCRIPT';
43             tell application "Address Book"
44             set _people to (get people)
45              
46             set _answer to ""
47              
48             repeat with _person in _people
49             repeat 1 times
50             if count of email of _person = 0 then
51             exit repeat
52             end if
53              
54             set _answer to _answer & "--- BEGIN " & id of _person & "\n"
55              
56             $dumper
57              
58             set _answer to _answer & "- BEGIN email\n"
59             repeat with _email in (get email of _person)
60             set _answer to _answer & (label of _email) & "\n"
61             set _answer to _answer & (value of _email) & "\n"
62             end repeat
63             set _answer to _answer & "- END email\n"
64              
65             set _answer to _answer & "--- END " & id of _person & "\n\n"
66             end repeat
67             end repeat
68              
69             _answer
70             end tell
71             END_APPLESCRIPT
72              
73 0           $osascript =~ s/\$dumper/$dumper/;
74              
75 0           return $osascript;
76             }
77              
78             sub _produce_scriptfile {
79 0     0     my ($self) = @_;
80              
81 0           my $osascript = $self->_produce_applescript;
82              
83 0           my ($fh, $filename) = File::Temp::tempfile(UNLINK => 1);
84              
85 0           $fh->print($osascript);
86 0 0         $fh->close or die "can't close $filename: $!";
87              
88 0           return $filename;
89             }
90              
91             sub entries {
92 0     0 1   my ($self) = @_;
93              
94 0           my $script = $self->_produce_scriptfile;
95 0           my @output = `/usr/bin/osascript $script`;
96              
97 0           @output = map {; Encode::decode('utf-8', $_) } @output;
  0            
98              
99 0           my @people;
100             my $this;
101 0           LINE: while (my $line = shift @output) {
102 0 0         unless ($this) {
103 0 0         next LINE unless $line =~ /\A--- BEGIN (.+)\Z/;
104 0           $this = { id => $1 };
105 0           push @people, $this;
106 0           next LINE;
107             }
108              
109 0           my @input;
110 0 0         if ($line =~ /\A- BEGIN (.+)\Z/) {
111 0           my $field = $1;
112 0   0       push @input, shift @output until @input and $input[-1] =~ /\A- END $1\Z/;
113 0           pop @input;
114 0           $this->{ $field } = join q{}, @input;
115 0           chomp $this->{ $field };
116              
117 0 0         if ($field eq 'email') {
118 0           $this->{emails} = [ split /\n/, delete $this->{email} ];
119             }
120             }
121              
122 0 0         if ($line =~ /\A--- END \Q$this->{id}\E\Z/) {
123 0           undef $this;
124 0           next LINE;
125             }
126             }
127              
128 0           my @entries = map {; $self->_entrify($_) } @people;
  0            
129              
130 0           return @entries;
131             }
132              
133             sub _entrify {
134 0     0     my ($self, $person) = @_;
135              
136 0           my %fields;
137 0 0 0       if (my $note = $person->{note} // '') {
138 0           my @lines = grep { length } split /\R/, $note;
  0            
139 0           for my $line (@lines) {
140 0 0         warn("bogus line in notes: $line\n"), next
141             unless $line =~ /\A([^:]+):\s*(.+?)\Z/;
142 0           $fields{$1} = $2;
143             }
144             }
145              
146 0   0       my $fname = $person->{'first name'} // '';
147 0   0       my $mname = $person->{'middle name'} // '';
148 0   0       my $lname = $person->{'last name'} // '';
149 0   0       my $suffix = $person->{suffix} // '';
150              
151 0 0 0       $mname = '' unless $fields{'use middle'} // 1;
152              
153 0           my $name;
154 0 0         if ($person->{company} eq 'true') {
155 0           $name = $person->{organization};
156             } else {
157 0 0         $name = $fname
    0          
    0          
158             . (length $mname ? " $mname" : '')
159             . (length $lname ? " $lname" : '')
160             . (length $suffix ? " $suffix" : '');
161             }
162              
163 0 0         unless (length $name) {
164 0           warn "couldn't figure out a name for this entry\n";
165 0           return;
166             }
167              
168 0           my @emails;
169 0           my @kv = @{ $person->{emails} };
  0            
170              
171 0           for (my $i = 0; $i < $#kv; $i += 2) {
172 0           push @emails, App::Addex::Entry::EmailAddress->new({
173             address => $kv[ $i + 1 ],
174             label => $kv[ $i ],
175             });
176             }
177              
178             CHECK_DEFAULT: {
179 0 0 0       if (@emails > 1 and my $default = $fields{default_email}) {
  0            
180 0           my $check;
181 0 0         if ($default =~ m{\A/(.+)/\z}) {
182 0           $default = qr/$1/;
183 0     0     $check = sub { $_[0]->address =~ $default };
  0            
184             } else {
185 0     0     $check = sub { $_[0]->label eq $default };
  0            
186             }
187              
188 0           for my $i (0 .. $#emails) {
189 0 0         if ($check->($emails[$i])) {
190 0 0         unshift @emails, splice @emails, $i, 1 if $i != 0;
191 0           last CHECK_DEFAULT;
192             }
193             }
194              
195 0           warn "no email found for $name matching $fields{default_email}\n";
196             }
197             }
198              
199 0           my $arg = {
200             name => $name,
201             nick => $person->{nickname},
202             emails => \@emails,
203             fields => \%fields,
204             };
205              
206 0           return App::Addex::Entry->new($arg);
207             }
208              
209             1;
210              
211             __END__
212              
213             =pod
214              
215             =head1 NAME
216              
217             App::Addex::AddressBook::AppleScript - Mac::Glue-less Addex adapter for Apple Address Book and Addex
218              
219             =head1 VERSION
220              
221             version 0.007
222              
223             =head1 SYNOPSIS
224              
225             This module implements the L<App::Addex::AddressBook> interface for Mac OS X's
226             Address Book application, using I<a horrible hack> to get entries from the
227             address book.
228              
229             A much cleaner interface would be to use L<App::Addex::AddressBook::Apple>,
230             which uses L<Mac::Glue> to access the address book. Unfortunately, Mac::Glue
231             does not work in many recent builds of Perl, and will cease to work as the
232             Carbon API is killed off.
233              
234             The AppleScript adapter builds an AppleScript program that prints out a dump of
235             relevant address book entries, then runs it, then parses its output. The
236             format of the intermediate form may change for all kinds of crazy reasons.
237              
238             =head1 AUTHOR
239              
240             Ricardo Signes <rjbs@cpan.org>
241              
242             =head1 COPYRIGHT AND LICENSE
243              
244             This software is copyright (c) 2010 by Ricardo Signes.
245              
246             This is free software; you can redistribute it and/or modify it under
247             the same terms as the Perl 5 programming language system itself.
248              
249             =cut