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