package PPI::Cache; =pod =head1 NAME PPI::Cache - The PPI Document Caching Layer =head1 SYNOPSIS # Set the cache use PPI::Cache path => '/var/cache/ppi-cache'; # Manually create a cache my $Cache = PPI::Cache->new( path => '/var/cache/perl/class-PPI', readonly => 1, ); =head1 DESCRIPTION C provides the default caching functionality for L. It integrates automatically with L itself. Once enabled, any attempt to load a document from the filesystem will be cached via cache. Please note that creating a L from raw source or something other object will B be cached. =head2 Using PPI::Cache The most common way of using C is to provide parameters to the C statement at the beginning of your program. # Load the class but do not set a cache use PPI::Cache; # Use a fairly normal cache location use PPI::Cache path => '/var/cache/ppi-cache'; Any of the arguments that can be provided to the C constructor can also be provided to C. =head1 METHODS =cut use strict; use Carp (); use File::Spec (); use File::Path (); use Storable 2.17 (); use Digest::MD5 2.35 (); use Params::Util qw{_INSTANCE _SCALAR}; use PPI::Document (); our $VERSION = '1.278'; use constant VMS => !! ( $^O eq 'VMS' ); sub import { my $class = ref $_[0] ? ref shift : shift; return 1 unless @_; # Create a cache from the params provided my $cache = $class->new(@_); # Make PPI::Document use it unless ( PPI::Document->set_cache( $cache ) ) { Carp::croak("Failed to set cache in PPI::Document"); } 1; } ##################################################################### # Constructor and Accessors =pod =head2 new param => $value, ... The C constructor creates a new standalone cache object. It takes a number of parameters to control the cache. =over =item path The C param sets the base directory for the cache. It must already exist, and must be writable. =item readonly The C param is a true/false flag that allows the use of an existing cache by a less-privileged user (such as the web user). Existing documents will be retrieved from the cache, but new documents will not be written to it. =back Returns a new C object, or dies on error. =cut sub new { my $class = shift; my %params = @_; # Path should exist and be usable my $path = $params{path} or Carp::croak("Cannot create PPI::Cache, no path provided"); unless ( -d $path ) { Carp::croak("Cannot create PPI::Cache, path does not exist"); } unless ( -r $path and -x $path ) { Carp::croak("Cannot create PPI::Cache, no read permissions for path"); } if ( ! $params{readonly} and ! -w $path ) { Carp::croak("Cannot create PPI::Cache, no write permissions for path"); } # Create the basic object my $self = bless { path => $path, readonly => !! $params{readonly}, }, $class; $self; } =pod =head2 path The C accessor returns the path on the local filesystem that is the root of the cache. =cut sub path { $_[0]->{path} } =pod =head2 readonly The C accessor returns true if documents should not be written to the cache. =cut sub readonly { $_[0]->{readonly} } ##################################################################### # PPI::Cache Methods =pod =head2 get_document $md5sum | \$source The C method checks to see if a Document is stored in the cache and retrieves it if so. =cut sub get_document { my $self = ref $_[0] ? shift : Carp::croak('PPI::Cache::get_document called as static method'); my $md5hex = $self->_md5hex(shift) or return undef; $self->_load($md5hex); } =pod =head2 store_document $Document The C method takes a L as argument and explicitly adds it to the cache. Returns true if saved, or C (or dies) on error. FIXME (make this return either one or the other, not both) =cut sub store_document { my $self = shift; my $Document = _INSTANCE(shift, 'PPI::Document') or return undef; # Shortcut if we are readonly return 1 if $self->readonly; # Find the filename to save to my $md5hex = $Document->hex_id or return undef; # Store the file $self->_store( $md5hex, $Document ); } ##################################################################### # Support Methods # Store an arbitrary PPI::Document object (using Storable) to a particular # path within the cache filesystem. sub _store { my ($self, $md5hex, $object) = @_; my ($dir, $file) = $self->_paths($md5hex); # Save the file File::Path::mkpath( $dir, 0, 0755 ) unless -d $dir; if ( VMS ) { Storable::lock_nstore( $object, $file ); } else { Storable::nstore( $object, $file ); } } # Load an arbitrary object (using Storable) from a particular # path within the cache filesystem. sub _load { my ($self, $md5hex) = @_; my (undef, $file) = $self->_paths($md5hex); # Load the file return '' unless -f $file; my $object = VMS ? Storable::retrieve( $file ) : Storable::lock_retrieve( $file ); # Security check unless ( _INSTANCE($object, 'PPI::Document') ) { Carp::croak("Security Violation: Object in '$file' is not a PPI::Document"); } $object; } # Convert a md5 to a dir and file name sub _paths { my $self = shift; my $md5hex = lc shift; my $dir = File::Spec->catdir( $self->path, substr($md5hex, 0, 1), substr($md5hex, 0, 2) ); my $file = File::Spec->catfile( $dir, $md5hex . '.ppi' ); return ($dir, $file); } # Check a md5hex param sub _md5hex { my $either = shift; my $it = _SCALAR($_[0]) ? PPI::Util::md5hex(${$_[0]}) : $_[0]; return (defined $it and ! ref $it and $it =~ /^[[:xdigit:]]{32}\z/s) ? lc $it : undef; } 1; =pod =head1 TO DO - Finish the basic functionality - Add support for use PPI::Cache auto-setting $PPI::Document::CACHE =head1 SUPPORT See the L in the main module. =head1 AUTHOR Adam Kennedy Eadamk@cpan.orgE =head1 COPYRIGHT Copyright 2005 - 2011 Adam Kennedy. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The full text of the license can be found in the LICENSE file included with this module. =cut