The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
##
#
#    Copyright 2005-2006, Brian Szymanski
#
#    This file is part of Cache::Static
#
#    Cache::Static is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    For more information about Cache::Static, point a web browser at
#    http://chronicle.allafrica.com/scache/ or read the
#    documentation included with the Cache::Static distribution in the
#    doc/ directory
#
##

package Cache::Static::HTML_Mason_Util::hmc;

use strict;
use warnings;

use vars qw ( @ISA );

use HTML::Mason;
use HTML::Mason::Compiler;
use HTML::Mason::Compiler::ToObject;
use Cache::Static;

@ISA = qw(HTML::Mason::Compiler::ToObject);

my %last_component_deps;
my ($r, $m, $wr);
my $parent_dir = undef;

sub find_extra_deps {
	my ($self, $subcomponent, %args) = @_;

	my $top_call = defined($args{top_call}) ? $args{top_call} : 1;
	$m = $args{m};
	$r = $args{r};
	$wr = $r->document_root;

	Cache::Static::_log(3, "called find_extra_deps: $subcomponent (top: $top_call)\n");

	my @ret = ();
	#cache Mason compiles we know if none of our depends changed we don't
	#need to do any compilation (however, if any of them changed, we may
	#still need to do compilation - currently we just recompile ALL -
	#this will be improved later)
	my $str_ret = undef;
	my $sc_key = ($top_call ? Cache::Static::make_key($subcomponent) : undef);
	if($top_call) {
		my $hmc_deps = Cache::Static::get_if_same($sc_key, [],
			namespace => "_Cache_Static_hmc");
		if(defined($hmc_deps)) {
			my @hmc_deps = @{Storable::thaw $hmc_deps};
			($str_ret, undef) = Cache::Static::_is_same($sc_key,
				\@hmc_deps, namespace => "_Cache_Static_hmc");
			@ret = @hmc_deps;
		} 
	}
	if(defined($str_ret)) {
		Cache::Static::_log(3, "got cached depend list for $subcomponent");
	} else {
		open(F, $subcomponent) || die "can't open $subcomponent";
		my $code = join("\n", <F>) || die "can't read $subcomponent";
		close(F) || die "can't close $subcomponent";
		my $cr = $self->compile(
			top_call => $top_call,
			comp_source => $code,
			name => "something or other (fill me in!)",
			cache_static_friendly_key => "$subcomponent" );
		die "couldn't compile $subcomponent: $cr" unless(defined($cr));

		if($top_call) {
			my %processed_deps = ();
			while((scalar keys %processed_deps) !=
					(scalar keys %last_component_deps)) {
				my @deps = keys %last_component_deps;
				Cache::Static::_log(4, "deps: @deps");
				Cache::Static::_log(4, "proc'd deps: ".
					join(" ", keys %processed_deps));
				foreach my $dep (@deps) {
					next if $processed_deps{$dep};
					unless($dep =~ /^file\|/) {
						$processed_deps{$dep} = 1;
						next;
					}
					Cache::Static::_log(4, "got new mason file dep: $dep");
					$processed_deps{$dep} = 1;
					$dep = _pathify($dep, 0);
					Cache::Static::_log(3, "recursing on dep: $dep\n");
					#note this has a side effect of modifying %last_component_deps
					$self->find_extra_deps($dep, %args, top_call => 0);
				}
			}
		}
		@ret = map { _pathify($_, 1) } keys %last_component_deps;
		Cache::Static::_log(4, "ret: @ret");
		#we are caching the dependencies of this mason file
		#e.g. @ret has a list of file dependencies
		if($top_call) {
			Cache::Static::set($sc_key, Storable::freeze(\@ret),
				[], namespace => "_Cache_Static_hmc");
		}
	}

	return \@ret;
}

sub _pathify {
	my $file = shift;
	my $leave_spec = shift || 0;
	if($leave_spec) {
		$file =~ s/^file\|/file\|$wr\//;
	} else {
		$file =~ s/^file\|/$wr\//;
	}
	#strip out ../, ./
	$file =~ s/\/[^\/]+\/\.\.\//\//g;
	$file =~ s/\.\///g;
	#strip redundant slashes
	$file =~ s/\/\/+/\//g;
	return $file;
}

sub _Cache_Static_component_call {
	my $self = shift;
	my $has_content = shift;

	#args should be 'call' => $component
	my $k = shift;
	die "Cache::Static - incompatible version of HTML::Mason" unless($k eq 'call');
	my $component = shift;

	my $curr_dir = $parent_dir || $m->current_comp->dir_path;
	Cache::Static::_log(4, "component: $component, curr_dir: $curr_dir",
		", parent_dir: $parent_dir, dir_path: ", $m->current_comp->dir_path);

	#don't worry about components with embedded content for now (<|& foo &>...</&>)
	unless($has_content) {
		$component =~ s/^\s+//;
		# (from HTML::Mason::Devel) - To eliminate the need for quotes in
		# most cases, Mason employs some magic parsing: If the first character is
		# one of "[\w/_.]", comp_path is assumed to be a literal string running
		# up to the first comma or &>. Otherwise, comp_path is evaluated as an
		# expression.
		if($component =~ /^['"]?[\w\/_.]/) {
			#support for "component" & 'component'
			if($component =~ /^[']/) {
				$component =~ s/^[']//;
				$component =~ s/['].*//;
			} elsif($component =~ /^["]/) {
				$component =~ s/^["]//;
				$component =~ s/["].*//;
			}
			#strip off everything after a comma
			$component =~ s/,.*$//s;
			#strip off any trailing whitespace
			$component =~ s/\s+$//s;
			#now we've got the component, add a file dep
			if($component) {
				my $component_path = 
					($component =~ /^\//) ?
						$component :
						$curr_dir.'/'.$component;
				$component_path =~ s/\/\/+/\//g;
				Cache::Static::_log(4, "HTML_Mason_Util::hmc added component dep: file|$component_path");
				$last_component_deps{"file|$component_path"} = 1;
			}
		} else {
			Cache::Static::_log(3, "HTML_Mason_Util::hmc added MISS dep for dynamic component: $component");
			$last_component_deps{MISS} = 1;
		}
	} else {
		Cache::Static::_log(3, "HTML_Mason_Util::hmc added MISS dep for component $component with content");
		$last_component_deps{MISS} = 1;
	}

	if($has_content) {
		return $self->SUPER::component_content_call(@_);
	} else {
		return $self->SUPER::component_call(@_);
	}
}

sub compile {
	my ($self, %args) = @_;
	my $fkey = $args{cache_static_friendly_key};
	delete $args{cache_static_friendly_key};
	$parent_dir = $fkey;
	$parent_dir =~ s/\/[^\/]*$//; #just the dir
	$parent_dir =~ s/^$wr//;      #strip off webroot
	Cache::Static::_log(4, "compiling: $fkey in $parent_dir");
	my $ret = 0;
	unless($ret) {
		%last_component_deps = () if($args{top_call});
		delete $args{top_call};
		$ret = $self->SUPER::compile(%args);
	}
	return $ret;
}

sub component_content_call {
	my $self = shift;
	return _Cache_Static_component_call($self, 1, @_);
}

sub component_call {
	my $self = shift;
	return _Cache_Static_component_call($self, 0, @_);
}
1;