From: Uri Guttman Date: Fri, 31 Oct 2008 01:40:05 +0000 (-0500) Subject: initial commit X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=e374d8da73279ac3fbba8d938f287281a6ae924f;p=urisagit%2FTemplate-Simple.git initial commit --- e374d8da73279ac3fbba8d938f287281a6ae924f diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..cb5c7a5 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,10 @@ +blib* +Makefile +Makefile.old +Build +_build* +pm_to_blib* +*.tar.gz +.lwpcookies +Template-Tiny-* +cover_db diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1f8c03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +;; This buffer is for notes you don't want to save, and for Lisp evaluation. +;; If you want to create a file, visit that file with C-x C-f, +;; then enter the text in that file's own buffer. + +CVS +*.gz +blib +*.tar +old +*~ + diff --git a/Changes b/Changes new file mode 100644 index 0000000..177ebbb --- /dev/null +++ b/Changes @@ -0,0 +1,11 @@ +Revision history for Template-Simple + +0.02 Tue Oct 17 02:08:28 EDT 2006 + - Fixed bug with nested hashes being rendered. Added nested.t to + the tests. + Thanks to Nigel Hamilton + +0.01 Sun Aug 27 00:07:13 EDT 2006 + First release + + diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..80ecf65 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,17 @@ +Changes +MANIFEST +META.yml # Will be created by "make dist" +Makefile.PL +README +lib/Template/Simple.pm +t/00-load.t +t/boilerplate.t +t/pod-coverage.t +t/pod.t +t/top.t +t/scalar.t +t/nested.t +t/options.t +t/include.t +t/error.t +t/common.pm diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..b85e551 --- /dev/null +++ b/META.yml @@ -0,0 +1,12 @@ +# http://module-build.sourceforge.net/META-spec.html +#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX# +name: Template-Simple +version: 0.02 +version_from: lib/Template/Simple.pm +installdirs: site +requires: + File::Slurp: 0 + Test::More: 0 + +distribution_type: module +generated_by: ExtUtils::MakeMaker version 6.17 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db8b045 --- /dev/null +++ b/Makefile @@ -0,0 +1,742 @@ +# This Makefile is for the Template::Simple extension to perl. +# +# It was generated automatically by MakeMaker version +# 6.17 (Revision: 1.133) from the contents of +# Makefile.PL. Don't edit this file, edit Makefile.PL instead. +# +# ANY CHANGES MADE HERE WILL BE LOST! +# +# MakeMaker ARGV: () +# +# MakeMaker Parameters: + +# ABSTRACT_FROM => q[lib/Template/Simple.pm] +# AUTHOR => q[Uri Guttman ] +# NAME => q[Template::Simple] +# PL_FILES => { } +# PREREQ_PM => { Test::More=>q[0], File::Slurp=>q[0] } +# VERSION_FROM => q[lib/Template/Simple.pm] +# clean => { FILES=>q[Template-Simple-*] } +# dist => { COMPRESS=>q[gzip -9f], SUFFIX=>q[gz] } + +# --- MakeMaker post_initialize section: + + +# --- MakeMaker const_config section: + +# These definitions are from config.sh (via /usr/local/lib/perl5/5.8.6/sun4-solaris/Config.pm) + +# They may have been overridden via Makefile.PL or on the command line +AR = ar +CC = gcc +CCCDLFLAGS = -fPIC +CCDLFLAGS = +DLEXT = so +DLSRC = dl_dlopen.xs +LD = gcc +LDDLFLAGS = -G -L/usr/local/lib +LDFLAGS = -L/usr/local/lib +LIBC = /lib/libc.so +LIB_EXT = .a +OBJ_EXT = .o +OSNAME = solaris +OSVERS = 2.9 +RANLIB = : +SITELIBEXP = /usr/local/lib/perl5/site_perl/5.8.6 +SITEARCHEXP = /usr/local/lib/perl5/site_perl/5.8.6/sun4-solaris +SO = so +EXE_EXT = +FULL_AR = /usr/ccs/bin/ar +VENDORARCHEXP = +VENDORLIBEXP = + + +# --- MakeMaker constants section: +AR_STATIC_ARGS = cr +DIRFILESEP = / +NAME = Template::Simple +NAME_SYM = Template_Simple +VERSION = 0.02 +VERSION_MACRO = VERSION +VERSION_SYM = 0_02 +DEFINE_VERSION = -D$(VERSION_MACRO)=\"$(VERSION)\" +XS_VERSION = 0.02 +XS_VERSION_MACRO = XS_VERSION +XS_DEFINE_VERSION = -D$(XS_VERSION_MACRO)=\"$(XS_VERSION)\" +INST_ARCHLIB = blib/arch +INST_SCRIPT = blib/script +INST_BIN = blib/bin +INST_LIB = blib/lib +INST_MAN1DIR = blib/man1 +INST_MAN3DIR = blib/man3 +MAN1EXT = 1 +MAN3EXT = 3 +INSTALLDIRS = site +DESTDIR = +PREFIX = +PERLPREFIX = /usr/local +SITEPREFIX = /usr/local +VENDORPREFIX = +INSTALLPRIVLIB = $(PERLPREFIX)/lib/perl5/5.8.6 +DESTINSTALLPRIVLIB = $(DESTDIR)$(INSTALLPRIVLIB) +INSTALLSITELIB = $(SITEPREFIX)/lib/perl5/site_perl/5.8.6 +DESTINSTALLSITELIB = $(DESTDIR)$(INSTALLSITELIB) +INSTALLVENDORLIB = +DESTINSTALLVENDORLIB = $(DESTDIR)$(INSTALLVENDORLIB) +INSTALLARCHLIB = $(PERLPREFIX)/lib/perl5/5.8.6/sun4-solaris +DESTINSTALLARCHLIB = $(DESTDIR)$(INSTALLARCHLIB) +INSTALLSITEARCH = $(SITEPREFIX)/lib/perl5/site_perl/5.8.6/sun4-solaris +DESTINSTALLSITEARCH = $(DESTDIR)$(INSTALLSITEARCH) +INSTALLVENDORARCH = +DESTINSTALLVENDORARCH = $(DESTDIR)$(INSTALLVENDORARCH) +INSTALLBIN = $(PERLPREFIX)/bin +DESTINSTALLBIN = $(DESTDIR)$(INSTALLBIN) +INSTALLSITEBIN = $(SITEPREFIX)/bin +DESTINSTALLSITEBIN = $(DESTDIR)$(INSTALLSITEBIN) +INSTALLVENDORBIN = +DESTINSTALLVENDORBIN = $(DESTDIR)$(INSTALLVENDORBIN) +INSTALLSCRIPT = $(PERLPREFIX)/bin +DESTINSTALLSCRIPT = $(DESTDIR)$(INSTALLSCRIPT) +INSTALLMAN1DIR = $(PERLPREFIX)/man/man1 +DESTINSTALLMAN1DIR = $(DESTDIR)$(INSTALLMAN1DIR) +INSTALLSITEMAN1DIR = $(SITEPREFIX)/man/man1 +DESTINSTALLSITEMAN1DIR = $(DESTDIR)$(INSTALLSITEMAN1DIR) +INSTALLVENDORMAN1DIR = +DESTINSTALLVENDORMAN1DIR = $(DESTDIR)$(INSTALLVENDORMAN1DIR) +INSTALLMAN3DIR = $(PERLPREFIX)/man/man3 +DESTINSTALLMAN3DIR = $(DESTDIR)$(INSTALLMAN3DIR) +INSTALLSITEMAN3DIR = $(SITEPREFIX)/man/man3 +DESTINSTALLSITEMAN3DIR = $(DESTDIR)$(INSTALLSITEMAN3DIR) +INSTALLVENDORMAN3DIR = +DESTINSTALLVENDORMAN3DIR = $(DESTDIR)$(INSTALLVENDORMAN3DIR) +PERL_LIB = /usr/local/lib/perl5/5.8.6 +PERL_ARCHLIB = /usr/local/lib/perl5/5.8.6/sun4-solaris +LIBPERL_A = libperl.a +FIRST_MAKEFILE = Makefile +MAKEFILE_OLD = $(FIRST_MAKEFILE).old +MAKE_APERL_FILE = $(FIRST_MAKEFILE).aperl +PERLMAINCC = $(CC) +PERL_INC = /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE +PERL = /usr/local/bin/perl +FULLPERL = /usr/local/bin/perl +ABSPERL = $(PERL) +PERLRUN = $(PERL) +FULLPERLRUN = $(FULLPERL) +ABSPERLRUN = $(ABSPERL) +PERLRUNINST = $(PERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)" +FULLPERLRUNINST = $(FULLPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)" +ABSPERLRUNINST = $(ABSPERLRUN) "-I$(INST_ARCHLIB)" "-I$(INST_LIB)" +PERL_CORE = 0 +PERM_RW = 644 +PERM_RWX = 755 + +MAKEMAKER = /usr/local/lib/perl5/5.8.6/ExtUtils/MakeMaker.pm +MM_VERSION = 6.17 +MM_REVISION = 1.133 + +# FULLEXT = Pathname for extension directory (eg Foo/Bar/Oracle). +# BASEEXT = Basename part of FULLEXT. May be just equal FULLEXT. (eg Oracle) +# PARENT_NAME = NAME without BASEEXT and no trailing :: (eg Foo::Bar) +# DLBASE = Basename part of dynamic library. May be just equal BASEEXT. +FULLEXT = Template/Simple +BASEEXT = Simple +PARENT_NAME = Template +DLBASE = $(BASEEXT) +VERSION_FROM = lib/Template/Simple.pm +OBJECT = +LDFROM = $(OBJECT) +LINKTYPE = dynamic + +# Handy lists of source code files: +XS_FILES = +C_FILES = +O_FILES = +H_FILES = +MAN1PODS = +MAN3PODS = lib/Template/Simple.pm + +# Where is the Config information that we are using/depend on +CONFIGDEP = $(PERL_ARCHLIB)$(DIRFILESEP)Config.pm $(PERL_INC)$(DIRFILESEP)config.h + +# Where to build things +INST_LIBDIR = $(INST_LIB)/Template +INST_ARCHLIBDIR = $(INST_ARCHLIB)/Template + +INST_AUTODIR = $(INST_LIB)/auto/$(FULLEXT) +INST_ARCHAUTODIR = $(INST_ARCHLIB)/auto/$(FULLEXT) + +INST_STATIC = +INST_DYNAMIC = +INST_BOOT = + +# Extra linker info +EXPORT_LIST = +PERL_ARCHIVE = +PERL_ARCHIVE_AFTER = + + +TO_INST_PM = bug.pl \ + lib/Template/Simple.pm \ + lib/Template/Simple.pm.expnad + +PM_TO_BLIB = lib/Template/Simple.pm.expnad \ + blib/lib/Template/Simple.pm.expnad \ + bug.pl \ + $(INST_LIB)/Template/bug.pl \ + lib/Template/Simple.pm \ + blib/lib/Template/Simple.pm + + +# --- MakeMaker platform_constants section: +MM_Unix_VERSION = 1.42 +PERL_MALLOC_DEF = -DPERL_EXTMALLOC_DEF -Dmalloc=Perl_malloc -Dfree=Perl_mfree -Drealloc=Perl_realloc -Dcalloc=Perl_calloc + + +# --- MakeMaker tool_autosplit section: +# Usage: $(AUTOSPLITFILE) FileToSplit AutoDirToSplitInto +AUTOSPLITFILE = $(PERLRUN) -e 'use AutoSplit; autosplit($$ARGV[0], $$ARGV[1], 0, 1, 1)' + + + +# --- MakeMaker tool_xsubpp section: + + +# --- MakeMaker tools_other section: +SHELL = /bin/sh +CHMOD = chmod +CP = cp +MV = mv +NOOP = $(SHELL) -c true +NOECHO = @ +RM_F = rm -f +RM_RF = rm -rf +TEST_F = test -f +TOUCH = touch +UMASK_NULL = umask 0 +DEV_NULL = > /dev/null 2>&1 +MKPATH = $(PERLRUN) "-MExtUtils::Command" -e mkpath +EQUALIZE_TIMESTAMP = $(PERLRUN) "-MExtUtils::Command" -e eqtime +ECHO = echo +ECHO_N = echo -n +UNINST = 0 +VERBINST = 0 +MOD_INSTALL = $(PERLRUN) -MExtUtils::Install -e 'install({@ARGV}, '\''$(VERBINST)'\'', 0, '\''$(UNINST)'\'');' +DOC_INSTALL = $(PERLRUN) "-MExtUtils::Command::MM" -e perllocal_install +UNINSTALL = $(PERLRUN) "-MExtUtils::Command::MM" -e uninstall +WARN_IF_OLD_PACKLIST = $(PERLRUN) "-MExtUtils::Command::MM" -e warn_if_old_packlist + + +# --- MakeMaker makemakerdflt section: +makemakerdflt: all + $(NOECHO) $(NOOP) + + +# --- MakeMaker dist section: +TAR = tar +TARFLAGS = cvf +ZIP = zip +ZIPFLAGS = -r +COMPRESS = gzip -9f +SUFFIX = gz +SHAR = shar +PREOP = $(NOECHO) $(NOOP) +POSTOP = $(NOECHO) $(NOOP) +TO_UNIX = $(NOECHO) $(NOOP) +CI = ci -u +RCS_LABEL = rcs -Nv$(VERSION_SYM): -q +DIST_CP = best +DIST_DEFAULT = tardist +DISTNAME = Template-Simple +DISTVNAME = Template-Simple-0.02 + + +# --- MakeMaker macro section: + + +# --- MakeMaker depend section: + + +# --- MakeMaker cflags section: + + +# --- MakeMaker const_loadlibs section: + + +# --- MakeMaker const_cccmd section: + + +# --- MakeMaker post_constants section: + + +# --- MakeMaker pasthru section: + +PASTHRU = LIB="$(LIB)"\ + LIBPERL_A="$(LIBPERL_A)"\ + LINKTYPE="$(LINKTYPE)"\ + PREFIX="$(PREFIX)"\ + OPTIMIZE="$(OPTIMIZE)"\ + PASTHRU_DEFINE="$(PASTHRU_DEFINE)"\ + PASTHRU_INC="$(PASTHRU_INC)" + + +# --- MakeMaker special_targets section: +.SUFFIXES: .xs .c .C .cpp .i .s .cxx .cc $(OBJ_EXT) + +.PHONY: all config static dynamic test linkext manifest + + + +# --- MakeMaker c_o section: + + +# --- MakeMaker xs_c section: + + +# --- MakeMaker xs_o section: + + +# --- MakeMaker top_targets section: +all :: pure_all manifypods + $(NOECHO) $(NOOP) + + +pure_all :: config pm_to_blib subdirs linkext + $(NOECHO) $(NOOP) + +subdirs :: $(MYEXTLIB) + $(NOECHO) $(NOOP) + +config :: $(FIRST_MAKEFILE) $(INST_LIBDIR)$(DIRFILESEP).exists + $(NOECHO) $(NOOP) + +config :: $(INST_ARCHAUTODIR)$(DIRFILESEP).exists + $(NOECHO) $(NOOP) + +config :: $(INST_AUTODIR)$(DIRFILESEP).exists + $(NOECHO) $(NOOP) + +$(INST_AUTODIR)/.exists :: /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h + $(NOECHO) $(MKPATH) $(INST_AUTODIR) + $(NOECHO) $(EQUALIZE_TIMESTAMP) /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h $(INST_AUTODIR)/.exists + + -$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_AUTODIR) + +$(INST_LIBDIR)/.exists :: /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h + $(NOECHO) $(MKPATH) $(INST_LIBDIR) + $(NOECHO) $(EQUALIZE_TIMESTAMP) /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h $(INST_LIBDIR)/.exists + + -$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_LIBDIR) + +$(INST_ARCHAUTODIR)/.exists :: /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h + $(NOECHO) $(MKPATH) $(INST_ARCHAUTODIR) + $(NOECHO) $(EQUALIZE_TIMESTAMP) /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h $(INST_ARCHAUTODIR)/.exists + + -$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_ARCHAUTODIR) + +config :: $(INST_MAN3DIR)$(DIRFILESEP).exists + $(NOECHO) $(NOOP) + + +$(INST_MAN3DIR)/.exists :: /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h + $(NOECHO) $(MKPATH) $(INST_MAN3DIR) + $(NOECHO) $(EQUALIZE_TIMESTAMP) /usr/local/lib/perl5/5.8.6/sun4-solaris/CORE/perl.h $(INST_MAN3DIR)/.exists + + -$(NOECHO) $(CHMOD) $(PERM_RWX) $(INST_MAN3DIR) + +help: + perldoc ExtUtils::MakeMaker + + +# --- MakeMaker linkext section: + +linkext :: $(LINKTYPE) + $(NOECHO) $(NOOP) + + +# --- MakeMaker dlsyms section: + + +# --- MakeMaker dynamic section: + +dynamic :: $(FIRST_MAKEFILE) $(INST_DYNAMIC) $(INST_BOOT) + $(NOECHO) $(NOOP) + + +# --- MakeMaker dynamic_bs section: + +BOOTSTRAP = + + +# --- MakeMaker dynamic_lib section: + + +# --- MakeMaker static section: + +## $(INST_PM) has been moved to the all: target. +## It remains here for awhile to allow for old usage: "make static" +static :: $(FIRST_MAKEFILE) $(INST_STATIC) + $(NOECHO) $(NOOP) + + +# --- MakeMaker static_lib section: + + +# --- MakeMaker manifypods section: + +POD2MAN_EXE = $(PERLRUN) "-MExtUtils::Command::MM" -e pod2man "--" +POD2MAN = $(POD2MAN_EXE) + + +manifypods : pure_all \ + lib/Template/Simple.pm \ + lib/Template/Simple.pm + $(NOECHO) $(POD2MAN) --section=3 --perm_rw=$(PERM_RW)\ + lib/Template/Simple.pm $(INST_MAN3DIR)/Template::Simple.$(MAN3EXT) + + + + +# --- MakeMaker processPL section: + + +# --- MakeMaker installbin section: + + +# --- MakeMaker subdirs section: + +# none + +# --- MakeMaker clean_subdirs section: +clean_subdirs : + $(NOECHO) $(NOOP) + + +# --- MakeMaker clean section: + +# Delete temporary files but do not touch installed files. We don't delete +# the Makefile here so a later make realclean still has a makefile to use. + +clean :: clean_subdirs + -$(RM_RF) Template-Simple-* ./blib $(MAKE_APERL_FILE) $(INST_ARCHAUTODIR)/extralibs.all $(INST_ARCHAUTODIR)/extralibs.ld perlmain.c tmon.out mon.out so_locations pm_to_blib *$(OBJ_EXT) *$(LIB_EXT) perl.exe perl perl$(EXE_EXT) $(BOOTSTRAP) $(BASEEXT).bso $(BASEEXT).def lib$(BASEEXT).def $(BASEEXT).exp $(BASEEXT).x core core.*perl.*.? *perl.core core.[0-9] core.[0-9][0-9] core.[0-9][0-9][0-9] core.[0-9][0-9][0-9][0-9] core.[0-9][0-9][0-9][0-9][0-9] + -$(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) $(DEV_NULL) + + +# --- MakeMaker realclean_subdirs section: +realclean_subdirs : + $(NOECHO) $(NOOP) + + +# --- MakeMaker realclean section: + +# Delete temporary files (via clean) and also delete installed files +realclean purge :: clean realclean_subdirs + $(RM_RF) $(INST_AUTODIR) $(INST_ARCHAUTODIR) + $(RM_RF) $(DISTVNAME) + $(RM_F) $(INST_LIB)/Template/bug.pl blib/lib/Template/Simple.pm.expnad blib/lib/Template/Simple.pm $(MAKEFILE_OLD) $(FIRST_MAKEFILE) + + +# --- MakeMaker metafile section: +metafile : + $(NOECHO) $(ECHO) '# http://module-build.sourceforge.net/META-spec.html' > META.yml + $(NOECHO) $(ECHO) '#XXXXXXX This is a prototype!!! It will change in the future!!! XXXXX#' >> META.yml + $(NOECHO) $(ECHO) 'name: Template-Simple' >> META.yml + $(NOECHO) $(ECHO) 'version: 0.02' >> META.yml + $(NOECHO) $(ECHO) 'version_from: lib/Template/Simple.pm' >> META.yml + $(NOECHO) $(ECHO) 'installdirs: site' >> META.yml + $(NOECHO) $(ECHO) 'requires:' >> META.yml + $(NOECHO) $(ECHO) ' File::Slurp: 0' >> META.yml + $(NOECHO) $(ECHO) ' Test::More: 0' >> META.yml + $(NOECHO) $(ECHO) '' >> META.yml + $(NOECHO) $(ECHO) 'distribution_type: module' >> META.yml + $(NOECHO) $(ECHO) 'generated_by: ExtUtils::MakeMaker version 6.17' >> META.yml + + +# --- MakeMaker metafile_addtomanifest section: +metafile_addtomanifest: + $(NOECHO) $(PERLRUN) -MExtUtils::Manifest=maniadd -e 'eval { maniadd({q{META.yml} => q{Module meta-data (added by MakeMaker)}}) } ' \ + -e ' or print "Could not add META.yml to MANIFEST: $${'\''@'\''}\n"' + + +# --- MakeMaker dist_basics section: +distclean :: realclean distcheck + $(NOECHO) $(NOOP) + +distcheck : + $(PERLRUN) "-MExtUtils::Manifest=fullcheck" -e fullcheck + +skipcheck : + $(PERLRUN) "-MExtUtils::Manifest=skipcheck" -e skipcheck + +manifest : + $(PERLRUN) "-MExtUtils::Manifest=mkmanifest" -e mkmanifest + +veryclean : realclean + $(RM_F) *~ *.orig */*~ */*.orig + + + +# --- MakeMaker dist_core section: + +dist : $(DIST_DEFAULT) $(FIRST_MAKEFILE) + $(NOECHO) $(PERLRUN) -l -e 'print '\''Warning: Makefile possibly out of date with $(VERSION_FROM)'\''' \ + -e ' if -e '\''$(VERSION_FROM)'\'' and -M '\''$(VERSION_FROM)'\'' < -M '\''$(FIRST_MAKEFILE)'\'';' + +tardist : $(DISTVNAME).tar$(SUFFIX) + $(NOECHO) $(NOOP) + +uutardist : $(DISTVNAME).tar$(SUFFIX) + uuencode $(DISTVNAME).tar$(SUFFIX) $(DISTVNAME).tar$(SUFFIX) > $(DISTVNAME).tar$(SUFFIX)_uu + +$(DISTVNAME).tar$(SUFFIX) : distdir + $(PREOP) + $(TO_UNIX) + $(TAR) $(TARFLAGS) $(DISTVNAME).tar $(DISTVNAME) + $(RM_RF) $(DISTVNAME) + $(COMPRESS) $(DISTVNAME).tar + $(POSTOP) + +zipdist : $(DISTVNAME).zip + $(NOECHO) $(NOOP) + +$(DISTVNAME).zip : distdir + $(PREOP) + $(ZIP) $(ZIPFLAGS) $(DISTVNAME).zip $(DISTVNAME) + $(RM_RF) $(DISTVNAME) + $(POSTOP) + +shdist : distdir + $(PREOP) + $(SHAR) $(DISTVNAME) > $(DISTVNAME).shar + $(RM_RF) $(DISTVNAME) + $(POSTOP) + + +# --- MakeMaker distdir section: +distdir : metafile metafile_addtomanifest + $(RM_RF) $(DISTVNAME) + $(PERLRUN) "-MExtUtils::Manifest=manicopy,maniread" \ + -e "manicopy(maniread(),'$(DISTVNAME)', '$(DIST_CP)');" + + + +# --- MakeMaker dist_test section: + +disttest : distdir + cd $(DISTVNAME) && $(ABSPERLRUN) Makefile.PL + cd $(DISTVNAME) && $(MAKE) $(PASTHRU) + cd $(DISTVNAME) && $(MAKE) test $(PASTHRU) + + +# --- MakeMaker dist_ci section: + +ci : + $(PERLRUN) "-MExtUtils::Manifest=maniread" \ + -e "@all = keys %{ maniread() };" \ + -e "print(qq{Executing $(CI) @all\n}); system(qq{$(CI) @all});" \ + -e "print(qq{Executing $(RCS_LABEL) ...\n}); system(qq{$(RCS_LABEL) @all});" + + +# --- MakeMaker install section: + +install :: all pure_install doc_install + +install_perl :: all pure_perl_install doc_perl_install + +install_site :: all pure_site_install doc_site_install + +install_vendor :: all pure_vendor_install doc_vendor_install + +pure_install :: pure_$(INSTALLDIRS)_install + +doc_install :: doc_$(INSTALLDIRS)_install + +pure__install : pure_site_install + $(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site + +doc__install : doc_site_install + $(NOECHO) $(ECHO) INSTALLDIRS not defined, defaulting to INSTALLDIRS=site + +pure_perl_install :: + $(NOECHO) $(MOD_INSTALL) \ + read $(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist \ + write $(DESTINSTALLARCHLIB)/auto/$(FULLEXT)/.packlist \ + $(INST_LIB) $(DESTINSTALLPRIVLIB) \ + $(INST_ARCHLIB) $(DESTINSTALLARCHLIB) \ + $(INST_BIN) $(DESTINSTALLBIN) \ + $(INST_SCRIPT) $(DESTINSTALLSCRIPT) \ + $(INST_MAN1DIR) $(DESTINSTALLMAN1DIR) \ + $(INST_MAN3DIR) $(DESTINSTALLMAN3DIR) + $(NOECHO) $(WARN_IF_OLD_PACKLIST) \ + $(SITEARCHEXP)/auto/$(FULLEXT) + + +pure_site_install :: + $(NOECHO) $(MOD_INSTALL) \ + read $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist \ + write $(DESTINSTALLSITEARCH)/auto/$(FULLEXT)/.packlist \ + $(INST_LIB) $(DESTINSTALLSITELIB) \ + $(INST_ARCHLIB) $(DESTINSTALLSITEARCH) \ + $(INST_BIN) $(DESTINSTALLSITEBIN) \ + $(INST_SCRIPT) $(DESTINSTALLSCRIPT) \ + $(INST_MAN1DIR) $(DESTINSTALLSITEMAN1DIR) \ + $(INST_MAN3DIR) $(DESTINSTALLSITEMAN3DIR) + $(NOECHO) $(WARN_IF_OLD_PACKLIST) \ + $(PERL_ARCHLIB)/auto/$(FULLEXT) + +pure_vendor_install :: + $(NOECHO) $(MOD_INSTALL) \ + read $(VENDORARCHEXP)/auto/$(FULLEXT)/.packlist \ + write $(DESTINSTALLVENDORARCH)/auto/$(FULLEXT)/.packlist \ + $(INST_LIB) $(DESTINSTALLVENDORLIB) \ + $(INST_ARCHLIB) $(DESTINSTALLVENDORARCH) \ + $(INST_BIN) $(DESTINSTALLVENDORBIN) \ + $(INST_SCRIPT) $(DESTINSTALLSCRIPT) \ + $(INST_MAN1DIR) $(DESTINSTALLVENDORMAN1DIR) \ + $(INST_MAN3DIR) $(DESTINSTALLVENDORMAN3DIR) + +doc_perl_install :: + $(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod + -$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) + -$(NOECHO) $(DOC_INSTALL) \ + "Module" "$(NAME)" \ + "installed into" "$(INSTALLPRIVLIB)" \ + LINKTYPE "$(LINKTYPE)" \ + VERSION "$(VERSION)" \ + EXE_FILES "$(EXE_FILES)" \ + >> $(DESTINSTALLARCHLIB)/perllocal.pod + +doc_site_install :: + $(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod + -$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) + -$(NOECHO) $(DOC_INSTALL) \ + "Module" "$(NAME)" \ + "installed into" "$(INSTALLSITELIB)" \ + LINKTYPE "$(LINKTYPE)" \ + VERSION "$(VERSION)" \ + EXE_FILES "$(EXE_FILES)" \ + >> $(DESTINSTALLARCHLIB)/perllocal.pod + +doc_vendor_install :: + $(NOECHO) $(ECHO) Appending installation info to $(DESTINSTALLARCHLIB)/perllocal.pod + -$(NOECHO) $(MKPATH) $(DESTINSTALLARCHLIB) + -$(NOECHO) $(DOC_INSTALL) \ + "Module" "$(NAME)" \ + "installed into" "$(INSTALLVENDORLIB)" \ + LINKTYPE "$(LINKTYPE)" \ + VERSION "$(VERSION)" \ + EXE_FILES "$(EXE_FILES)" \ + >> $(DESTINSTALLARCHLIB)/perllocal.pod + + +uninstall :: uninstall_from_$(INSTALLDIRS)dirs + +uninstall_from_perldirs :: + $(NOECHO) $(UNINSTALL) $(PERL_ARCHLIB)/auto/$(FULLEXT)/.packlist + +uninstall_from_sitedirs :: + $(NOECHO) $(UNINSTALL) $(SITEARCHEXP)/auto/$(FULLEXT)/.packlist + +uninstall_from_vendordirs :: + $(NOECHO) $(UNINSTALL) $(VENDORARCHEXP)/auto/$(FULLEXT)/.packlist + + +# --- MakeMaker force section: +# Phony target to force checking subdirectories. +FORCE: + $(NOECHO) $(NOOP) + + +# --- MakeMaker perldepend section: + + +# --- MakeMaker makefile section: + +# We take a very conservative approach here, but it's worth it. +# We move Makefile to Makefile.old here to avoid gnu make looping. +$(FIRST_MAKEFILE) : Makefile.PL $(CONFIGDEP) + $(NOECHO) $(ECHO) "Makefile out-of-date with respect to $?" + $(NOECHO) $(ECHO) "Cleaning current config before rebuilding Makefile..." + $(NOECHO) $(RM_F) $(MAKEFILE_OLD) + $(NOECHO) $(MV) $(FIRST_MAKEFILE) $(MAKEFILE_OLD) + -$(MAKE) -f $(MAKEFILE_OLD) clean $(DEV_NULL) || $(NOOP) + $(PERLRUN) Makefile.PL + $(NOECHO) $(ECHO) "==> Your Makefile has been rebuilt. <==" + $(NOECHO) $(ECHO) "==> Please rerun the make command. <==" + false + + + +# --- MakeMaker staticmake section: + +# --- MakeMaker makeaperl section --- +MAP_TARGET = perl +FULLPERL = /usr/local/bin/perl + +$(MAP_TARGET) :: static $(MAKE_APERL_FILE) + $(MAKE) -f $(MAKE_APERL_FILE) $@ + +$(MAKE_APERL_FILE) : $(FIRST_MAKEFILE) + $(NOECHO) $(ECHO) Writing \"$(MAKE_APERL_FILE)\" for this $(MAP_TARGET) + $(NOECHO) $(PERLRUNINST) \ + Makefile.PL DIR= \ + MAKEFILE=$(MAKE_APERL_FILE) LINKTYPE=static \ + MAKEAPERL=1 NORECURS=1 CCCDLFLAGS= + + +# --- MakeMaker test section: + +TEST_VERBOSE=0 +TEST_TYPE=test_$(LINKTYPE) +TEST_FILE = test.pl +TEST_FILES = t/*.t +TESTDB_SW = -d + +testdb :: testdb_$(LINKTYPE) + +test :: $(TEST_TYPE) + +test_dynamic :: pure_all + PERL_DL_NONLAZY=1 $(FULLPERLRUN) "-MExtUtils::Command::MM" "-e" "test_harness($(TEST_VERBOSE), '$(INST_LIB)', '$(INST_ARCHLIB)')" $(TEST_FILES) + +testdb_dynamic :: pure_all + PERL_DL_NONLAZY=1 $(FULLPERLRUN) $(TESTDB_SW) "-I$(INST_LIB)" "-I$(INST_ARCHLIB)" $(TEST_FILE) + +test_ : test_dynamic + +test_static :: test_dynamic +testdb_static :: testdb_dynamic + + +# --- MakeMaker ppd section: +# Creates a PPD (Perl Package Description) for a binary distribution. +ppd: + $(NOECHO) $(ECHO) '' > $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' $(DISTNAME)' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' A simple and fast template module' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' Uri Guttman <uri@sysarch.com>' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) ' ' >> $(DISTNAME).ppd + $(NOECHO) $(ECHO) '' >> $(DISTNAME).ppd + + +# --- MakeMaker pm_to_blib section: + +pm_to_blib: $(TO_INST_PM) + $(NOECHO) $(PERLRUN) -MExtUtils::Install -e 'pm_to_blib({@ARGV}, '\''$(INST_LIB)/auto'\'', '\''$(PM_FILTER)'\'')'\ + lib/Template/Simple.pm.expnad blib/lib/Template/Simple.pm.expnad \ + bug.pl $(INST_LIB)/Template/bug.pl \ + lib/Template/Simple.pm blib/lib/Template/Simple.pm + $(NOECHO) $(TOUCH) $@ + +# --- MakeMaker selfdocument section: + + +# --- MakeMaker postamble section: + + +# End. diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..8713e6e --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,17 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'Template::Simple', + AUTHOR => 'Uri Guttman ', + VERSION_FROM => 'lib/Template/Simple.pm', + ABSTRACT_FROM => 'lib/Template/Simple.pm', + PL_FILES => {}, + PREREQ_PM => { + 'Test::More' => 0, + 'File::Slurp' => 0, + }, + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, + clean => { FILES => 'Template-Simple-*' }, +); diff --git a/README b/README new file mode 100644 index 0000000..e72fdf0 --- /dev/null +++ b/README @@ -0,0 +1,38 @@ +Template-Simple + +INSTALLATION + +To install this module, run the following commands: + + perl Makefile.PL + make + make test + make install + + +SUPPORT AND DOCUMENTATION + +After installing, you can find documentation for this module with the perldoc command. + + perldoc Template::Simple + +You can also look for information at: + + Search CPAN + http://search.cpan.org/dist/Template-Simple + + CPAN Request Tracker: + http://rt.cpan.org/NoAuth/Bugs.html?Dist=Template-Simple + + AnnoCPAN, annotated CPAN documentation: + http://annocpan.org/dist/Template-Simple + + CPAN Ratings: + http://cpanratings.perl.org/d/Template-Simple + +COPYRIGHT AND LICENCE + +Copyright (C) 2006 Uri Guttman + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. diff --git a/Trexy/Template.pm b/Trexy/Template.pm new file mode 100644 index 0000000..3a22e9f --- /dev/null +++ b/Trexy/Template.pm @@ -0,0 +1,108 @@ + +package Trexy::Template; + +############################################################################### +# Nigel Hamilton +# +# Copyright Nigel Hamilton 2005 +# All Rights Reserved +# +# Author: Nigel Hamilton +# Filename: Trexy::Template.pm +# Description: Do simple, fast, "no code in the template" processing +# +# Based on Uri Guttman's sublime +# TinyTemplate module - released +# at YAPC::NA, 2006. +# +# This template module features: +# +# * implicit loops +# * template includes +# * template caching +# * no coding in the template +# +# Date Change +# ----------------------------------------------------------------------------- +# 24/09/2006 Took Uri's module and extended it slightly +# +############################################################################### + +use strict; +use warnings ; + +use base qw(Template::Simple); + +# escape Regex meta characters +my $start_delimiter = qr/\[\-/; +my $end_delimiter = qr/\-\]/; + + +############################################################################### +# +# new - construct a template +# +############################################################################### + +sub new { + + my ($class) = @_; + + return $class->SUPER::new( pre_delim => $start_delimiter, + post_delim => $end_delimiter ); + +} + + +############################################################################### +# +# render - fully render the template into a string +# +############################################################################### + +sub render { + + my ($this, $string, $tokens) = @_; + + # return a string from render + return ${ $this->SUPER::render($string, $tokens) }; + +} + + +############################################################################### +# +# render_as_ref - render as a string reference +# +############################################################################### + +sub render_as_ref { + + my ($this, $string, $tokens) = @_; + + # return a string from render + return $this->SUPER::render($string, $tokens); + +} + + +############################################################################### +# +# get_tokens - grab the tokens values out of a template +# +############################################################################### + +sub get_tokens { + + my ($string) = @_; + + my @found_tokens = $string =~ m/$start_delimiter(\w+)$end_delimiter/g; + + my %unique_tokens = map { $_ => 1 } @found_tokens; + + return keys %unique_tokens; + +} + + +1; diff --git a/bug.pl b/bug.pl new file mode 100644 index 0000000..0a4d9da --- /dev/null +++ b/bug.pl @@ -0,0 +1,38 @@ +#!/usr/local/bin/perl + +use strict ; +use warnings ; + +use Trexy::Template ; + +my $template = < + [-start widgets-] + + [-anchor-] + + [-title-] +
[-description-] + + [-escaped_anchor-] + [-options-] + + [-end widgets-] + + +TEST + + +my $tokens = { + widgets => [ + { title => "bart" }, + { title => "marge" } + ], +}; + +print Trexy::Template->new->render($template, $tokens); + + + diff --git a/bug2.pl b/bug2.pl new file mode 100644 index 0000000..ceac363 --- /dev/null +++ b/bug2.pl @@ -0,0 +1,29 @@ +#!/usr/local/bin/perl + +use warnings ; +use strict ; + +use Template::Simple ; + +my $data = { + widgets => [ + { + title => "bart", + }, + { + title => "marge", + } + ], +} ; + +my $template = <new() ; + +my $text = $renderer->render( $template, $data ) ; + +print ${$text} ; + +print "$Template::Simple::VERSION\n" ; diff --git a/lib/Template/Simple.pm b/lib/Template/Simple.pm new file mode 100644 index 0000000..173c605 --- /dev/null +++ b/lib/Template/Simple.pm @@ -0,0 +1,1068 @@ +package Template::Simple; + +use warnings; +use strict; + +use Carp ; +use Scalar::Util qw( reftype ) ; +use File::Slurp ; + +use Data::Dumper ; + +our $VERSION = '0.03'; + +my %opt_defaults = ( + + pre_delim => qr/\[%/, + post_delim => qr/%\]/, + greedy_chunk => 0, +# upper_case => 0, +# lower_case => 0, + include_paths => [ qw( templates ) ], +) ; + +sub new { + + my( $class, %opts ) = @_ ; + + my $self = bless {}, $class ; + +# get all the options or defaults into the object + + while( my( $name, $default ) = each %opt_defaults ) { + + $self->{$name} = defined( $opts{$name} ) ? + $opts{$name} : $default ; + } + +# make up the regexes to parse the markup from templates + +# this matches scalar markups and grabs the name + + $self->{scalar_re} = qr{ + $self->{pre_delim} + \s* # optional leading whitespace + (\w+?) # grab scalar name + \s* # optional trailing whitespace + $self->{post_delim} + }xi ; # case insensitive + +#print "RE <$self->{scalar_re}>\n" ; + +# this grabs the body of a chunk in either greedy or non-greedy modes + + my $chunk_body = $self->{greedy_chunk} ? qr/.+/s : qr/.+?/s ; + +# this matches a marked chunk and grabs its name and text body + + $self->{chunk_re} = qr{ + $self->{pre_delim} + \s* # optional leading whitespace + START # required START token + \s+ # required whitespace + (\w+?) # grab the chunk name + \s* # optional trailing whitespace + $self->{post_delim} + ($chunk_body) # grab the chunk body + $self->{pre_delim} + \s* # optional leading whitespace + END # required END token + \s+ # required whitespace + \1 # match the grabbed chunk name + \s* # optional trailing whitespace + $self->{post_delim} + }xi ; # case insensitive + +#print "RE <$self->{chunk_re}>\n" ; + +# this matches a include markup and grabs its template name + + $self->{include_re} = qr{ + $self->{pre_delim} + \s* # optional leading whitespace + INCLUDE # required INCLUDE token + \s+ # required whitespace + (\w+?) # grab the included template name + \s* # optional trailing whitespace + $self->{post_delim} + }xi ; # case insensitive + +# load in any templates + + $self->add_templates( $opts{templates} ) ; + + return $self ; +} + + + +sub render { + + my( $self, $template, $data ) = @_ ; + +# make a copy if a scalar ref is passed as the template text is +# modified in place + + my $tmpl_ref = ref $template eq 'SCALAR' ? $template : \$template ; + + my $rendered = $self->_render_includes( $tmpl_ref ) ; + +#print "INC EXP <$rendered>\n" ; + + $rendered = eval { + $self->_render_chunk( $rendered, $data ) ; + } ; + + croak "Template::Simple $@" if $@ ; + + return $rendered ; +} + +sub _render_includes { + + my( $self, $tmpl_ref ) = @_ ; + +# make a copy of the initial template so we can render it. + + my $rendered = ${$tmpl_ref} ; + +# loop until we can render no more include markups + + 1 while $rendered =~ + s{$self->{include_re}} + { ${ $self->_get_template($1) } + }e ; + + return \$rendered ; +} + +my %renderers = ( + + HASH => \&_render_hash, + ARRAY => \&_render_array, + CODE => \&_render_code, +# if no ref then data is a scalar so replace the template with just the data + '' => sub { \$_[2] }, +) ; + + +sub _render_chunk { + + my( $self, $tmpl_ref, $data ) = @_ ; + +#print "T ref [$tmpl_ref] [$$tmpl_ref]\n" ; +#print "CHUNK ref [$tmpl_ref] TMPL\n<$$tmpl_ref>\n" ; + +#print Dumper $data ; + + return \'' unless defined $data ; + +# now render this chunk based on the type of data + + my $renderer = $renderers{reftype $data || ''} ; + +#print "EXP $renderer\nREF ", reftype $data, "\n" ; + + die "unknown template data type '$data'\n" unless defined $renderer ; + + return $self->$renderer( $tmpl_ref, $data ) ; +} + +sub _render_hash { + + my( $self, $tmpl_ref, $href ) = @_ ; + + return $tmpl_ref unless keys %{$href} ; + +# we need a local copy of the template to render + + my $rendered = ${$tmpl_ref} ; + + +# recursively render all top level chunks in this chunk + + $rendered =~ s{$self->{chunk_re}} + { + # print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ; + ${ $self->_render_chunk( \"$2", $href->{$1} ) } + }gex ; + +# now render scalars + +#print "HREF: ", Dumper $href ; + + $rendered =~ s{$self->{scalar_re}} + { + # print "SCALAR $1 VAL $href->{$1}\n" ; + defined $href->{$1} ? $href->{$1} : '' + }ge ; + +#print "HASH REND3\n<$rendered>\n" ; + + return \$rendered ; +} + +sub _render_array { + + my( $self, $tmpl_ref, $aref ) = @_ ; + +# render this $tmpl_ref for each element of the aref and join them + + my $rendered ; + +#print "AREF: ", Dumper $aref ; + + $rendered .= ${$self->_render_chunk( $tmpl_ref, $_ )} for @{$aref} ; + + return \$rendered ; +} + +sub _render_code { + + my( $self, $tmpl_ref, $cref ) = @_ ; + + my $rendered = $cref->( $tmpl_ref ) ; + + die <{templates}}{ keys %{$tmpls} } = + map ref $_ eq 'SCALAR' ? \"${$_}" : \"$_", values %{$tmpls} ; + +#print Dumper $self->{templates} ; + + return ; +} + +sub delete_templates { + + my( $self, @names ) = @_ ; + + @names = keys %{$self->{templates}} unless @names ; + + delete @{$self->{templates}}{ @names } ; + + delete @{$self->{template_paths}}{ @names } ; + + return ; +} + +sub _get_template { + + my( $self, $tmpl_name ) = @_ ; + +#print "INC $tmpl_name\n" ; + + my $tmpls = $self->{templates} ; + +# get the template from the cache and send it back if it was found there + + my $template = $tmpls->{ $tmpl_name } ; + return $template if $template ; + +# not found, so find, slurp in and cache the template + + $template = $self->_find_template( $tmpl_name ) ; + $tmpls->{ $tmpl_name } = $template ; + + return $template ; +} + +sub _find_template { + + my( $self, $tmpl_name ) = @_ ; + + foreach my $dir ( @{$self->{include_paths}} ) { + + my $tmpl_path = "$dir/$tmpl_name.tmpl" ; + +#print "PATH: $tmpl_path\n" ; + next unless -r $tmpl_path ; + +# cache the path to this template + + $self->{template_paths}{$tmpl_name} = $tmpl_path ; + +# slurp in the template file and return it as a scalar ref + + return scalar read_file( $tmpl_path, scalar_ref => 1 ) ; + } + + die <{include_paths}}' +DIE + +} + +1; # End of Template::Simple + +__END__ + +=head1 NAME + +Template::Simple - A simple and fast template module + +=head1 VERSION + +Version 0.03 + +=head1 SYNOPSIS + + use Template::Simple; + + my $tmpl = Template::Simple->new(); + + my $template = < { + date => 'Jan 1, 2008', + author => 'Me, myself and I', + }, + row => [ + { + first => 'row 1 value 1', + second => 'row 1 value 2', + }, + { + first => 'row 2 value 1', + second => 'row 2 value 2', + }, + ], + footer => { + modified => 'Aug 31, 2006', + }, + } ; + + my $rendered = $tmpl->render( $template, $data ) ; + +=head1 DESCRIPTION + +Template::Simple has these goals: + +=over 4 + +=item * Support most common template operations + +It can recursively include other templates, replace tokens (scalars), +recursively render nested chunks of text and render lists. By using +simple idioms you can get conditional renderings. + +=item * Complete isolation of template from program code + +This is very important as template design can be done by different +people than the program logic. It is rare that one person is well +skilled in both template design and also programming. + +=item * Very simple template markup (only 4 markups) + +The only markups are C, C, C and C. See +MARKUP for more. + +=item * Easy to follow rendering rules + +Rendering of templates and chunks is driven from a data tree. The type +of the data element used in an rendering controls how the rendering +happens. The data element can be a scalar or scalar reference or an +array, hash or code reference. + +=item * Efficient template rendering + +Rendering is very simple and uses Perl's regular expressions +efficiently. Because the markup is so simple less processing is needed +than many other templaters. Precompiling templates is not supported +yet but that optimization is on the TODO list. + +=item * Easy user extensions + +User code can be called during an rendering so you can do custom +renderings and plugins. Closures can be used so the code can have its +own private data for use in rendering its template chunk. + +=back + +=head2 new() + +You create a Template::Simple by calling the class method new: + + my $tmpl = Template::Simple->new() ; + +All the arguments to C are key/value options that change how +the object will do renderings. + +=over 4 + +=item pre_delim + +This option sets the string or regex that is the starting delimiter +for all markups. You can use a plain string or a qr// but you need to +escape (with \Q or \) any regex metachars if you want them to be plain +chars. The default is qr/\[%/. + + my $tmpl = Template::Simple->new( + pre_delim => '<%', + ); + + my $rendered = $tmpl->render( '<%FOO%]', 'bar' ) ; + +=item post_delim + +This option sets the string or regex that is the ending delimiter +for all markups. You can use a plain string or a qr// but you need to +escape (with \Q or \) any regex metachars if you want them to be plain +chars. The default is qr/%]/. + + my $tmpl = Template::Simple->new( + post_delim => '%>', + ); + + my $rendered = $tmpl->render( '[%FOO%>', 'bar' ) ; + +=item greedy_chunk + +This boolean option will cause the regex that grabs a chunk of text +between the C markups to become greedy (.+). The default is +a not-greedy grab of the chunk text. (UNTESTED) + +=item templates + +This option lets you load templates directly into the cache of the +Template::Simple object. This cache will be searched by the C +markup which will be replaced by the template if found. The option +value is a hash reference which has template names (the name in the +C markup) for keys and their template text as their +values. You can delete or clear templates from the object cache with +the C method. + + + my $tmpl = Template::Simple->new( + templates => { + + foo => < <render( + $template, + { + baz => 'blue', + quux => 'color, + } + ) ; + +=item include_paths + +Template::Simple can also load C templates from files. This +option lets you set the directory paths to search for those +files. Note that the template name in the C markup has the +.tmpl suffix appended to it when searched for in one of these +paths. The loaded file is cached inside the Template::Simple object +along with any loaded by the C option. + +=back + +=head1 METHODS + +=head2 render + +This method is passed a template and a data tree and it renders it and +returns a reference to the resulting string. The template argument can +be a scalar or a scalar reference. The data tree argument can be any +value allowed by Template::Simple when rendering a template. It can +also be a blessed reference (Perl object) since +C is used instead of C to determine the +data type. + +Note that the author recommends against passing in an object as this +breaks encapsulation and forces your object to be (most likely) a +hash. It would be better to create a simple method that copies the +object contents to a hash reference and pass that. But current +templaters allow passing in objects so that is supported here as well. + + my $rendered = $tmpl->render( $template, $data ) ; + +=head2 add_templates + +This method adds templates to the object cache. It takes a list of template names and texts just like the C constructor option. + + $tmpl->add_templates( + { + foo => \$foo_template, + bar => '[%include bar%]', + } + ) ; + +=head2 delete_templates + +This method takes a list of template names and will delete them from +the template cache in the object. If you pass in an empty list then +all the templates will be deleted. This can be used when you know a +template file has been updated and you want to get it loaded back into +the cache. Note that you can delete templates that were loaded +directly (via the C constructor option or the +C method) or loaded from a file. + + # this deletes only the foo and bar templates from the object cache + + $tmpl->delete_templates( qw( foo bar ) ; + + # this deletes all of templates from the object cache + + $tmpl->delete_templates() ; + +=head2 get_dependencies + +This method render the only C markups of a template and it +returns a list of the file paths that were found and loaded. It is +meant to be used to build up a dependency list of included templates +for a main template. Typically this can be called from a script (see +TODO) that will do this for a set of main templates and will generate +Makefile dependencies for them. Then you can regenerate rendered +templates only when any of their included templates have changed. It +takes a single argument of a template. + +UNKNOWN: will this require a clearing of the cache or will it do the +right thing on its own? or will it use the file path cache? + + my @dependencies = + $tmpl->get_dependencies( '[%INCLUDE top_level%]' ); + +=head1 MARKUP + +All the markups in Template::Simple use the same delimiters which are +C<[%> and C<%]>. You can change the delimiters with the C +and C options in the C constructor. + +=head2 Tokens + +A token is a single markup with a C<\w+> Perl word inside. The token +can have optional whitespace before and after it. A token is replaced +by a value looked up in a hash with the token as the key. The hash +lookup keeps the same case as parsed from the token markup. + + [% foo %] [%BAR%] + +Those will be replaced by C<$href->{foo}> and C<$href->{BAR}> assuming +C<$href> is the current data for this rendering. Tokens are only +parsed out during hash data rendering so see Hash Data for more. + +=head2 Chunks + +Chunks are regions of text in a template that are marked off with a +start and end markers with the same name. A chunk start marker is +C<[%START name%]> and the end marker for that chunk is C<[%END +name%]>. C is a C<\w+> Perl word which is the name of this +chunk. The whitespace between C and C is required and +there is optional whitespace before C and after the +C. C are case insensitive but the C's case is +kept. C must match in the C pair and it used as a key +in a hash data rendering. Chunks are the primary way to markup +templates for structures (sets of tokens), nesting (hashes of hashes), +repeats (array references) and callbacks to user code. Chunks are only +parsed out during hash data rendering so see Hash Data for more. + +The body of text between the C markups is grabbed with a +C<.+?> regular expression with the /s option enabled so it will match +all characters. By default it will be a non-greedy grab but you can +change that in the constructor by enabling the C option. + + [%Start FOO%] + [% START bar %] + [% field %] + [% end bar %] + [%End FOO%] + +=head2 Includes + +=head1 RENDERING RULES + +Template::Simple has a short list of rendering rules and they are easy +to understand. There are two types of renderings, include rendering +and chunk rendering. In the C method, the template is an +unnamed top level chunk of text and it first gets its C +markups rendered. The text then undergoes a chunk rendering and a +scalar reference to that rendered template is returned to the caller. + +=head2 Include Rendering + +Include rendering is performed one time on a top level template. When +it is done the template is ready for chunk rendering. Any markup of +the form C<[%INCLUDE name]%> will be replaced by the text found in the +template C. The template name is looked up in the object's +template cache and if it is found there its text is used as the +replacement. + +If a template is not found in the cache, it will be searched for in +the list of directories in the C option. The file name +will be a directory in that list appended with the template name and +the C<.tmpl> suffix. The first template file found will be read in and +stored in the cache. Its path is also saved and those will be returned +in the C method. See the C and +C methods and the C option. + +Rendered include text can contain more C markups and they +will also be rendered. The include rendering phase ends where there +are no more C found. + +=head2 Chunk Rendering + +A chunk is the text found between C and C markups and it +gets its named from the C markup. The top level template is +considered an unamed chunk and also gets chunk rendered. + +The data for a chunk determines how it will be rendered. The data can +be a scalar or scalar reference or an array, hash or code +reference. Since chunks can contain nested chunks, rendering will +recurse down the data tree as it renders the chunks. Each of these +renderings are explained below. Also see the IDIOMS and BEST PRACTICES +section for examples and used of these renderings. + +=head2 Scalar Data Rendering + +If the current data for a chunk is a scalar or scalar reference, the +chunk's text in the templated is replaced by the scalar's value. This +can be used to overwrite one default section of text with from the +data tree. + +=head2 Code Data Rendering + +If the current data for a chunk is a code reference (also called +anonymous sub) then the code reference is called and it is passed a +scalar reference to the that chunk's text. The code must return a +scalar or a scalar reference and its value replaces the chunk's text +in the template. If the code returns any other type of data it is a +fatal error. Code rendering is how you can do custom renderings and +plugins. A key idiom is to use closures as the data in code renderings +and keep the required outside data in the closure. + +=head2 Array Data Rendering + +If the current data for a chunk is an array reference do a full chunk +rendering for each value in the array. It will replace the original +chunk text with the joined list of rendered chunks. This is how you do +repeated sections in Template::Simple and why there is no need for any +loop markups. Note that this means that rendering a chunk with $data +and [ $data ] will do the exact same thing. A value of an empty array +C<[]> will cause the chunk to be replaced by the empty string. + +=head2 Hash Data Rendering + +If the current data for a chunk is a hash reference then two phases of +rendering happen, nested chunk rendering and token rendering. First +nested chunks are parsed of of this chunk along with their names. Each +parsed out chunk is rendered based on the value in the current hash +with the nested chunk's name as the key. + +If a value is not found (undefined), then the nested chunk is replaced +by the empty string. Otherwise the nested chunk is rendered according +to the type of its data (see chunk rendering) and it is replaced by +the rendered text. + +Chunk name and token lookup in the hash data is case sensitive (see +the TODO for cased lookups). + +Note that to keep a plain text chunk or to just have the all of its +markups (chunks and tokens) be deleted just pass in an empty hash +reference C<{}> as the data for the chunk. It will be rendered but all +markups will be replaced by the empty string. + +=head2 Token Rendering + +The second phase is token rendering. Markups of the form [%token%] are +replaced by the value of the hash element with the token as the +key. If a token's value is not defined it is replaced by the empty +string. This means if a token key is missing in the hash or its value +is undefined or its value is the empty string, the [%token%] markup +will be deleted in the rendering. + +=head1 IDIOMS and BEST PRACTICES + +With all template systems there are better ways to do things and +Template::Simple is no different. This section will show some ways to +handle typical template needs while using only the 4 markups in this +module. + +=head2 Conditionals + +This conditional idiom can be when building a fresh data tree or +modifying an existing one. + + $href->{$chunk_name} = $keep_chunk ? {} : '' ; + +If you are building a fresh data tree you can use this idiom to do a +conditional chunk: + + $href->{$chunk_name} = {} if $keep_chunk ; + +To handle an if/else conditional use two chunks, with the else chunk's +name prefixed with NOT_ (or use any name munging you want). Then you +set the data for either the true chunk (just the plain name) or the +false trunk with the NOT_ name. You can use a different name for the +else chunk if you want but keeping the names of the if/else chunks +related is a good idea. Here are two ways to set the if/else data. The +first one uses the same data for both the if and else chunks and the +second one uses different data so the it uses the full if/else code +for that. + + $href->{ ($boolean ? '' : 'NOT_') . $chunk_name} = $data + + if ( $boolean ) { + $href->{ $chunk_name} = $true_data ; + else { + $href->{ "NOT_$chunk_name" } = $false_data ; + } + +NOTE TO ALPHA USERS: i am also thinking that a non-existing key or +undefined hash value should leave the chunk as is. then you would need +to explicitly replace a chunk with the empty string if you wanted it +deleted. It does affect the list of styles idiom. Any thoughts on +this change of behavior? Since this hasn't been released it is the +time to decide this. + +=head2 Chunked Includes + +One of the benefits of using include templates is the ability to share +and reuse existing work. But if an included template has a top level +named chunk, then that name would also be the same everywhere where +this template is included. If a template included another template in +multiple places, its data tree would use the same name for each and +not allow unique data to be rendered for each include. A better way is +to have the current template wrap an include markup in a named chunk +markup. Then the data tree could use unique names for each included +template. Here is how it would look: + + [%START foo_prime%][%INCLUDE foo%][%START foo_prime%] + random noise + [%START foo_second%][%INCLUDE foo%][%START foo_second%] + +See the TODO section for some ideas on how to make this even more high level. + +=head2 Repeated Sections + +If you looked at the markup of Template::Simple you have noticed that +there is no loop or repeat construct. That is because there is no need +for one. Any chunk can be rendered in a loop just by having its +rendering data be an anonymous array. The renderer will loop over each +element of the array and do a fresh rendering of the chunk with this +data. A join (on '') of the list of renderings replaces the original +chunk and you have a repeated chunk. + +=head2 A List of Mixed Styles + +One formating style is to have a list of sections each which can have +its own style or content. Template::Simple can do this very easily +with just a 2 level nested chunk and an array of data for +rendering. The outer chunk includes (or contains) each of the desired +styles in any order. It looks like this: + + [%START para_styles%] + [%START main_style%] + [%INCLUDE para_style_main%] + [%END main_style%] + [%START sub_style%] + [%INCLUDE para_style_sub%] + [%END sub_style%] + [%START footer_style%] + [%INCLUDE para_style_footer%] + [%END footer_style%] + [%END para_styles%] + +The other part to make this work is in the data tree. The data for +para_styles should be a list of hashes. Each hash contains the data +for one pargraph style which is keyed by the style's chunk name. Since +the other styles's chunk names are not hash they are deleted. Only the +style which has its name as a key in the hash is rendered. The data +tree would look something like this: + + [ + { + main_style => $main_data, + }, + { + sub_style => $sub_data, + }, + { + sub_style => $other_sub_data, + }, + { + footer_style => $footer_data, + }, + ] + +=head1 TESTS + +The test scripts use a common test driver module in t/common.pl. It is +passed a list of hashes, each of which has the data for one test. A +test can create a ne Template::Simple object or use the one from the +previous test. The template source, the data tree and the expected +results are also important keys. See the test scripts for examples of +how to write tests using this common driver. + +=over 4 + +=item name + +This is the name of the test and is used by Test::More + +=item opts + +This is a hash ref of the options passed to the Template::Simple +constructor. The object is not built if the C key is set. + +=item keep_obj + +If set, this will make this test keep the Template::Simple object from +the previous test and not build a new one. + +=item template + +This is the template to render for this test. If not set, the test +driver will use the template from the previous test. This is useful to +run a series of test variants with the same template. + +=item data + +This is the data tree for the rendering of the template. + +=item expected + +This is the text that is expected after the rendering. + +=item skip + +If set, this test is skipped. + +=back + +=head1 TODO + +Even though this template system is simple, that doesn't mean it can't +be extended in many ways. Here are some features and designs that +would be good extensions which add useful functionality without adding +too much complexity. + +=head2 Compiled Templates + +A commonly performed optimization in template modules is to precompile +(really preparse) templates into a internal form that will render +faster. Precompiling is slower than rendering from the original +template which means you won't want to do it for each rendering. This +means it has a downside that you lose out when you want to render +using templates which change often. Template::Simple makes it very +easy to precompile as it already has the regexes to parse out the +markup. So instead of calling subs to do actual rendering, a +precompiler would call subs to generate a compiled rendering tree. +The rendering tree can then be run or processes with rendering data +passed to it. You can think of a precompiled template as having all +the nested chunks be replaced by nested code that does the same +rendering. It can still do the dynamic rendering of the data but it +saves the time of parsing the template souice. There are three +possible internal formats for the precompiled template: + +=over 4 + +=item Source code + +This precompiler will generate source code that can be stored and/or +eval'ed. The eval'ed top level sub can then be called and passed the +rendering data. + +=item Closure call tree + +The internal format can be a nested set of closures. Each closure would contain +private data such as fixed text parts of the original template, lists +of other closures to run, etc. It is trivial to write a basic closure +generator which will make build this tree a simple task. + +=item Code ref call tree + +This format is a Perl data tree where the nodes have a code reference +and its args (which can be nested instances of the same +nodes). Instead of executing this directly, you will need a small +interpreter to execute all the code refs as it runs through the tree. + +This would make for a challenging project to any intermediate Perl +hacker. It just involves knowing recursion, data trees and code refs. +Contact me if you are interested in doing this. + +=back + +=head2 Cased Hash Lookups + +One possible option is to allow hash renderings to always use upper or +lower cased keys in their lookups. + +=head2 Render tokens before includes and chunks + +Currently tokens are rendered after includes and chunks. If tokens +were rendered in a pass before the others, the include and chunk names +could be dynamically set. This would make it harder to precompile +templates as too much would be dynamic, i.e. you won't know what the +fixed text to parse out is since anything can be included at render +time. But the extra flexibility of changing the include and chunk +names would be interesting. It could be done easily and enabled by an +option. + +=head2 Plugins + +There are two different potential areas in Template::Simple that could +use plugins. The first is with the rendering of chunkas and +dispatching based on the data type. This dispatch table can easily be +replaced by loaded modules which offer a different way to +render. These include the precompiled renderers mentioned above. The +other area is with code references as the data type. By defining a +closure (or a closure making) API you can create different code refs +for the rendering data. The range of plugins is endless some of the +major template modules have noticed. One idea is to make a closure +which contains a different Template::Simple object than the current +one. This will allow rendering of a nested chunk with different rules +than the current chunk being rendered. + +=head2 Data Escaping + +Some templaters have options to properly escape data for some types of +text files such as html. this can be done with some variant of the +_render_hash routine which also does the scalar rendering (which is +where data is rendered). The rendering scalars code could be factored +out into a set of subs one of which is used based on any escaping +needs. + +=head2 Data Tree is an Object + +This is a concept I don't like but it was requested so it goes into +the TODO file. Currently C can only be passed a regular +(unblessed) ref (or a scalar) for its data tree. Passing in an object +would break encapsulation and force the object layout to be a hash +tree that matches the layout of the template. I doubt that most +objects will want to be organized to match a template. I have two +ideas, one is that you add a method to that object that builds up a +proper (unblessed) data tree to pass to C. The other is by +subclassing C and overriding C with a sub +that does take an object hash and it can unbless it or build a proper +data tree and then call C in SUPER::. A quick solution is to +use C (from Scalar::Utils) instead of C to allow object +hashes to be passed in. + +=head2 Includes and Closure Synergy + +By pairing up an include template along with code that can generate +the appropriate data tree for its rendering, you can create a higher +level template framework (the synergy). Additional code can be +associated with them that will handle input processing and +verification for the templates (e.g. web forms) that need it. A key to +this will be making all the closures for the data tree. This can be +greatly simplified by using a closure maker sub that can create all +the required closures. + +=head2 Metafields and UI Generation + +Taking the synergy up to a much higher level is the concept of meta +knowledge of fields which can generate templates, output processing +(data tree generation), input processing, DB backing and more. If you +want to discuss such grandiose wacky application schemes in a long +rambling mind bending conversation, please contact me. + +=head2 More Examples and Idioms + +As I convert several scripts over to this module (they all used the +hack version), I will add them to an examples section or possibly put +them in another (pod only) module. Similarly the Idioms section needs +rendering and could be also put into a pod module. One goal requested +by an early alpha tester is to keep the primary docs as simple as the +markup itself. This means moving all the extra stuff (and plenty of +that) into other pod modules. All the pod modules would be in the same +cpan tarball so you get all the docs and examples when you install +this. + +=head1 AUTHOR + +Uri Guttman, C<< >> + +=head1 BUGS + +Please report any bugs or feature requests to +C, or through the web interface at +L. +I will be notified, and then you'll automatically be notified of progress on +your bug as I make changes. + +=head1 SUPPORT + +You can find documentation for this module with the perldoc command. + + perldoc Template::Simple + +You can also look for information at: + +=over 4 + +=item * RT: CPAN's request tracker + +L + +=item * Search CPAN + +L + +=back + +=head1 ACKNOWLEDGEMENTS + +I wish to thank Turbo10 for their support in developing this module. + +=head1 COPYRIGHT & LICENSE + +Copyright 2006 Uri Guttman, all rights reserved. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=cut + + +find templates and tests + +deep nesting tests + +greedy tests + +methods pod + +delete_templates test + +pod cleanup + +fine edit + +more tests + +slurp dependency in makefile.pl + diff --git a/lib/Template/Simple.pm.expnad b/lib/Template/Simple.pm.expnad new file mode 100644 index 0000000..d1801d5 --- /dev/null +++ b/lib/Template/Simple.pm.expnad @@ -0,0 +1,1049 @@ +package Template::Simple; + +use warnings; +use strict; + +use Carp ; +use File::Slurp ; + +use Data::Dumper ; + +our $VERSION = '0.01'; + +my %opt_defaults = ( + + pre_delim => qr/\[%/, + post_delim => qr/%\]/, + greedy_chunk => 0, +# upper_case => 0, +# lower_case => 0, + include_paths => [ qw( templates ) ], +) ; + +sub new { + + my( $class, %opts ) = @_ ; + + my $self = bless {}, $class ; + +# get all the options or defaults into the object + + while( my( $name, $default ) = each %opt_defaults ) { + + $self->{$name} = defined( $opts{$name} ) ? + $opts{$name} : $default ; + } + +# make up the regexes to parse the markup from templates + +# this matches scalar markups and grabs the name + + $self->{scalar_re} = qr{ + $self->{pre_delim} + \s* # optional leading whitespace + (\w+?) # grab scalar name + \s* # optional trailing whitespace + $self->{post_delim} + }xi ; # case insensitive + +#print "RE <$self->{scalar_re}>\n" ; + +# this grabs the body of a chunk in either greedy or non-greedy modes + + my $chunk_body = $self->{greedy_chunk} ? qr/.+/s : qr/.+?/s ; + +# this matches a marked chunk and grabs its name and text body + + $self->{chunk_re} = qr{ + $self->{pre_delim} + \s* # optional leading whitespace + START # required START token + \s+ # required whitespace + (\w+?) # grab the chunk name + \s* # optional trailing whitespace + $self->{post_delim} + ($chunk_body) # grab the chunk body + $self->{pre_delim} + \s* # optional leading whitespace + END # required END token + \s+ # required whitespace + \1 # match the grabbed chunk name + \s* # optional trailing whitespace + $self->{post_delim} + }xi ; # case insensitive + +#print "RE <$self->{chunk_re}>\n" ; + +# this matches a include markup and grabs its template name + + $self->{include_re} = qr{ + $self->{pre_delim} + \s* # optional leading whitespace + INCLUDE # required INCLUDE token + \s+ # required whitespace + (\w+?) # grab the included template name + \s* # optional trailing whitespace + $self->{post_delim} + }xi ; # case insensitive + +# load in any templates + + $self->add_templates( $opts{templates} ) ; + + return $self ; +} + + + +sub expand { + + my( $self, $template, $data ) = @_ ; + +# make a copy if a scalar ref is passed as the template text is +# modified in place + + my $tmpl_ref = ref $template eq 'SCALAR' ? $template : \$template ; + + my $expanded = $self->_expand_includes( $tmpl_ref ) ; + +#print "INC EXP <$expanded>\n" ; + + $expanded = eval { + $self->_expand_chunk( $expanded, $data ) ; + } ; + + croak "Template::Simple $@" if $@ ; + + return $expanded ; +} + +sub _expand_includes { + + my( $self, $tmpl_ref ) = @_ ; + +# make a copy of the initial template so we can expand it. + + my $expanded = ${$tmpl_ref} ; + +# loop until we can expand no more include markups + + 1 while $expanded =~ + s{$self->{include_re}} + { ${ $self->_get_template($1) } + }e ; + + return \$expanded ; +} + +my %expanders = ( + + HASH => \&_expand_hash, + ARRAY => \&_expand_array, + CODE => \&_expand_code, +# if no ref then data is a scalar so replace the template with just the data + '' => sub { \$_[2] }, +) ; + +sub _expand_chunk { + + my( $self, $tmpl_ref, $data ) = @_ ; + +#print "T ref [$tmpl_ref] [$$tmpl_ref]\n" ; +#print "CHUNK TMPL\n<$$tmpl_ref>\n" ; + +#print Dumper $data ; + + return \'' unless defined $data ; + +# now expand this chunk based on the type of data + + my $expander = $expanders{ref $data} ; + +#print "EXP $expander\nREF ", ref $data, "\n" ; + + die "unknown template data type '$data'\n" unless defined $expander ; + + return $self->$expander( $tmpl_ref, $data ) ; +} + +sub _expand_hash { + + my( $self, $tmpl_ref, $href ) = @_ ; + + return $tmpl_ref unless keys %{$href} ; + +# print "T ref [$tmpl_ref] [$$tmpl_ref]\n" ; +# print "HASH TMPL\n$$tmpl_ref\n" ; + +# we need a local copy of the template to expand + + my $expanded = ${$tmpl_ref} ; + +# recursively expand all top level chunks in this chunk + + $expanded =~ s{$self->{chunk_re}} + { + # print "CHUNK $1\nBODY\n----\n<$2>\n\n------\n" ; + ${$self->_expand_chunk( \$2, $href->{$1} ) }}gex ; + +# now expand scalars + +#print "HASH TMPL\n<$expanded>\n" ; +#print Dumper $href ; + + $expanded =~ s{$self->{scalar_re}} + { + #print "SCALAR $1 VAL $href->{$1}\n" ; + defined $href->{$1} ? $href->{$1} : '' }ge ; + +#print "HASH2 TMPL\n$$expanded\n" ; + + return \$expanded ; +} + +sub _expand_array { + + my( $self, $tmpl_ref, $aref ) = @_ ; + +# expand this $tmpl_ref for each element of the aref and join them + + my $expanded ; + +#print Dumper $aref ; + + $expanded .= ${$self->_expand_chunk( $tmpl_ref, $_ )} for @{$aref} ; + + return \$expanded ; +} + +sub _expand_code { + + my( $self, $tmpl_ref, $cref ) = @_ ; + + my $expanded = $cref->( $tmpl_ref ) ; + + croak <{templates}}{ keys %{$tmpls} } = + map ref $_ eq 'SCALAR' ? \"${$_}" : \"$_", values %{$tmpls} ; + +#print Dumper $self->{templates} ; + + return ; +} + +sub delete_templates { + + my( $self, @names ) = @_ ; + + @names = keys %{$self->{templates}} unless @names ; + + delete @{$self->{templates}}{ @names } ; + + delete @{$self->{template_paths}}{ @names } ; + + return ; +} + +sub _get_template { + + my( $self, $tmpl_name ) = @_ ; + +#print "INC $tmpl_name\n" ; + + my $tmpls = $self->{templates} ; + +# get the template from the cache and send it back if it was found there + + my $template = $tmpls->{ $tmpl_name } ; + return $template if $template ; + +# not found, so find, slurp in and cache the template + + $template = $self->_find_template( $tmpl_name ) ; + $tmpls->{ $tmpl_name } = $template ; + + return $template ; +} + +sub _find_template { + + my( $self, $tmpl_name ) = @_ ; + + foreach my $dir ( @{$self->{include_paths}} ) { + + my $tmpl_path = "$dir/$tmpl_name.tmpl" ; + +print "PATH: $tmpl_path\n" ; + next unless -r $tmpl_path ; + +# cache the path to this template + + $self->{template_paths}{$tmpl_name} = $tmpl_path ; + +# slurp in the template file and return it as a scalar ref + + return scalar read_file( $tmpl_path, scalar_ref => 1 ) ; + } + + croak <{include_paths}}' +CROAK + +} + +1; # End of Template::Simple + +__END__ + +=head1 NAME + +Template::Simple - A simple and fast template module + +=head1 VERSION + +Version 0.01 + +=head1 SYNOPSIS + + use Template::Simple; + + my $tmpl = Template::Simple->new(); + + my $template = < { + date => 'Jan 1, 2008', + author => 'Me, myself and I', + }, + row => [ + { + first => 'row 1 value 1', + second => 'row 1 value 2', + }, + { + first => 'row 2 value 1', + second => 'row 2 value 2', + }, + ], + footer_data => { + modified => 'Aug 31, 2006', + }, + } ; + + my $expanded = $tmpl->expand( $template, $data ) ; + +=head1 DESCRIPTION + +Template::Simple has these goals: + +=over 4 + +=item * Support most common template operations + +It can recursively include other templates, replace tokens (scalars), +recursively expand nested chunks of text and expand lists. By using +simple idioms you can get conditional expansions. + +=item * Complete isolation of template from program code + +This is very important as template design can be done by different +people than the program logic. It is rare that one person is well +skilled in both template design and also programming. + +=item * Very simple template markup (only 4 markups) + +The only markups are C, C, C and C. See +MARKUP for more. + +=item * Easy to follow expansion rules + +Expansion of templates and chunks is driven from a data tree. The type +of the data element used in an expansion controls how the expansion +happens. The data element can be a scalar or scalar reference or an +array, hash or code reference. + +=item * Efficient template expansion + +Expansion is very simple and uses Perl's regular expressions +efficiently. Because the markup is so simple less processing is needed +than many other templaters. Precompiling templates is not supported +yet but that optimization is on the TODO list. + +=item * Easy user extensions + +User code can be called during an expansion so you can do custom +expansions and plugins. Closures can be used so the code can have its +own private data for use in expanding its template chunk. + +=back + +=head2 new() + +You create a Template::Simple by calling the class method new: + + my $tmpl = Template::Simple->new() ; + +All the arguments to C are key/value options that change how +the object will do expansions. + +=over 4 + +=item pre_delim + +This option sets the string or regex that is the starting delimiter +for all markups. You can use a plain string or a qr// but you need to +escape (with \Q or \) any regex metachars if you want them to be plain +chars. The default is qr/\[%/. + + my $tmpl = Template::Simple->new( + pre_delim => '<%', + ); + + my $expanded = $tmpl->expand( '<%FOO%]', 'bar' ) ; + +=item post_delim + +This option sets the string or regex that is the ending delimiter +for all markups. You can use a plain string or a qr// but you need to +escape (with \Q or \) any regex metachars if you want them to be plain +chars. The default is qr/%]/. + + my $tmpl = Template::Simple->new( + post_delim => '%>', + ); + + my $expanded = $tmpl->expand( '[%FOO%>', 'bar' ) ; + +=item greedy_chunk + +This boolean option will cause the regex that grabs a chunk of text +between the C markups to become greedy (.+). The default is +a not-greedy grab of the chunk text. (UNTESTED) + +=item templates + +This option lets you load templates directly into the cache of the +Template::Simple object. This cache will be searched by the C +markup which will be replaced by the template if found. The option +value is a hash reference which has template names (the name in the +C markup) for keys and their template text as their +values. You can delete or clear templates from the object cache with +the C method. + + + my $tmpl = Template::Simple->new( + templates => { + + foo => < <expand( + $template, + { + baz => 'blue', + quux => 'color, + } + ) ; + +=item include_paths + +Template::Simple can also load C templates from files. This +option lets you set the directory paths to search for those +files. Note that the template name in the C markup has the +.tmpl suffix appended to it when searched for in one of these +paths. The loaded file is cached inside the Template::Simple object +along with any loaded by the C option. + +=back + +=head1 METHODS + +=head2 expand( $template, $data ) + +=head2 add_templates + +This method adds templates to the object cache. It takes a list of template names and texts just like the C constructor option. + + $tmpl->add_templates( + { + foo => \$foo_template, + bar => '[%include bar%]', + } + ) ; + +=head2 delete_templates + +This method takes a list of template names and will delete them from +the template cache in the object. If you pass in an empty list then +all the templates will be deleted. This can be used when you know a +template file has been updated and you want to get it loaded back into +the cache. Note that you can delete templates that were loaded +directly (via the C constructor option or the +C method) or loaded from a file. + + # this deletes only the foo and bar templates from the object cache + + $tmpl->delete_templates( qw( foo bar ) ; + + # this deletes all of templates from the object cache + + $tmpl->delete_templates() ; + +=head2 get_dependencies + +This method expand the only C markups of a template and it +returns a list of the file paths that were found and loaded. It is +meant to be used to build up a dependency list of included templates +for a main template. Typically this can be called from a script (see +TODO) that will do this for a set of main templates and will generate +Makefile dependencies for them. Then you can regenerate expanded +templates only when any of their included templates have changed. It +takes a single argument of a template. + +UNKNOWN: will this require a clearing of the cache or will it do the +right thing on its own? or will it use the file path cache? + + my @dependencies = + $tmpl->get_dependencies( '[%INCLUDE top_level%]' ); + +=head1 MARKUP + +All the markups in Template::Simple use the same delimiters which are +C<[%> and C<%]>. You can change the delimiters with the C +and C options in the C constructor. + +=head2 Tokens + +A token is a single markup with a C<\w+> Perl word inside. The token +can have optional whitespace before and after it. A token is replaced +by a value looked up in a hash with the token as the key. The hash +lookup keeps the same case as parsed from the token markup. + + [% foo %] [%BAR%] + +Those will be replaced by C<$href->{foo}> and C<$href->{BAR}> assuming +C<$href> is the current data for this expansion. Tokens are only +parsed out during hash data expansion so see Hash Data for more. + +=head2 Chunks + +Chunks are regions of text in a template that are marked off with a +start and end markers with the same name. A chunk start marker is +C<[%START name%]> and the end marker for that chunk is C<[%END +name%]>. C is a C<\w+> Perl word which is the name of this +chunk. The whitespace between C and C is required and +there is optional whitespace before C and after the +C. C are case insensitive but the C's case is +kept. C must match in the C pair and it used as a key +in a hash data expansion. Chunks are the primary way to markup +templates for structures (sets of tokens), nesting (hashes of hashes), +repeats (array references) and callbacks to user code. Chunks are only +parsed out during hash data expansion so see Hash Data for more. + +The body of text between the C markups is grabbed with a +C<.+?> regular expression with the /s option enabled so it will match +all characters. By default it will be a non-greedy grab but you can +change that in the constructor by enabling the C option. + + [%Start FOO%] + [% START bar %] + [% field %] + [% end bar %] + [%End FOO%] + +=head2 Includes + +=head1 EXPANSION RULES + +Template::Simple has a short list of expansion rules and they are easy +to understand. There are two types of expansions, include expansion +and chunk expansion. In the C method, the template is an +unnamed top level chunk of text and it first gets its C +markups expanded. The text then undergoes a chunk expansion and a +scalar reference to that expanded template is returned to the caller. + +=head2 Include Expansion + +Include expansion is performed one time on a top level template. When +it is done the template is ready for chunk expansion. Any markup of +the form C<[%INCLUDE name]%> will be replaced by the text found in the +template C. The template name is looked up in the object's +template cache and if it is found there its text is used as the +replacement. + +If a template is not found in the cache, it will be searched for in +the list of directories in the C option. The file name +will be a directory in that list appended with the template name and +the C<.tmpl> suffix. The first template file found will be read in and +stored in the cache. Its path is also saved and those will be returned +in the C method. See the C and +C methods and the C option. + +Expanded include text can contain more C markups and they +will also be expanded. The include expansion phase ends where there +are no more C found. + +=head2 Chunk Expansion + +A chunk is the text found between C and C markups and it +gets its named from the C markup. The top level template is +considered an unamed chunk and also gets chunk expanded. + +The data for a chunk determines how it will be expanded. The data can +be a scalar or scalar reference or an array, hash or code +reference. Since chunks can contain nested chunks, expansion will +recurse down the data tree as it expands the chunks. Each of these +expansions are explained below. Also see the IDIOMS and BEST PRACTICES +section for examples and used of these expansions. + +=head2 Scalar Data Expansion + +If the current data for a chunk is a scalar or scalar reference, the +chunk's text in the templated is replaced by the scalar's value. This +can be used to overwrite one default section of text with from the +data tree. + +=head2 Code Data Expansion + +If the current data for a chunk is a code reference (also called +anonymous sub) then the code reference is called and it is passed a +scalar reference to the that chunk's text. The code must return a +scalar or a scalar reference and its value replaces the chunk's text +in the template. If the code returns any other type of data it is a +fatal error. Code expansion is how you can do custom expansions and +plugins. A key idiom is to use closures as the data in code expansions +and keep the required outside data in the closure. + +=head2 Array Data Expansion + +If the current data for a chunk is an array reference do a full chunk +expansion for each value in the array. It will replace the original +chunk text with the joined list of expanded chunks. This is how you do +repeated sections in Template::Simple and why there is no need for any +loop markups. Note that this means that expanding a chunk with $data +and [ $data ] will do the exact same thing. A value of an empty array +C<[]> will cause the chunk to be replaced by the empty string. + +=head2 Hash Data Expansion + +If the current data for a chunk is a hash reference then two phases of +expansion happen, nested chunk expansion and token expansion. First +nested chunks are parsed of of this chunk along with their names. Each +parsed out chunk is expanded based on the value in the current hash +with the nested chunk's name as the key. + +If a value is not found (undefined), then the nested chunk is replaced +by the empty string. Otherwise the nested chunk is expanded according +to the type of its data (see chunk expansion) and it is replaced by +the expanded text. + +Chunk name and token lookup in the hash data is case sensitive (see +the TODO for cased lookups). + +Note that to keep a plain text chunk or to just have the all of its +markups (chunks and tokens) be deleted just pass in an empty hash +reference C<{}> as the data for the chunk. It will be expanded but all +markups will be replaced by the empty string. + +=head2 Token Expansion + +The second phase is token expansion. Markups of the form [%token%] are +replaced by the value of the hash element with the token as the +key. If a token's value is not defined it is replaced by the empty +string. This means if a token key is missing in the hash or its value +is undefined or its value is the empty string, the [%token%] markup +will be deleted in the expansion. + +=head1 IDIOMS and BEST PRACTICES + +With all template systems there are better ways to do things and +Template::Simple is no different. This section will show some ways to +handle typical template needs while using only the 4 markups in this +module. + +=head2 Conditionals + +This conditional idiom can be when building a fresh data tree or +modifying an existing one. + + $href->{$chunk_name} = $keep_chunk ? {} : '' ; + +If you are building a fresh data tree you can use this idiom to do a +conditional chunk: + + $href->{$chunk_name} = {} if $keep_chunk ; + +To handle an if/else conditional use two chunks, with the else chunk's +name prefixed with NOT_ (or use any name munging you want). Then you +set the data for either the true chunk (just the plain name) or the +false trunk with the NOT_ name. You can use a different name for the +else chunk if you want but keeping the names of the if/else chunks +related is a good idea. Here are two ways to set the if/else data. The +first one uses the same data for both the if and else chunks and the +second one uses different data so the it uses the full if/else code +for that. + + $href->{ ($boolean ? '' : 'NOT_') . $chunk_name} = $data + + if ( $boolean ) { + $href->{ $chunk_name} = $true_data ; + else { + $href->{ "NOT_$chunk_name" } = $false_data ; + } + +NOTE TO ALPHA USERS: i am also thinking that a non-existing key or +undefined hash value should leave the chunk as is. then you would need +to explicitly replace a chunk with the empty string if you wanted it +deleted. It does affect the list of styles idiom. Any thoughts on +this change of behavior? Since this hasn't been released it is the +time to decide this. + +=head2 Chunked Includes + +One of the benefits of using include templates is the ability to share +and reuse existing work. But if an included template has a top level +named chunk, then that name would also be the same everywhere where +this template is included. If a template included another template in +multiple places, its data tree would use the same name for each and +not allow unique data to be expanded for each include. A better way is +to have the current template wrap an include markup in a named chunk +markup. Then the data tree could use unique names for each included +template. Here is how it would look: + + [%START foo_prime%][%INCLUDE foo%][%START foo_prime%] + random noise + [%START foo_second%][%INCLUDE foo%][%START foo_second%] + +See the TODO section for some ideas on how to make this even more high level. + +=head2 Repeated Sections + +If you looked at the markup of Template::Simple you have noticed that +there is no loop or repeat construct. That is because there is no need +for one. Any chunk can be expanded in a loop just by having its +expansion data be an anonymous array. The expander will loop over each +element of the array and do a fresh expansion of the chunk with this +data. A join (on '') of the list of expansions replaces the original +chunk and you have a repeated chunk. + +=head2 A List of Mixed Styles + +One formating style is to have a list of sections each which can have +its own style or content. Template::Simple can do this very easily +with just a 2 level nested chunk and an array of data for +expansion. The outer chunk includes (or contains) each of the desired +styles in any order. It looks like this: + + [%START para_styles%] + [%START main_style%] + [%INCLUDE para_style_main%] + [%END main_style%] + [%START sub_style%] + [%INCLUDE para_style_sub%] + [%END sub_style%] + [%START footer_style%] + [%INCLUDE para_style_footer%] + [%END footer_style%] + [%END para_styles%] + +The other part to make this work is in the data tree. The data for +para_styles should be a list of hashes. Each hash contains the data +for one pargraph style which is keyed by the style's chunk name. Since +the other styles's chunk names are not hash they are deleted. Only the +style which has its name as a key in the hash is expanded. The data +tree would look something like this: + + [ + { + main_style => $main_data, + }, + { + sub_style => $sub_data, + }, + { + sub_style => $other_sub_data, + }, + { + footer_style => $footer_data, + }, + ] + +=head1 TESTS + +The test scripts use a common test driver module in t/common.pl. It is +passed a list of hashes, each of which has the data for one test. A +test can create a ne Template::Simple object or use the one from the +previous test. The template source, the data tree and the expected +results are also important keys. See the test scripts for examples of +how to write tests using this common driver. + +=over 4 + +=item name + +This is the name of the test and is used by Test::More + +=item opts + +This is a hash ref of the options passed to the Template::Simple +constructor. The object is not built if the C key is set. + +=item keep_obj + +If set, this will make this test keep the Template::Simple object from +the previous test and not build a new one. + +=item template + +This is the template to expand for this test. + +=item data + +This is the data tree for the expansion of the template. + +=item expected + +This is the text that is expected after the expansion. + +=item skip + +If set, this test is skipped. + +=back + +=head1 TODO + +Even though this template system is simple, that doesn't mean it can't +be extended in many ways. Here are some features and designs that +would be good extensions which add useful functionality without adding +too much complexity. + +=head2 Compiled Templates + +A commonly performed optimization in template modules is to precompile +(really preparse) templates into a internal form that will expand +faster. Precompiling is slower than expansion from the original +template which means you won't want to do it for each expansion. This +means it has a downside that you lose out when you want to expand +using templates which change often. Template::Simple makes it very +easy to precompile as it already has the regexes to parse out the +markup. So instead of calling subs to do actual expansion, a +precompiler would call subs to generate a compiled expansion tree. +The expansion tree can then be run or processes with expansion data +passed to it. You can think of a precompiled template as having all +the nested chunks be replaced by nested code that does the same +expansion. It can still do the dynamic expansion of the data but it +saves the time of parsing the template souice. There are three +possible internal formats for the precompiled template: + +=over 4 + +=item Source code + +This precompiler will generate source code that can be stored and/or +eval'ed. The eval'ed top level sub can then be called and passed the +expansion data. + +=item Closure call tree + +The internal format can be a nested set of closures. Each closure would contain +private data such as fixed text parts of the original template, lists +of other closures to run, etc. It is trivial to write a basic closure +generator which will make build this tree a simple task. + +=item Code ref call tree + +This format is a Perl data tree where the nodes have a code reference +and its args (which can be nested instances of the same +nodes). Instead of executing this directly, you will need a small +interpreter to execute all the code refs as it runs through the tree. + +This would make for a challenging project to any intermediate Perl +hacker. It just involves knowing recursion, data trees and code refs. +Contact me if you are interested in doing this. + +=back + +=head2 Cased Hash Lookups + +One possible option is to allow hash expansions to always use upper or +lower cased keys in their lookups. + +=head2 Expand tokens before includes and chunks + +Currently tokens are expanded after includes and chunks. If tokens +were expanded in a pass before the others, the include and chunk names +could be dynamically set. This would make it harder to precompile +templates as too much would be dynamic, i.e. you won't know what the +fixed text to parse out is since anything can be included at expand +time. But the extra flexibility of changing the include and chunk +names would be interesting. It could be done easily and enabled by an +option. + +=head2 Plugins + +There are two different potential areas in Template::Simple that could +use plugins. The first is with the expansion of chunkas and +dispatching based on the data type. This dispatch table can easily be +replaced by loaded modules which offer a different way to +expand. These include the precompiled expanders mentioned above. The +other area is with code references as the data type. By defining a +closure (or a closure making) API you can create different code refs +for the expansion data. The range of plugins is endless some of the +major template modules have noticed. One idea is to make a closure +which contains a different Template::Simple object than the current +one. This will allow expansion of a nested chunk with different rules +than the current chunk being expanded. + +=head2 Data Escaping + +Some templaters have options to properly escape data for some types of +text files such as html. this can be done with some variant of the +_expand_hash routine which also does the scalar expansion (which is +where data is expanded). The expanding scalars code could be factored +out into a set of subs one of which is used based on any escaping +needs. + +=head2 Data Tree is an Object + +This is a concept I don't like but it was requested so it goes into +the TODO file. Currently C can only be passed a regular +(unblessed) ref (or a scalar) for its data tree. Passing in an object +would break encapsulation and force the object layout to be a hash +tree that matches the layout of the template. I doubt that most +objects will want to be organized to match a template. I have two +ideas, one is that you add a method to that object that builds up a +proper (unblessed) data tree to pass to C. The other is by +subclassing C and overriding C with a sub +that does take an object hash and it can unbless it or build a proper +data tree and then call C in SUPER::. A quick solution is to +use C (from Scalar::Utils) instead of C to allow object +hashes to be passed in. + +=head2 Includes and Closure Synergy + +By pairing up an include template along with code that can generate +the appropriate data tree for its expansion, you can create a higher +level template framework (the synergy). Additional code can be +associated with them that will handle input processing and +verification for the templates (e.g. web forms) that need it. A key to +this will be making all the closures for the data tree. This can be +greatly simplified by using a closure maker sub that can create all +the required closures. + +=head2 Metafields and UI Generation + +Taking the synergy up to a much higher level is the concept of meta +knowledge of fields which can generate templates, output processing +(data tree generation), input processing, DB backing and more. If you +want to discuss such grandiose wacky application schemes in a long +rambling mind bending conversation, please contact me. + +=head2 More Examples and Idioms + +As I convert several scripts over to this module (they all used the +hack version), I will add them to an examples section or possibly put +them in another (pod only) module. Similarly the Idioms section needs +expansion and could be also put into a pod module. One goal requested +by an early alpha tester is to keep the primary docs as simple as the +markup itself. This means moving all the extra stuff (and plenty of +that) into other pod modules. All the pod modules would be in the same +cpan tarball so you get all the docs and examples when you install +this. + +=head1 AUTHOR + +Uri Guttman, C<< >> + +=head1 BUGS + +Please report any bugs or feature requests to +C, or through the web interface at +L. +I will be notified, and then you'll automatically be notified of progress on +your bug as I make changes. + +=head1 SUPPORT + +You can find documentation for this module with the perldoc command. + + perldoc Template::Simple + +You can also look for information at: + +=over 4 + +=item * RT: CPAN's request tracker + +L + +=item * Search CPAN + +L + +=back + +=head1 ACKNOWLEDGEMENTS + +I wish to thank Turbo10 for their support in developing this module. + +=head1 COPYRIGHT & LICENSE + +Copyright 2006 Uri Guttman, all rights reserved. + +This program is free software; you can redistribute it and/or modify it +under the same terms as Perl itself. + +=cut + + +find templates and tests + +deep nesting tests + +greedy tests + +methods pod + +delete_templates test + +pod cleanup + +fine edit + +more tests + +slurp dependency in makefile.pl + diff --git a/pm_to_blib b/pm_to_blib new file mode 100644 index 0000000..e69de29 diff --git a/s5-blank.zip b/s5-blank.zip new file mode 100644 index 0000000..d9c820e Binary files /dev/null and b/s5-blank.zip differ diff --git a/t/00-load.t b/t/00-load.t new file mode 100644 index 0000000..d53d3cc --- /dev/null +++ b/t/00-load.t @@ -0,0 +1,9 @@ +#!perl -T + +use Test::More tests => 1; + +BEGIN { + use_ok( 'Template::Simple' ); +} + +#diag( "Testing Template::Simple $Template::Simple::VERSION, Perl $], $^X" ); diff --git a/t/boilerplate.t b/t/boilerplate.t new file mode 100644 index 0000000..a256135 --- /dev/null +++ b/t/boilerplate.t @@ -0,0 +1,48 @@ +#!perl -T + +use strict; +use warnings; +use Test::More tests => 3; + +sub not_in_file_ok { + my ($filename, %regex) = @_; + open my $fh, "<", $filename + or die "couldn't open $filename for reading: $!"; + + my %violated; + + while (my $line = <$fh>) { + while (my ($desc, $regex) = each %regex) { + if ($line =~ $regex) { + push @{$violated{$desc}||=[]}, $.; + } + } + } + + if (%violated) { + fail("$filename contains boilerplate text"); + diag "$_ appears on lines @{$violated{$_}}" for keys %violated; + } else { + pass("$filename contains no boilerplate text"); + } +} + +not_in_file_ok(README => + "The README is used..." => qr/The README is used/, + "'version information here'" => qr/to provide version information/, +); + +not_in_file_ok(Changes => + "placeholder date/time" => qr(Date/time) +); + +sub module_boilerplate_ok { + my ($module) = @_; + not_in_file_ok($module => + 'the great new $MODULENAME' => qr/ - The great new /, + 'boilerplate description' => qr/Quick summary of what the module/, + 'stub function definition' => qr/function[12]/, + ); +} + +module_boilerplate_ok('lib/Template/Simple.pm'); diff --git a/t/bug.pl b/t/bug.pl new file mode 100644 index 0000000..3e8f503 --- /dev/null +++ b/t/bug.pl @@ -0,0 +1,79 @@ +#!perl + +use strict ; +use lib qw(t) ; +use common ; + +use File::Slurp ; +use Data::Dumper ; + +my $tests = [ + + { + name => 'bug', + skip => 0, + opts => { + + pre_delim => qr/\[\-/, + post_delim => qr/\-\]/, + }, + }, + data => { + widgets => [ + { + title => "bart" + }, + { + title => "marge", + } + ], + }, + + + + + [-start widgets-] + + + + + + + [-end widgets-] +
[-anchor-] + [-title-] +
[-description-] +
[-escaped_anchor-][-options-]
+ + + expected => 'bar', + }, +] ; + + +write_tmpl_files() ; + +template_tester( $tests ) ; + +#remove_tmpl_files() ; + +exit ; + + +sub write_tmpl_files { + + mkdir $_, 0755 for @tmpl_dirs ; + + while( my( $file, $tmpl ) = each %tmpl_files ) { + + write_file( $file, $tmpl ) ; + } +} + +sub remove_tmpl_files { + + unlink keys %tmpl_files ; + + rmdir $_ for reverse @tmpl_dirs ; +} +#!/usr/local/bin/perl diff --git a/t/common.pm b/t/common.pm new file mode 100644 index 0000000..d61d67c --- /dev/null +++ b/t/common.pm @@ -0,0 +1,102 @@ +# common.pm - common test driver code + +use Test::More ; +use Template::Simple ; + +sub template_tester { + + my( $tests ) = @_ ; + +# plan for one expected ok() call per test + + plan( tests => scalar @{$tests} ) ; + + my( $obj, $tmpl ) ; + +# loop over all the tests + + foreach my $test ( @{$tests} ) { + + if ( $test->{skip} ) { + ok( 1, "SKIPPING $test->{name}" ) ; + next ; + } + + unless( $obj && $test->{keep_obj} ) { + +# if there is no kept object, we will constuct one + + $obj = eval { + Template::Simple->new( + %{ $test->{opts} || {} } + ) ; + } ; + +print $@ if $@ ; + +# check for expected errors +# no errors in new() to catch (yet) + + } + + $test->{obj} = $obj ; + +# see if we use the test's template or keep the previous one + + $tmpl = $test->{template} if defined $test->{template} ; + +# run any setup sub before this test. this can is used to modify the +# object for this test (e.g. delete templates from the cache). + + if( my $pretest = $test->{pretest} ) { + + $pretest->($test) ; + } + +# get any existing template object + +# render the template and catch any fatal errors + + my $rendered = eval { + $obj->render( $tmpl, $test->{data} ) ; + } ; + +#print "ERR $@\n" if $@; + +# if we had an error and expected it, we pass this test + + if ( $@ ) { + + if ( $test->{error} && $@ =~ /$test->{error}/ ) { + + ok( 1, $test->{name} ) ; + } + else { + + print "unexpected error: $@\n" ; + ok( 0, $test->{name} ) ; + } + next ; + } + +# see if the expansion was what we expected + + my $ok = ${$rendered} eq $test->{expected} ; + +# dump any bad expansions + + print <{expected}] +------ +ERR + +# report success/failure for this test + + ok( $ok, $test->{name} ) ; + } +} + +1 ; diff --git a/t/error.t b/t/error.t new file mode 100644 index 0000000..1579882 --- /dev/null +++ b/t/error.t @@ -0,0 +1,43 @@ +#!perl + +use lib qw(t) ; +use common ; + +my $tests = [ + + { + name => 'unknown data type', + opts => {}, + data => qr//, + template => < < qr/unknown template data/, + }, + + { + name => 'missing include', + skip => 0, + data => {}, + template => '[%INCLUDE foo%]', + error => qr/can't find/, + }, + + { + name => 'code data', + skip => 0, + data => sub { return '' }, + template => 'bar', + error => qr/data callback/, + }, + + +] ; + +template_tester( $tests ) ; + +exit ; + diff --git a/t/include.t b/t/include.t new file mode 100644 index 0000000..4352416 --- /dev/null +++ b/t/include.t @@ -0,0 +1,159 @@ +#!perl + +use strict ; +use lib qw(t) ; +use common ; + +use File::Slurp ; +use Data::Dumper ; + +# these dirs must be in order to the deepest for rmdir to work properly + +my @tmpl_dirs = qw( templates templates/deeper templates/deeper/deepest ) ; + +my %tmpl_files = ( + + 'templates/FOO.tmpl' => < +FOO + 'templates/deeper/BAR.tmpl' => < <