1package App::Info::Lib::Expat;
2
3=head1 NAME
4
5App::Info::Lib::Expat - Information about the Expat XML parser
6
7=head1 SYNOPSIS
8
9  use App::Info::Lib::Expat;
10
11  my $expat = App::Info::Lib::Expat->new;
12
13  if ($expat->installed) {
14      print "App name: ", $expat->name, "\n";
15      print "Version:  ", $expat->version, "\n";
16      print "Bin dir:  ", $expat->bin_dir, "\n";
17  } else {
18      print "Expat is not installed. :-(\n";
19  }
20
21=head1 DESCRIPTION
22
23App::Info::Lib::Expat supplies information about the Expat XML parser
24installed on the local system. It implements all of the methods defined by
25App::Info::Lib. Methods that trigger events will trigger them only the first
26time they're called (See L<App::Info|App::Info> for documentation on handling
27events). To start over (after, say, someone has installed Expat) construct a
28new App::Info::Lib::Expat object to aggregate new meta data.
29
30Some of the methods trigger the same events. This is due to cross-calling of
31shared subroutines. However, any one event should be triggered no more than
32once. For example, although the info event "Searching for 'expat.h'" is
33documented for the methods C<version()>, C<major_version()>,
34C<minor_version()>, and C<patch_version()>, rest assured that it will only be
35triggered once, by whichever of those four methods is called first.
36
37=cut
38
39use strict;
40use App::Info::Util;
41use App::Info::Lib;
42use vars qw(@ISA $VERSION);
43@ISA = qw(App::Info::Lib);
44$VERSION = '0.57';
45
46my $u = App::Info::Util->new;
47
48##############################################################################
49
50=head1 INTERFACE
51
52=head2 Constructor
53
54=head3 new
55
56  my $expat = App::Info::Lib::Expat->new(@params);
57
58Returns an App::Info::Lib::Expat object. See L<App::Info|App::Info> for a
59complete description of argument parameters.
60
61When called, C<new()> searches all of the paths returned by the
62C<search_lib_dirs()> method for one of the files returned by the
63C<search_lib_names()> method. If any of is found, then Expat is assumed to be
64installed. Otherwise, most of the object methods will return C<undef>.
65
66B<Events:>
67
68=over 4
69
70=item info
71
72Searching for Expat libraries
73
74=item confirm
75
76Path to Expat library directory?
77
78=item unknown
79
80Path to Expat library directory?
81
82=back
83
84=cut
85
86sub new {
87    # Construct the object.
88    my $self = shift->SUPER::new(@_);
89    # Find libexpat.
90    $self->info("Searching for Expat libraries");
91
92    my @libs = $self->search_lib_names;
93    my $cb = sub { $u->first_cat_dir(\@libs, $_) };
94    if (my $lexpat = $u->first_cat_dir(\@libs, $self->search_lib_dirs)) {
95        # We found libexpat. Confirm.
96        $self->{libexpat} =
97          $self->confirm( key      => 'expat lib dir',
98                          prompt   => 'Path to Expat library directory?',
99                          value    => $lexpat,
100                          callback => $cb,
101                          error    => 'No Expat libraries found in directory');
102    } else {
103        # Handle an unknown value.
104        $self->{libexpat} =
105          $self->unknown( key      => 'expat lib dir',
106                          prompt   => 'Path to Expat library directory?',
107                          callback => $cb,
108                          error    => 'No Expat libraries found in directory');
109    }
110
111    return $self;
112}
113
114##############################################################################
115
116=head2 Class Method
117
118=head3 key_name
119
120  my $key_name = App::Info::Lib::Expat->key_name;
121
122Returns the unique key name that describes this class. The value returned is
123the string "Expat".
124
125=cut
126
127sub key_name { 'Expat' }
128
129##############################################################################
130
131=head2 Object Methods
132
133=head3 installed
134
135  print "Expat is ", ($expat->installed ? '' : 'not '),
136    "installed.\n";
137
138Returns true if Expat is installed, and false if it is not.
139App::Info::Lib::Expat determines whether Expat is installed based on the
140presence or absence on the file system of one of the files searched for when
141C<new()> constructed the object. If Expat does not appear to be installed,
142then most of the other object methods will return empty values.
143
144=cut
145
146sub installed { $_[0]->{libexpat} ? 1 : undef }
147
148##############################################################################
149
150=head3 name
151
152  my $name = $expat->name;
153
154Returns the name of the application. In this case, C<name()> simply returns
155the string "Expat".
156
157=cut
158
159sub name { 'Expat' }
160
161##############################################################################
162
163=head3 version
164
165Returns the full version number for Expat. App::Info::Lib::Expat attempts
166parse the version number from the F<expat.h> file, if it exists.
167
168B<Events:>
169
170=over 4
171
172=item info
173
174Searching for 'expat.h'
175
176Searching for include directory
177
178=item error
179
180Cannot find include directory
181
182Cannot find 'expat.h'
183
184Failed to parse version from 'expat.h'
185
186=item unknown
187
188Enter a valid Expat include directory
189
190Enter a valid Expat version number
191
192=back
193
194=cut
195
196my $get_version = sub {
197    my $self = shift;
198    $self->{version} = undef;
199    $self->info("Searching for 'expat.h'");
200    my $inc = $self->inc_dir
201      or ($self->error("Cannot find 'expat.h'")) && return;
202    my $header = $u->catfile($inc, 'expat.h');
203    my @regexen = ( qr/XML_MAJOR_VERSION\s+(\d+)$/,
204                    qr/XML_MINOR_VERSION\s+(\d+)$/,
205                    qr/XML_MICRO_VERSION\s+(\d+)$/ );
206
207    my ($x, $y, $z) = $u->multi_search_file($header, @regexen);
208    if (defined $x and defined $y and defined $z) {
209        # Assemble the version number and store it.
210        my $v = "$x.$y.$z";
211        @{$self}{qw(version major minor patch)} = ($v, $x, $y, $z);
212    } else {
213        # Warn them if we couldn't get them all.
214        $self->error("Failed to parse version from '$header'");
215    }
216};
217
218sub version {
219    my $self = shift;
220    return unless $self->{libexpat};
221
222    # Get data.
223    $get_version->($self) unless exists $self->{version};
224
225    # Handle an unknown value.
226    unless ($self->{version}) {
227        # Create a validation code reference.
228        my $chk_version = sub {
229            # Try to get the version number parts.
230            my ($x, $y, $z) = /^(\d+)\.(\d+).(\d+)$/;
231            # Return false if we didn't get all three.
232            return unless $x and defined $y and defined $z;
233            # Save all three parts.
234            @{$self}{qw(major minor patch)} = ($x, $y, $z);
235            # Return true.
236            return 1;
237        };
238        $self->{version} = $self->unknown( key      => 'expat version number',
239                                           callback => $chk_version);
240    }
241    return $self->{version};
242}
243
244##############################################################################
245
246=head3 major_version
247
248  my $major_version = $expat->major_version;
249
250Returns the Expat major version number. App::Info::Lib::Expat attempts to
251parse the version number from the F<expat.h> file, if it exists. For example,
252if C<version()> returns "1.95.2", then this method returns "1".
253
254B<Events:>
255
256=over 4
257
258=item info
259
260Searching for 'expat.h'
261
262Searching for include directory
263
264=item error
265
266Cannot find include directory
267
268Cannot find 'expat.h'
269
270Failed to parse version from 'expat.h'
271
272=item unknown
273
274Enter a valid Expat include directory
275
276Enter a valid Expat major version number
277
278=back
279
280=cut
281
282# This code reference is used by major_version(), minor_version(), and
283# patch_version() to validate a version number entered by a user.
284my $is_int = sub { /^\d+$/ };
285
286sub major_version {
287    my $self = shift;
288    return unless $self->{libexpat};
289
290    # Get data.
291    $get_version->($self) unless exists $self->{version};
292
293    # Handle an unknown value.
294    $self->{major} = $self->unknown( key      => 'expat major version number',
295                                     callback => $is_int)
296      unless $self->{major};
297
298    return $self->{major};
299}
300
301##############################################################################
302
303=head3 minor_version
304
305  my $minor_version = $expat->minor_version;
306
307Returns the Expat minor version number. App::Info::Lib::Expat attempts to
308parse the version number from the F<expat.h> file, if it exists. For example,
309if C<version()> returns "1.95.2", then this method returns "95".
310
311B<Events:>
312
313=over 4
314
315=item info
316
317Searching for 'expat.h'
318
319Searching for include directory
320
321=item error
322
323Cannot find include directory
324
325Cannot find 'expat.h'
326
327Failed to parse version from 'expat.h'
328
329=item unknown
330
331Enter a valid Expat include directory
332
333Enter a valid Expat minor version number
334
335=back
336
337=cut
338
339sub minor_version {
340    my $self = shift;
341    return unless $self->{libexpat};
342
343    # Get data.
344    $get_version->($self) unless exists $self->{version};
345
346    # Handle an unknown value.
347    $self->{minor} = $self->unknown( key       =>'expat minor version number',
348                                     callback  => $is_int)
349      unless $self->{minor};
350
351    return $self->{minor};
352}
353
354##############################################################################
355
356=head3 patch_version
357
358  my $patch_version = $expat->patch_version;
359
360Returns the Expat patch version number. App::Info::Lib::Expat attempts to
361parse the version number from the F<expat.h> file, if it exists. For example,
362C<version()> returns "1.95.2", then this method returns "2".
363
364B<Events:>
365
366=over 4
367
368=item info
369
370Searching for 'expat.h'
371
372Searching for include directory
373
374=item error
375
376Cannot find include directory
377
378Cannot find 'expat.h'
379
380Failed to parse version from 'expat.h'
381
382=item unknown
383
384Enter a valid Expat include directory
385
386Enter a valid Expat patch version number
387
388=back
389
390=cut
391
392sub patch_version {
393    my $self = shift;
394    return unless $self->{libexpat};
395
396    # Get data.
397    $get_version->($self) unless exists $self->{version};
398
399    # Handle an unknown value.
400    $self->{patch} = $self->unknown( key      => 'expat patch version number',
401                                     callback => $is_int)
402      unless $self->{patch};
403
404    return $self->{patch};
405}
406
407##############################################################################
408
409=head3 bin_dir
410
411  my $bin_dir = $expat->bin_dir;
412
413Since Expat includes no binaries, this method always returns false.
414
415=cut
416
417sub bin_dir { return }
418
419##############################################################################
420
421=head3 executable
422
423  my $executable = $expat->executable;
424
425Since Expat includes no executable program, this method always returns false.
426
427=cut
428
429sub executable { return }
430
431##############################################################################
432
433=head3 inc_dir
434
435  my $inc_dir = $expat->inc_dir;
436
437Returns the directory path in which the file F<expat.h> was found.
438App::Info::Lib::Expat searches for F<expat.h> in the following directories:
439
440=over 4
441
442=item /usr/local/include
443
444=item /usr/include
445
446=item /sw/include
447
448=back
449
450B<Events:>
451
452=over 4
453
454=item info
455
456Searching for include directory
457
458=item error
459
460Cannot find include directory
461
462=item unknown
463
464Enter a valid Expat include directory
465
466=back
467
468=cut
469
470# This code reference is used by inc_dir() and so_lib_dir() to validate a
471# directory entered by the user.
472my $is_dir = sub { -d };
473
474sub inc_dir {
475    my $self = shift;
476    return unless $self->{libexpat};
477    unless (exists $self->{inc_dir}) {
478        $self->info("Searching for include directory");
479        my @incs = $self->search_inc_names;
480
481        if (my $dir = $u->first_cat_dir(\@incs, $self->search_inc_dirs)) {
482            $self->{inc_dir} = $dir;
483        } else {
484            $self->error("Cannot find include directory");
485            my $cb = sub { $u->first_cat_dir(\@incs, $_) };
486            $self->{inc_dir} =
487              $self->unknown( key      => 'explat inc dir',
488                              callback => $cb,
489                              error    => "No expat include file found in " .
490                                          "directory");
491        }
492    }
493    return $self->{inc_dir};
494}
495
496##############################################################################
497
498=head3 lib_dir
499
500  my $lib_dir = $expat->lib_dir;
501
502Returns the directory path in which a Expat library was found. The files and
503paths searched are as described for the L<"new"|new> constructor, as are
504the events.
505
506=cut
507
508sub lib_dir { $_[0]->{libexpat} }
509
510##############################################################################
511
512=head3 so_lib_dir
513
514  my $so_lib_dir = $expat->so_lib_dir;
515
516Returns the directory path in which a Expat shared object library was found.
517It searches all of the paths in the C<libsdirs> and C<loclibpth> attributes
518defined by the Perl L<Config|Config> module -- plus F</sw/lib> (for all you
519Fink fans) -- for one of the following files:
520
521=over
522
523=item libexpat.so
524
525=item libexpat.so.0
526
527=item libexpat.so.0.0.1
528
529=item libexpat.dylib
530
531=item libexpat.0.dylib
532
533=item libexpat.0.0.1.dylib
534
535=back
536
537B<Events:>
538
539=over 4
540
541=item info
542
543Searching for shared object library directory
544
545=item error
546
547Cannot find shared object library directory
548
549=item unknown
550
551Enter a valid Expat shared object library directory
552
553=back
554
555=cut
556
557sub so_lib_dir {
558    my $self = shift;
559    return unless $self->{libexpat};
560    unless (exists $self->{so_lib_dir}) {
561        $self->info("Searching for shared object library directory");
562
563    my @libs = $self->search_so_lib_names;
564    my $cb = sub { $u->first_cat_dir(\@libs, $_) };
565        if (my $dir = $u->first_cat_dir(\@libs, $self->search_lib_dirs)) {
566            $self->{so_lib_dir} = $dir;
567        } else {
568            $self->error("Cannot find shared object library directory");
569            $self->{so_lib_dir} =
570              $self->unknown( key      => 'expat so dir',
571                              callback => $cb,
572                              error    => "Shared object libraries not " .
573                                          "found in directory");
574        }
575    }
576    return $self->{so_lib_dir};
577}
578
579=head3 home_url
580
581  my $home_url = $expat->home_url;
582
583Returns the libexpat home page URL.
584
585=cut
586
587sub home_url { 'http://expat.sourceforge.net/' }
588
589=head3 download_url
590
591  my $download_url = $expat->download_url;
592
593Returns the libexpat download URL.
594
595=cut
596
597sub download_url { 'http://sourceforge.net/projects/expat/' }
598
599##############################################################################
600
601=head3 search_lib_names
602
603  my @seach_lib_names = $self->search_lib_nams
604
605Returns a list of possible names for library files. Used by C<lib_dir()> to
606search for library files. By default, the list is:
607
608=over
609
610=item libexpat.a
611
612=item libexpat.la
613
614=item libexpat.so
615
616=item libexpat.so.0
617
618=item libexpat.so.0.0.1
619
620=item libexpat.dylib
621
622=item libexpat.0.dylib
623
624=item libexpat.0.0.1.dylib
625
626=back
627
628=cut
629
630sub search_lib_names {
631    my $self = shift;
632    return $self->SUPER::search_lib_names,
633      map { "libexpat.$_"} qw(a la so so.0 so.0.0.1 dylib 0.dylib 0.0.1.dylib);
634}
635
636##############################################################################
637
638=head3 search_so_lib_names
639
640  my @seach_so_lib_names = $self->search_so_lib_nams
641
642Returns a list of possible names for shared object library files. Used by
643C<so_lib_dir()> to search for library files. By default, the list is:
644
645=over
646
647=item libexpat.so
648
649=item libexpat.so.0
650
651=item libexpat.so.0.0.1
652
653=item libexpat.dylib
654
655=item libexpat.0.dylib
656
657=item libexpat.0.0.1.dylib
658
659=back
660
661=cut
662
663sub search_so_lib_names {
664    my $self = shift;
665    return $self->SUPER::search_so_lib_names,
666      map { "libexpat.$_"} qw(so so.0 so.0.0.1 dylib 0.dylib 0.0.1.dylib);
667}
668
669##############################################################################
670
671=head3 search_lib_dirs
672
673  my @search_lib_dirs = $expat->search_lib_dirs;
674
675Returns a list of possible directories in which to search for libraries. By
676default, it returns all of the paths in the C<libsdirs> and C<loclibpth>
677attributes defined by the Perl L<Config|Config> module -- plus F</sw/lib> (in
678support of all you Fink users out there).
679
680=cut
681
682sub search_lib_dirs { shift->SUPER::search_lib_dirs, $u->lib_dirs, '/sw/lib' }
683
684##############################################################################
685
686=head3 search_inc_names
687
688  my @search_inc_names = $expat->search_inc_names;
689
690Returns a list of include file names to search for. Used by C<inc_dir()> to
691search for an include file. By default, the only name returned is F<expat.h>.
692
693=cut
694
695sub search_inc_names {
696    my $self = shift;
697    return $self->SUPER::search_inc_names, "expat.h";
698}
699
700##############################################################################
701
702=head3 search_inc_dirs
703
704  my @search_inc_dirs = $expat->search_inc_dirs;
705
706Returns a list of possible directories in which to search for include files.
707Used by C<inc_dir()> to search for an include file. By default, the
708directories are:
709
710=over 4
711
712=item /usr/local/include
713
714=item /usr/include
715
716=item /sw/include
717
718=back
719
720=cut
721
722sub search_inc_dirs {
723    shift->SUPER::search_inc_dirs,
724      qw(/usr/local/include
725         /usr/include
726         /sw/include);
727}
728
7291;
730__END__
731
732=head1 KNOWN ISSUES
733
734This is a pretty simple class. It's possible that there are more directories
735that ought to be searched for libraries and includes. And if anyone knows
736how to get the version numbers, let me know!
737
738The format of the version number seems to have changed recently (1.95.1-2),
739and now I don't know where to find the version number. Patches welcome.
740
741=head1 SUPPORT
742
743This module is stored in an open L<GitHub
744repository|http://github.com/theory/app-info/>. Feel free to fork and
745contribute!
746
747Please file bug reports via L<GitHub
748Issues|http://github.com/theory/app-info/issues/> or by sending mail to
749L<bug-App-Info@rt.cpan.org|mailto:bug-App-Info@rt.cpan.org>.
750
751=head1 AUTHOR
752
753David E. Wheeler <david@justatheory.com> based on code by Sam Tregar
754<sam@tregar.com> that Sam, in turn, borrowed from Clark Cooper's
755L<XML::Parser|XML::Parser> module.
756
757=head1 SEE ALSO
758
759L<App::Info|App::Info> documents the event handling interface.
760
761L<App::Info::Lib|App::Info::Lib> is the App::Info::Lib::Expat parent class.
762
763L<XML::Parser|XML::Parser> uses Expat to parse XML.
764
765L<Config|Config> provides Perl configure-time information used by
766App::Info::Lib::Expat to locate Expat libraries and files.
767
768L<http://expat.sourceforge.net/> is the Expat home page.
769
770=head1 COPYRIGHT AND LICENSE
771
772Copyright (c) 2002-2011, David E. Wheeler. Some Rights Reserved.
773
774This module is free software; you can redistribute it and/or modify it under the
775same terms as Perl itself.
776
777=cut
778