├── .gitignore ├── ChangeLog ├── LICENSE ├── MANIFEST ├── MANIFEST.SKIP ├── META.yml ├── Makefile.PL ├── PLUGINS.md ├── README.md ├── bin ├── build-templer ├── templer └── templer-generate ├── examples ├── breadcrumbs │ ├── input │ │ ├── index.skx │ │ ├── robots.txt │ │ └── software │ │ │ ├── example │ │ │ └── index.skx │ │ │ └── index.skx │ ├── layouts │ │ └── default.layout │ └── templer.cfg ├── complex │ ├── input │ │ ├── dark.css │ │ ├── file.skx │ │ ├── glob.skx │ │ ├── img │ │ │ ├── cat1.jpg │ │ │ └── cat2.jpg │ │ ├── index.skx │ │ ├── jquery.min.js │ │ ├── layout.skx │ │ ├── page.skx │ │ ├── sidebar.inc │ │ ├── structure.skx │ │ ├── style.css │ │ ├── stylesheet.skx │ │ └── variables.skx │ ├── layouts │ │ └── default.layout │ └── templer.cfg └── simple │ ├── input │ ├── about.skx │ ├── foo.skx │ ├── index.skx │ └── robots.txt │ ├── layouts │ └── default.layout │ └── templer.cfg ├── lib ├── App │ └── Templer.pm └── Templer │ ├── Global.pm │ ├── Plugin │ ├── Breadcrumbs.pm │ ├── Dollar.pm │ ├── Factory.pm │ ├── FileContents.pm │ ├── FileGlob.pm │ ├── HTML.pm │ ├── Hash.pm │ ├── Markdown.pm │ ├── Perl.pm │ ├── RSS.pm │ ├── Redis.pm │ ├── RootPath.pm │ ├── ShellCommand.pm │ ├── SiteMap.pm │ ├── Strict.pm │ ├── Textile.pm │ └── TimeStamp.pm │ ├── Site.pm │ ├── Site │ ├── Asset.pm │ ├── New.pm │ └── Page.pm │ └── Timer.pm ├── t ├── style-no-tabs.t ├── style-no-trailing-whitespace.t ├── test-dependencies.t ├── test-pod-syntax.t ├── test-strict.t ├── test-templer-asset-copying.t ├── test-templer-page-expansion.t ├── test-templer-plugin-factory-formatters.t ├── test-templer-plugin-factory.t ├── test-templer-plugin-filecontents.t ├── test-templer-plugin-fileglob.t ├── test-templer-plugin-filters.t ├── test-templer-plugin-redis.t ├── test-templer-plugin-rootpath.t ├── test-templer-plugin-shellcommand.t ├── test-templer-plugin-timestamp.t ├── test-templer-site-new.t ├── test-templer-site-synopsis.t ├── test-templer-site.t └── test-templer-synchronization.t └── templer.cfg.sample /.gitignore: -------------------------------------------------------------------------------- 1 | examples/complex/output/ 2 | examples/simple/output/ 3 | examples/symlinks/output/ 4 | examples/breadcrumbs/output/ 5 | debian/templer 6 | debian/*.log 7 | debian/*.substvars 8 | debian/files 9 | 10 | Makefile 11 | Makefile.old 12 | 13 | blib/ 14 | pm_to_blib 15 | MYMETA.yml 16 | templer 17 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | templer (1.3-1) stable; urgency=low 2 | 3 | * Added explicit dependency upon `Pod::Find` and `Pod::Usage` 4 | 5 | -- Steve Kemp Thu, 13 Jan 2021 21:55:12 +0200 6 | 7 | templer (1.2-1) stable; urgency=low 8 | 9 | * Only invoke plugin methods that exist. 10 | * Added Templer::Plugin::SiteMap 11 | * This generates /sitemap.xml file, if configured to do so. 12 | 13 | -- Steve Kemp Sun, 10 Jul 2016 12:44:21 +0000 14 | 15 | templer (1.1-1) stable; urgency=low 16 | 17 | * Added `init` method to our plugin interface. 18 | 19 | -- Steve Kemp Sun, 10 Jul 2016 10:33:01 +0000 20 | 21 | templer (1.0-1) stable; urgency=low 22 | 23 | * Avoid running tests if Redis isn't available/running. 24 | 25 | -- Steve Kemp Sun, 20 Sep 2015 18:00:00 +0000 26 | 27 | templer (0.9-9-1) stable; urgency=low 28 | 29 | * Bumped version to 0.9.9.1 30 | * Updated copyright year to 2015. 31 | * Moved version from Templer::VERSION to App::Templer::VERSION. 32 | 33 | -- Steve Kemp Sat, 18 Apr 2015 16:55:60 +0000 34 | 35 | templer (0.9-9) stable; urgency=low 36 | 37 | * Updated POD files to fix a minor error. 38 | * Bumped copyright years to 2014. 39 | * Updated Makefile.PL to declare test-dependencies correctly. 40 | 41 | -- Steve Kemp Sat, 25 Oct 2014 08:18:28 +0000 42 | 43 | templer (0.9-8) stable; urgency=low 44 | 45 | * This release fixes a bug where L wouldn't be invoked 46 | correctly by the file_glob() plugin - because it would use the 47 | wrong file location. 48 | * Previous release changelogs aren't available, but since 0.9.2 the 49 | only changes applied related to the CPAN Makefile.PL and MANIFEST 50 | files. 51 | 52 | -- Steve Kemp Thu, 11 Sep 2014 07:44:07 +0000 53 | 54 | templer (0.9-2) stable; urgency=low 55 | 56 | * Added a the missing dependency Test::Exception to allow the test suite 57 | to pass. 58 | 59 | -- Steve Kemp Sun, 15 Jun 2014 07:08:09 +0000 60 | 61 | templer (0.9-1) stable; urgency=low 62 | 63 | * Install in a CPAN-like manner, rather than our older system. 64 | 65 | -- Steve Kemp Sun, 8 Jun 2014 08:44:00 +0000 66 | 67 | templer (0.8-1) stable; urgency=low 68 | 69 | * When the dependencies aren't available for the various formatting 70 | plugins let the user know. 71 | 72 | -- Steve Kemp Sat, 1 Feb 2014 10:00:01 +0000 73 | 74 | templer (0.7-1) stable; urgency=low 75 | 76 | * Updated the file-glob plugin to allow the content of files to be 77 | available - for non-images. 78 | 79 | -- Steve Kemp Fri, 27 Dec 2013 10:00:01 +0000 80 | 81 | templer (0.6.5-1) stable; urgency=low 82 | 83 | * New plugins: 84 | - RootPath 85 | - TimeStamp 86 | * Cleaned up and simplified coding. 87 | * Additional test-cases. 88 | * Rebuild pages when referenced include-files change. 89 | 90 | -- Steve Kemp Mon, 1 Apr 2013 11:20:02 +0000 91 | 92 | templer (0.6-1) stable; urgency=low 93 | 94 | * Updated to keep track of dependencies for input-pages. 95 | This means if a page includes a file and the include file changes 96 | then the output page will be rebuilt. 97 | 98 | -- Steve Kemp Sun, 10 Mar 2013 14:15:16 +0000 99 | 100 | templer (0.5-1) stable; urgency=low 101 | 102 | * Simplified the command-line handling, by moving all options to 103 | the configuration file. 104 | * Work in sub-directories, not just the top-level directory. 105 | * Reorganized code to be more logical. 106 | * Pages may setup their own output filename. 107 | 108 | -- Steve Kemp Sat, 09 Mar 2013 15:16:17 +0000 109 | 110 | templer (0.4-1) stable; urgency=low 111 | 112 | * Added release target to the makefile. 113 | * Bundle up changes. 114 | 115 | -- Steve Kemp Thu, 28 Feb 2013 22:33:44 +0000 116 | 117 | templer (0.3-1) stable; urgency=low 118 | 119 | * Updated to build with our modular code-base. 120 | 121 | -- Steve Kemp Fri, 4 Jan 2012 08:33:08 +0000 122 | 123 | templer (0.2-1) stable; urgency=low 124 | 125 | * Added the new script "templer-generate" 126 | 127 | -- Steve Kemp Sun, 30 Dec 2012 08:33:08 +0000 128 | 129 | templer (0.1-1) stable; urgency=low 130 | 131 | * Initial release 132 | 133 | -- Steve Kemp Mon, 03 Dec 2012 08:44:08 +0000 134 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This module is free software; you can redistribute it and/or modify it 2 | under the terms of either: 3 | 4 | a) the GNU General Public License as published by the Free Software 5 | Foundation; either version 2, or (at your option) any later version, 6 | or 7 | 8 | b) the Perl "Artistic License". 9 | 10 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | bin/build-templer 2 | bin/templer 3 | bin/templer-generate 4 | ChangeLog 5 | examples/breadcrumbs/input/index.skx 6 | examples/breadcrumbs/input/robots.txt 7 | examples/breadcrumbs/input/software/example/index.skx 8 | examples/breadcrumbs/input/software/index.skx 9 | examples/breadcrumbs/layouts/default.layout 10 | examples/breadcrumbs/templer.cfg 11 | examples/complex/input/dark.css 12 | examples/complex/input/file.skx 13 | examples/complex/input/glob.skx 14 | examples/complex/input/img/cat1.jpg 15 | examples/complex/input/img/cat2.jpg 16 | examples/complex/input/index.skx 17 | examples/complex/input/jquery.min.js 18 | examples/complex/input/layout.skx 19 | examples/complex/input/page.skx 20 | examples/complex/input/sidebar.inc 21 | examples/complex/input/structure.skx 22 | examples/complex/input/style.css 23 | examples/complex/input/stylesheet.skx 24 | examples/complex/input/variables.skx 25 | examples/complex/layouts/default.layout 26 | examples/complex/templer.cfg 27 | examples/simple/input/about.skx 28 | examples/simple/input/foo.skx 29 | examples/simple/input/index.skx 30 | examples/simple/input/robots.txt 31 | examples/simple/layouts/default.layout 32 | examples/simple/templer.cfg 33 | lib/App/Templer.pm 34 | lib/Templer/Global.pm 35 | lib/Templer/Plugin/Breadcrumbs.pm 36 | lib/Templer/Plugin/Dollar.pm 37 | lib/Templer/Plugin/Factory.pm 38 | lib/Templer/Plugin/FileContents.pm 39 | lib/Templer/Plugin/FileGlob.pm 40 | lib/Templer/Plugin/Hash.pm 41 | lib/Templer/Plugin/HTML.pm 42 | lib/Templer/Plugin/Markdown.pm 43 | lib/Templer/Plugin/Perl.pm 44 | lib/Templer/Plugin/Redis.pm 45 | lib/Templer/Plugin/RootPath.pm 46 | lib/Templer/Plugin/RSS.pm 47 | lib/Templer/Plugin/ShellCommand.pm 48 | lib/Templer/Plugin/SiteMap.pm 49 | lib/Templer/Plugin/Strict.pm 50 | lib/Templer/Plugin/Textile.pm 51 | lib/Templer/Plugin/TimeStamp.pm 52 | lib/Templer/Site.pm 53 | lib/Templer/Site/Asset.pm 54 | lib/Templer/Site/New.pm 55 | lib/Templer/Site/Page.pm 56 | lib/Templer/Timer.pm 57 | LICENSE 58 | Makefile.PL 59 | MANIFEST This list of files 60 | META.yml 61 | PLUGINS.md 62 | README.md 63 | t/style-no-tabs.t 64 | t/style-no-trailing-whitespace.t 65 | t/test-dependencies.t 66 | t/test-pod-syntax.t 67 | t/test-strict.t 68 | t/test-templer-asset-copying.t 69 | t/test-templer-page-expansion.t 70 | t/test-templer-plugin-factory-formatters.t 71 | t/test-templer-plugin-factory.t 72 | t/test-templer-plugin-filecontents.t 73 | t/test-templer-plugin-fileglob.t 74 | t/test-templer-plugin-filters.t 75 | t/test-templer-plugin-redis.t 76 | t/test-templer-plugin-rootpath.t 77 | t/test-templer-plugin-shellcommand.t 78 | t/test-templer-plugin-timestamp.t 79 | t/test-templer-site-new.t 80 | t/test-templer-site.t 81 | t/test-templer-synchronization.t 82 | templer.cfg.sample 83 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | Makefile$ 2 | Makefile.old 3 | MYMETA.yml 4 | TODO 5 | CVS 6 | pm_to_blib 7 | docs 8 | .svn 9 | ^cover_db 10 | ^report 11 | ^blib 12 | ^Build$ 13 | ^_build 14 | \.bak$ 15 | \.orig$ 16 | \.rej$ 17 | \.mine$ 18 | \.gz$ 19 | \.r\d+$ 20 | .*sessiondata.* 21 | .*cgisess.* 22 | .shipit 23 | ^.git 24 | ^.hg 25 | -------------------------------------------------------------------------------- /META.yml: -------------------------------------------------------------------------------- 1 | --- #YAML:1.0 2 | name: App-Templer 3 | version: 1.4-0 4 | abstract: Extensible Static Site Generator. 5 | author: 6 | - Steve Kemp 7 | license: perl_5 8 | distribution_type: module 9 | configure_requires: 10 | ExtUtils::MakeMaker: 0 11 | build_requires: 12 | ExtUtils::MakeMaker: 0 13 | requires: 14 | Getopt::Long: 0 15 | HTML::Template: 0 16 | Module::Pluggable: 0 17 | Pod::Find: 0 18 | Pod::Usage: 0 19 | Test::Exception: 0 20 | Test::More: 0 21 | Test::NoTabs: 0 22 | resources: 23 | bugtracker: https://github.com/skx/templer/issues 24 | homepage: https://github.com/skx/templer/ 25 | license: http://dev.perl.org/licenses/ 26 | repository: https://github.com/skx/templer.git 27 | no_index: 28 | directory: 29 | - t 30 | - inc 31 | generated_by: ExtUtils::MakeMaker version 6.57_05 32 | meta-spec: 33 | url: http://module-build.sourceforge.net/META-spec-v1.4.html 34 | version: 1.4 35 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use 5.008; 5 | 6 | use ExtUtils::MakeMaker 6.48; 7 | 8 | 9 | 10 | my %WriteMakefileArgs = ( 11 | NAME => 'App::Templer', 12 | VERSION_FROM => 'lib/App/Templer.pm', 13 | EXE_FILES => ['bin/templer', 'bin/templer-generate'], 14 | PREREQ_PM => { 15 | 16 | # These are hard requirements. 17 | 'HTML::Template' => 0, 18 | 'Getopt::Long' => 0, 19 | 'Module::Pluggable' => 0, 20 | 21 | # These are OPTIONAL. 22 | "Image::Size" => 0, 23 | "Redis" => 0, 24 | "Text::Markdown" => 0, 25 | "Text::Template" => 0, 26 | "Text::Textile" => 0, 27 | 28 | # solely for the test-suite. 29 | 'Test::More' => 0, 30 | 'Test::Pod' => 0, 31 | 'Test::Strict' => 0, 32 | 'Test::Exception' => 0, 33 | 'Test::NoTabs' => 0, 34 | 35 | }, 36 | ABSTRACT => 'Extensible Static Site Generator.', 37 | AUTHOR => 'Steve Kemp ', 38 | LICENSE => 'perl', 39 | 40 | MIN_PERL_VERSION => '5.008', 41 | 42 | META_MERGE => { 43 | resources => { 44 | license => 'http://dev.perl.org/licenses/', 45 | homepage => 'https://github.com/skx/templer/', 46 | bugtracker => 'https://github.com/skx/templer/issues', 47 | repository => 'https://github.com/skx/templer.git', 48 | }, 49 | }, 50 | ); 51 | 52 | 53 | # 54 | # This gives a custom-target so that we can run: 55 | # 56 | # perl Makefile.PL 57 | # make standalone 58 | # 59 | # This will generate ./templer which is complete. 60 | # 61 | sub MY::postamble { 62 | return <<'MAKE_FRAG'; 63 | standalone: 64 | perl bin/build-templer 65 | MAKE_FRAG 66 | } 67 | 68 | WriteMakefile(%WriteMakefileArgs); 69 | -------------------------------------------------------------------------------- /PLUGINS.md: -------------------------------------------------------------------------------- 1 | Plugins 2 | -------- 3 | 4 | Templer allows itself to be extended via the addition of plugins. 5 | 6 | The following formatting plugins are distributed as part of the project: 7 | 8 | * `Templer::Plugin::Markdown` 9 | * Allows input files to be written in Markdown. 10 | * `Templer::Plugin::Perl` 11 | * Allows dynamic Perl to be included in your input pages. 12 | * `Templer::Plugin::Textile` 13 | * Allows input files to be written in Textile. 14 | 15 | The following variable plugins are distributed as part of the project: 16 | 17 | * `Templer::Plugin::Breadcrumbs` 18 | * Setup "breadcrumb" trails in your templates easily. 19 | * [Read the documentation](https://raw.github.com/skx/templer/master/lib/Templer/Plugin/Breadcrumbs.pm). 20 | * This was added partly as a demo, and partly for [use on my site](http://steve.org.uk/Software/templer/). 21 | * `Templer::Plugin::FileContents` 22 | * Set variable values to the contents of files. 23 | * `Templer::Plugin::FileGlob` 24 | * Set variable values to lists of files, based on a globbing pattern. 25 | * dirname, basename and file extension are made available. 26 | * If the glob matches images then heights and widths will be available to your HTML. 27 | * If the glob doesn't match images then the contents of the files will also be made available. 28 | * If the glob matches templer input files then templer input variables will be available to your HTML. 29 | * `Templer::Plugin::ShellCommand` 30 | * Set variable values to the output of shell commands. 31 | * `Templer::Plugin::RootPath` 32 | * Allow access to your site prefix, without hardcoding it. 33 | * `Templer::Plugin::RSS` 34 | * Allow pages to include remote RSS feed data. 35 | * `Templer::Plugin::Redis` 36 | * Allow variables to be retrieved from a Redis store running on the local system. 37 | * `Templer::Plugin::Timestamp` 38 | * Allow pages to contain their own modification timestamp. 39 | 40 | The following template filter plugins are distributed as part of the project: 41 | 42 | * `Templer::Plugin::Dollar` 43 | * Allow template variables to be written using a simple shell-like syntax. 44 | * `Templer::Plugin::Strict` 45 | * Allow template tags to be written as empty-element tags conforming to XML syntax. 46 | 47 | If you wish you may contain write your own plugins, contained beneath your 48 | templer-site. The default [templer.cfg](templer.cfg.sample) documents the 49 | `plugin-path` setting. 50 | 51 | 52 | Plugin Types 53 | ------------ 54 | 55 | There are three types of plugins which are supported: 56 | 57 | * Plugins which are used for formatting. 58 | * These convert your input files from Textile, Markdown, etc, into HTML. 59 | 60 | * Plugins which present variables for use in your template(s). 61 | * Creating variables that refer to file contents, file globs, etc. 62 | 63 | * Plugins which filter content of your template(s). 64 | * Simplify the way template can be written (escaping `HTML::Template` syntax which is too rigid for some text-editors facility, namely `nxml-mode` in Emacs for instance). 65 | 66 | Although similar there is a different API for the three plugin-families. 67 | 68 | 69 | ### Formatter Plugins 70 | 71 | The formatting plugins are intentionally simple because they are explicitly 72 | enabled on a per-page basis. There is no need to dynamically try them all in 73 | turn, executing whichever matches a particular condition, for example. 74 | 75 | A standard input page-file might look like this: 76 | 77 | Title: My page title. 78 | Format: textile 79 | ---- 80 | This is a textile page. It has **bold** text! 81 | 82 | When this page is rendered the Textile plugin is created and it is then called 83 | like so: 84 | 85 | if ( $plugin->available() ) 86 | { 87 | $html = $plugin->format( $input ); 88 | } 89 | 90 | If the named formatter is not present, or does not report itself as "enabled" 91 | then the markup will be returned without any expansion. To be explicit 92 | any formatter plugin must implement only the following two methods: 93 | 94 | * `available` 95 | * To determine whether this plugin is available. 96 | * i.e. It might only be enabled if the modules it relies upon are present. 97 | * `format` 98 | * Given some input text return the rendered content. 99 | * This method receives all the per-page and global variables. 100 | 101 | 102 | ### Filter Plugins 103 | 104 | The template filter plugin is similar in use as the formatter one. The only 105 | difference is at the level it operates. While a formatter plugin operates on 106 | the content of the page, a filter one operates directly on the core template 107 | engine allowing one to escape `HTML::Template` rigid syntax. 108 | 109 | A standard input page-file might look like this: 110 | 111 | Title: My page title. 112 | template-filter: dollar 113 | ---- 114 | This is a html page with a ${title}. 115 | 116 | A standard template layout might look like this: 117 | 118 | ${title escape=html} 119 | 120 | When this page is rendered the Dollar plugin is created and is used to add a 121 | filter to the `HTML::Template` object creation (through the `filter` property 122 | of the `HTML::Template->new` method). 123 | 124 | If the named filter is not present, or does not report itself as "enabled" 125 | then the filter is just not used. To be explicit any filter plugin must 126 | implement only the following two methods: 127 | 128 | * `available` 129 | * To determine whether this plugin is available. 130 | * i.e. It might only be enabled if the modules it relies upon are present. 131 | * `filter` 132 | * Given some input text (read template) return the filtered template. 133 | 134 | 135 | ### Variable Expansion Plugins 136 | 137 | For the variable-expansion plugins the approach is similar, but an arbitrary 138 | number of plugins may be registered and each one is executed in turn - so 139 | return values are chained. 140 | 141 | Each site page is loaded and the variable names & values are stored in a hash. 142 | Each plugin is free to modify that hash of known variables and their values. 143 | 144 | Generally we expect that plugins will look for variable values having a 145 | particular pattern and ignoring those that don't match. But there is 146 | certainly no reason why you couldn't write a plugin to convert each 147 | variable-value to uppercase, or perform other global operations. 148 | 149 | In pseudo-code the processing looks like this: 150 | 151 | $data = ( "foo" => "bar", 152 | title => "This is my page title .." ); 153 | 154 | foreach my $plugin ( $plugins ) 155 | { 156 | $data = $plugin->expand_variables( $page, $data ); 157 | } 158 | 159 | Each plugin will be called once, and once only, for each page. The 160 | `expand_variables` method is given a reference to the page from which the 161 | variable(s) were loaded, which may be useful in some situations. 162 | 163 | It should be noted for completeness that the same expansion happens on global 164 | variables defined within your `templer.cfg` file. 165 | 166 | 167 | Help? 168 | ----- 169 | 170 | If you need help writing a plugin, or whish me to supply one for you and your 171 | needs, please do get in touch. 172 | -------------------------------------------------------------------------------- /bin/build-templer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # Concatenate our various files together to make a single 4 | # script called "templer". 5 | # 6 | # Steve 7 | # -- 8 | # 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use English qw( -no_match_vars ); 14 | use File::Find; 15 | 16 | 17 | 18 | =begin doc 19 | 20 | Find all modules beneath "./lib" 21 | 22 | =end doc 23 | 24 | =cut 25 | 26 | sub find_files 27 | { 28 | my @files; 29 | my $tf_finder = sub { 30 | return if !-f; 31 | return unless /\.pm$/; 32 | push @files, $File::Find::name; 33 | }; 34 | find( $tf_finder, "lib" ); 35 | return @files; 36 | 37 | } 38 | 39 | 40 | # 41 | # Open templer for output. 42 | # 43 | open( my $handle, ">", "templer" ); 44 | print $handle < ) 70 | { 71 | if ($site) 72 | { 73 | # 74 | # We don't want to "use Templer::Site::Asset" or 75 | # "use Templer::Site::Page" when running in standalone 76 | # mode. 77 | # 78 | print $handle $line 79 | unless ( $line =~ /use Templer::Site::(Asset|Page)/ ); 80 | } 81 | else 82 | { 83 | print $handle $line; 84 | } 85 | } 86 | close($tmp); 87 | } 88 | 89 | 90 | # 91 | # Now add on "./bin/templer" 92 | # 93 | open( my $master, "<", "bin/templer" ) or 94 | die "Failed to open bin/templer - $!"; 95 | while ( my $line = <$master> ) 96 | { 97 | next if ( $line =~ /^require.*Templer.*/ ); 98 | print $handle $line; 99 | } 100 | close($handle); 101 | 102 | # 103 | # Make the file executable. 104 | # 105 | if ( $OSNAME eq 'MSWin32' ) 106 | { 107 | #pl2bat is included by default in Strawberry and ActiveState Perl. 108 | `pl2bat templer`; # Will wrap templer in a batch file named templer.bat. 109 | } 110 | else 111 | { 112 | 113 | chmod( 0755, "templer" ); 114 | } 115 | 116 | # 117 | # All done. 118 | # 119 | close($handle); 120 | -------------------------------------------------------------------------------- /bin/templer: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | package main; 7 | 8 | use Getopt::Long; 9 | use Pod::Find qw! pod_where!; 10 | use Pod::Usage; 11 | 12 | 13 | # 14 | # Core templer libraries. 15 | # 16 | require App::Templer; 17 | require Templer::Timer; 18 | require Templer::Global; 19 | require Templer::Site; 20 | require Templer::Site::Asset; 21 | require Templer::Site::Page; 22 | require Templer::Plugin::Factory; 23 | 24 | 25 | 26 | 27 | #### 28 | ## 29 | # Entry point to the code. 30 | ## 31 | ######## 32 | 33 | 34 | 35 | 36 | 37 | # 38 | # Configuration variables 39 | # 40 | my %CONFIG; 41 | 42 | 43 | # 44 | # Parse the command line. 45 | # 46 | parseCommandLine(); 47 | 48 | 49 | # 50 | # Test that we have required dependencies. 51 | # 52 | testDependencies(); 53 | 54 | 55 | # 56 | # Unless config file already specified, if we're not in a templer-directory 57 | # then go up the filesystem looking for one. Stop after "a few" attempts. 58 | # 59 | if ( !defined( $CONFIG{ 'config' } ) ) 60 | { 61 | for ( my $count = 0 ; $count < 10 ; $count += 1 ) 62 | { 63 | if ( !( ( -e "templer.cfg" ) || ( -d "input/" ) ) ) 64 | { 65 | chdir("../"); 66 | } 67 | } 68 | } 69 | 70 | 71 | # 72 | # Read the global configuration file, which might have been 73 | # specified upon the command-line, but will otherwise be 74 | # templer.cfg. 75 | # 76 | my $cnf = $CONFIG{ 'config' } || "./templer.cfg"; 77 | my $cfg = 78 | Templer::Global->new( file => $cnf, debug => ( $CONFIG{ 'debug' } || 0 ) ); 79 | 80 | 81 | # 82 | # These are the default settings. 83 | # 84 | my %DEFAULTS = ( "in-place" => 0, 85 | "include-path" => "./includes", 86 | "input" => "./input/", 87 | "layout" => "default.layout", 88 | "layout-path" => "./layouts", 89 | "output" => "./output/", 90 | "plugin-path" => "./plugins", 91 | "suffix" => ".skx", 92 | "verbose" => ( $CONFIG{ 'verbose' } || 0 ), 93 | "debug" => ( $CONFIG{ 'debug' } || 0 ), 94 | "force" => ( $CONFIG{ 'force' } || 0 ), 95 | "sync" => ( $CONFIG{ 'sync' } || 0 ), 96 | ); 97 | 98 | 99 | # 100 | # The defaults will now be over-ridden by anything which is specified 101 | # in the configuration file that we've loaded/parsed. 102 | # 103 | my %SITE = $cfg->fields(); 104 | foreach my $field ( keys %SITE ) 105 | { 106 | $DEFAULTS{ $field } = $SITE{ $field }; 107 | } 108 | 109 | # 110 | # We need to keep the global configuration file name to add it as a dependency 111 | # for every page 112 | # 113 | $DEFAULTS{ 'config' } = $cnf; 114 | 115 | # 116 | # Add in any definitions we've received. 117 | # 118 | if ( $CONFIG{ 'defines' } ) 119 | { 120 | my $defs = $CONFIG{ 'defines' }; 121 | 122 | foreach my $kv (@$defs) 123 | { 124 | if ( $kv =~ /^([^=]+)=(.*)$/ ) 125 | { 126 | my $key = $1; 127 | my $val = $2; 128 | $DEFAULTS{ lc $key } = $val; 129 | } 130 | } 131 | } 132 | 133 | 134 | # 135 | # If we're running a HTTP-server do it now, since we've got 136 | # all the data we need. 137 | # 138 | runHTTPD() if ( $CONFIG{ 'serve' } ); 139 | 140 | 141 | # 142 | # Now we've setup the defaults, and updated them via the configuration 143 | # file we're ready to actually build/rebuild the templer-site. 144 | # 145 | # We'll create the Templer::Site object to do that, the init method 146 | # will ensure we have the input directory, and create the output directory 147 | # if it is missing and required. 148 | # 149 | my $site = Templer::Site->new(%DEFAULTS); 150 | $site->init(); 151 | 152 | 153 | # 154 | # Execute any pre-build commands the user might have defined. 155 | # 156 | runCommands('pre-build'); 157 | 158 | 159 | # 160 | # Record the start time. 161 | # 162 | my $timer = Templer::Timer->new(); 163 | 164 | 165 | # 166 | # Build/rebuild the site. 167 | # 168 | my $rebuilt = $site->build(); 169 | 170 | # 171 | # Copy the assets into place. 172 | # 173 | $site->copyAssets(); 174 | 175 | # 176 | # Synchronize destination with input 177 | # 178 | $site->sync(); 179 | 180 | # 181 | # Run any post-build commands the user might have defined. 182 | # 183 | runCommands("post-build"); 184 | 185 | 186 | 187 | 188 | # 189 | # All done. 190 | # 191 | my $duration = $timer->elapsed(); 192 | print "\nAll done: $rebuilt page(s) updated in $duration.\n" 193 | unless ( $CONFIG{ 'quiet' } ); 194 | 195 | exit(0); 196 | 197 | 198 | 199 | 200 | # 201 | # Parse the command line 202 | # 203 | sub parseCommandLine 204 | { 205 | my @defines; 206 | 207 | # 208 | # Parse the command line. 209 | # 210 | exit 211 | if ( 212 | !Getopt::Long::GetOptions( 213 | 214 | # Help options 215 | "help", \$CONFIG{ 'help' }, 216 | "manual", \$CONFIG{ 'manual' }, 217 | 218 | # load the config file 219 | "config=s", \$CONFIG{ 'config' }, 220 | 221 | # define a variable 222 | "define=s", \@defines, 223 | 224 | # force a rebuild 225 | "force", \$CONFIG{ 'force' }, 226 | 227 | # undocumented. 228 | "serve=i", \$CONFIG{ 'serve' }, 229 | 230 | # run quietly. 231 | "quiet", \$CONFIG{ 'quiet' }, 232 | 233 | # run verbosely. 234 | "verbose", \$CONFIG{ 'verbose' }, 235 | "version", \$CONFIG{ 'version' }, 236 | "debug", \$CONFIG{ 'debug' }, 237 | ) ); 238 | 239 | 240 | # 241 | # Help/Manual handling. 242 | # 243 | Pod::Usage::pod2usage( -input => \*DATA, ) if ( $CONFIG{ 'help' } ); 244 | Pod::Usage::pod2usage( -input => \*DATA, -verbose => 2 ) 245 | if ( $CONFIG{ 'manual' } ); 246 | 247 | 248 | # 249 | # Show the release. 250 | # 251 | if ( $CONFIG{'version'} ) 252 | { 253 | print $App::Templer::VERSION . "\n"; 254 | exit(0); 255 | } 256 | 257 | # 258 | # Poke in definitions 259 | # 260 | foreach my $kv (@defines) 261 | { 262 | push( @{ $CONFIG{ 'defines' } }, $kv ); 263 | } 264 | } 265 | 266 | 267 | 268 | # 269 | # Test for required dependencies 270 | # 271 | sub testDependencies 272 | { 273 | my $fatal = 0; 274 | 275 | # 276 | # The required modules 277 | # 278 | foreach my $module (qw! HTML::Template !) 279 | { 280 | my $eval = "use $module;"; 281 | 282 | ## no critic (Eval) 283 | eval($eval); 284 | ## use critic 285 | if ($@) 286 | { 287 | print "The $module module doesn't seem to be installed.\n"; 288 | $fatal = 1; 289 | } 290 | } 291 | 292 | exit(1) if ($fatal); 293 | } 294 | 295 | 296 | 297 | # 298 | # Run the commands the user might have specified for pre-build/post-build 299 | # execution 300 | # 301 | sub runCommands 302 | { 303 | my ($type) = (@_); 304 | 305 | my $cmds = $cfg->field($type); 306 | if ($cmds) 307 | { 308 | foreach my $cmd (@$cmds) 309 | { 310 | $CONFIG{ 'verbose' } && print "$type command: $cmd\n"; 311 | system($cmd ); 312 | } 313 | } 314 | } 315 | 316 | 317 | 318 | # 319 | # Launch python(!!) to run a HTTP-server. 320 | # 321 | sub runHTTPD 322 | { 323 | 324 | # 325 | # Document root 326 | # 327 | my $root = $DEFAULTS{ 'output' }; 328 | $root = $DEFAULTS{ 'input' } 329 | if ( defined $DEFAULTS{ 'in-place' } && $DEFAULTS{ 'in-place' } ); 330 | 331 | if ( !-d $root ) 332 | { 333 | print "Document root doesn't exist: $root\n"; 334 | exit 0; 335 | } 336 | 337 | # 338 | # This is appalling. 339 | # 340 | my $port = $CONFIG{ 'serve' } || "8000"; 341 | system("cd $root && python -m SimpleHTTPServer $port"); 342 | exit(0); 343 | } 344 | 345 | 346 | __DATA__ 347 | 348 | 349 | =head1 NAME 350 | 351 | templer - A static-site generator, written in perl. 352 | 353 | =cut 354 | 355 | =head1 SYNOPSIS 356 | 357 | templer [options] 358 | 359 | 360 | Help Options: 361 | 362 | --help Show the help information for this script. 363 | --manual Read the manual for this script. 364 | 365 | Flags 366 | 367 | --config=I Use I as global config file 368 | --force Force a rebuild of all pages/assets 369 | --in-place Specify we're processing pages in-place, ignoring the output dir. 370 | --quiet Don't show output. 371 | --verbose Be noisy during the execution. 372 | 373 | =cut 374 | 375 | =head1 ABOUT 376 | 377 | Templer is the static-site generator utility I use for my websites. 378 | 379 | Templer flexible with input pages, and allows variables to be defined 380 | on a global or per-page basis, and then inserted into the output. 381 | 382 | Given a single template a complete site may be generated with an arbitrary 383 | number of pages, each sharing a common look and feel. If required you can 384 | define a range of templates and select which to use on a per-page basis. 385 | 386 | We allow variable interpolation, loops, and conditional expansion in the 387 | generated output via the use of the L module. 388 | 389 | The name? It stuck. Initially I was thinking "templator" and 390 | "Templer" popped into my mind, via Knights Templer. 391 | 392 | =cut 393 | 394 | =head1 Live Usage 395 | 396 | This code is in use on several domains I host/maintain: 397 | 398 | =over 8 399 | 400 | =item http://blogspam.net/ 401 | 402 | =item http://kvm-hosting.org/ 403 | 404 | =item http://steve.org.uk/ 405 | 406 | =item http://stolen-souls.com/ 407 | 408 | =back 409 | 410 | Originally I had one utility to generate the HTML for each of those sites, called 411 | 'webgen'. Over time this single version divererged into several, as I made ad-hoc 412 | changes to cope with the different sites. 413 | 414 | Templer was created to replace my previous script, ensuring that each of the 415 | site-specific requirements would continue to be satisified, on that basis it 416 | should be generally flexible and usable by others. 417 | 418 | =cut 419 | 420 | 421 | =head1 Site Structure 422 | 423 | A templer-based site consists of a configuration file C and 424 | an input directory. Input files are processed with L, to 425 | expand any variables set in the pages themselves, or in the global template. 426 | 427 | Once page-content is expanded it is then inserted into a global layout template. 428 | 429 | There are two modes of operation when it comes to processing files: 430 | 431 | =over 8 432 | 433 | =item "In-place" 434 | 435 | Files are processed and the suffix is replaced with .html 436 | 437 | =item Output Path 438 | 439 | Files are generated in a distinct output-tree, and any static 440 | assets such as JPG, GIF, PNG, CSS, and JS files are copied across too. 441 | 442 | =back 443 | 444 | This allows you to run in-place in your C<~/public_html> directory or to 445 | setup an output path which is later synced to your final/live location. 446 | 447 | =cut 448 | 449 | 450 | =head1 Code Layout 451 | 452 | The implementation uses several simple classes, mostly as wrappers around 453 | variable parsing: 454 | 455 | =over 8 456 | 457 | =item Templer::Global 458 | 459 | This contains the parsing code for the global configuration file, which is 460 | located at the top-level directory of the site. (It must be named 461 | C). 462 | 463 | =item Templer::Plugin::Factory 464 | 465 | A class-factory for loading/invoking plugin methods. 466 | 467 | =item Templer::Site 468 | 469 | Given an input directory this module finds and returns C and 470 | C objects for each file present. This module is also where 471 | all the actual building occurs. 472 | 473 | =item Templer::Site::Asset 474 | 475 | An item which requires zero expansion. Media, javascript, etc. 476 | 477 | =item Templer::Site::Page 478 | 479 | A page which requires template expansion. 480 | 481 | =item Templer::Timer 482 | 483 | A utility to report upon time-durations. 484 | 485 | =back 486 | 487 | Note #1: The command-line options override those specified in the global object. 488 | 489 | Note #2: The global object has sensible defaults. 490 | 491 | =cut 492 | 493 | =head1 Questions / Bug Reports 494 | 495 | The code is developed and hosted on gitub in the following location: 496 | 497 | =over 8 498 | 499 | =item https://github.com/skx/templer 500 | 501 | =back 502 | 503 | Please raise any issues in the tracker there. 504 | 505 | =cut 506 | 507 | =head1 LICENSE 508 | 509 | This module is free software; you can redistribute it and/or modify it 510 | under the terms of either: 511 | 512 | a) the GNU General Public License as published by the Free Software 513 | Foundation; either version 2, or (at your option) any later version, 514 | or 515 | 516 | b) the Perl "Artistic License". 517 | 518 | =cut 519 | 520 | =head1 AUTHOR 521 | 522 | Steve 523 | -- 524 | http://www.steve.org.uk/ 525 | 526 | =cut 527 | 528 | =head1 LICENSE 529 | 530 | Copyright (c) 2012-2015 by Steve Kemp. All rights reserved. 531 | 532 | This module is free software; 533 | you can redistribute it and/or modify it under 534 | the same terms as Perl itself. 535 | The LICENSE file contains the full text of the license. 536 | 537 | =cut 538 | 539 | -------------------------------------------------------------------------------- /bin/templer-generate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | =head1 NAME 4 | 5 | templer-generate - Generate a stub static-site to be processed via templer. 6 | 7 | =cut 8 | 9 | =head1 SYNOPSIS 10 | 11 | templer-generate [options] path/to/generate 12 | 13 | 14 | Help Options: 15 | 16 | --help Show the help information for this script. 17 | --manual Read the manual for this script. 18 | 19 | Flags 20 | 21 | --force Force overwriting existing files. 22 | --verbose Be verbose. 23 | 24 | =cut 25 | 26 | =head1 LICENSE 27 | 28 | This module is free software; you can redistribute it and/or modify it 29 | under the terms of either: 30 | 31 | a) the GNU General Public License as published by the Free Software 32 | Foundation; either version 2, or (at your option) any later version, 33 | or 34 | 35 | b) the Perl "Artistic License". 36 | 37 | =cut 38 | 39 | =head1 AUTHOR 40 | 41 | Steve 42 | -- 43 | http://www.steve.org.uk/ 44 | 45 | =cut 46 | 47 | =head1 LICENSE 48 | 49 | Copyright (c) 2012-2015 by Steve Kemp. All rights reserved. 50 | 51 | This module is free software; 52 | you can redistribute it and/or modify it under 53 | the same terms as Perl itself. 54 | The LICENSE file contains the full text of the license. 55 | 56 | =cut 57 | 58 | 59 | 60 | use strict; 61 | use warnings; 62 | 63 | use File::Path qw(mkpath); 64 | use Getopt::Long; 65 | use Pod::Usage; 66 | 67 | use App::Templer; 68 | use Templer::Site::New; 69 | 70 | my %CONFIG; 71 | 72 | exit 73 | if ( 74 | !GetOptions( 75 | 76 | # Help options 77 | "help", \$CONFIG{ 'help' }, 78 | "manual", \$CONFIG{ 'manual' }, 79 | "force", \$CONFIG{ 'force' }, 80 | "verbose", \$CONFIG{ 'verbose' }, 81 | "version", \$CONFIG{ 'version' }, 82 | ) ); 83 | 84 | 85 | # 86 | # Help/Manual handling. 87 | # 88 | pod2usage(1) if ( $CONFIG{ 'help' } ); 89 | pod2usage( -verbose => 2 ) if ( $CONFIG{ 'manual' } ); 90 | 91 | # 92 | # Show the release. 93 | # 94 | if ( $CONFIG{'version'} ) 95 | { 96 | print $App::Templer::VERSION . "\n"; 97 | exit(0); 98 | } 99 | 100 | 101 | # 102 | # Get the directory to populate. 103 | # 104 | my $base = shift; 105 | if ( !defined($base) ) 106 | { 107 | pod2usage(1); 108 | } 109 | 110 | 111 | my $helper = Templer::Site::New->new(); 112 | $helper->create( $base, $CONFIG{ 'force' } ); 113 | 114 | 115 | # 116 | # All done 117 | # 118 | exit(0); 119 | -------------------------------------------------------------------------------- /examples/breadcrumbs/input/index.skx: -------------------------------------------------------------------------------- 1 | Title: The first page 2 | ---- 3 | 4 |

This is my first page.

5 |

This page is simple.

6 |

Look at my software page?

7 |

Look at my software example page

8 | -------------------------------------------------------------------------------- /examples/breadcrumbs/input/robots.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skx/templer/1b224a3358027804e8197cb3d1cfe0d3e765d49a/examples/breadcrumbs/input/robots.txt -------------------------------------------------------------------------------- /examples/breadcrumbs/input/software/example/index.skx: -------------------------------------------------------------------------------- 1 | Title: Software Page 2 | Crumbs: Home, software, example 3 | css: path_to(css) 4 | ---- 5 | 6 |

This "site" was created by templer.

7 |

The CSS files are stored in

8 | -------------------------------------------------------------------------------- /examples/breadcrumbs/input/software/index.skx: -------------------------------------------------------------------------------- 1 | Title: Software Page 2 | Crumbs: home, software 3 | ---- 4 | 5 |

This "site" was created by templer.

6 | -------------------------------------------------------------------------------- /examples/breadcrumbs/layouts/default.layout: -------------------------------------------------------------------------------- 1 | 2 | 3 | <!-- tmpl_var name='title' escape='html' --> 4 | 5 | 6 |
7 |

8 |
9 | 10 |
    11 | 12 |
  • 13 | 14 |
15 | 16 |

 

17 |
18 | 19 |
20 |
21 |

22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /examples/breadcrumbs/templer.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## 6 | # 7 | # If you wish to execute commands prior to building the 8 | # site you may include an arbitrary number of keys called 9 | # pre-build. 10 | # 11 | # pre-build = echo Starting build at $(date) 12 | # pre-build = echo Running on $(hostname) 13 | # 14 | ## 15 | 16 | 17 | 18 | 19 | ## 20 | # 21 | # The first section of the configuration file refers to the 22 | # input and output paths. 23 | # 24 | # Templer will process all files matching "*.skx" beneath a 25 | # particular directory. That directory is the input directory. 26 | # 27 | input = ./input/ 28 | # 29 | ## 30 | 31 | 32 | 33 | 34 | ## 35 | # 36 | # Within the input directory we'll process files that match 37 | # a given suffix. 38 | # 39 | # By default this is ".skx", so we'll template-expand files 40 | # named "index.skx", "about.skx", etc. 41 | # 42 | # suffix = .skx 43 | # 44 | ## 45 | 46 | 47 | 48 | 49 | ## 50 | # 51 | # If we're working in-place then files will be expanded where 52 | # they are found. 53 | # 54 | # This means that the following files will be created: 55 | # 56 | # ./input/index.skx -> input/index.html 57 | # ./input/foo/index.skx -> input/foo/index.html 58 | # .. 59 | # 60 | # 61 | # in-place = 1 62 | # 63 | ## 64 | 65 | 66 | 67 | ## 68 | # 69 | # The more common way of working is to produce the output in a separate 70 | # directory. 71 | # 72 | # NOTE: If you specify both "in-place=1" and an output directory the former 73 | # will take precedence. 74 | # 75 | # 76 | output = ./output/ 77 | # 78 | ## 79 | 80 | 81 | 82 | 83 | ## 84 | # 85 | # When pages are processed a layout-template will be used to expand the content 86 | # into. 87 | # 88 | # Each page may specify its own layout if it so wishes, but generally we'd 89 | # expect only one layout to exist. 90 | # 91 | # Here we specify both the path to the layout directory and the layout to use 92 | # if none is specified: 93 | # 94 | # 95 | # layout-path = ./layouts/ 96 | # 97 | # layout = default.layout 98 | # 99 | ## 100 | 101 | 102 | 103 | 104 | ## 105 | # 106 | # Templer supports plugins for expanding variable definitions 107 | # inside the input files, or for formating with text systems 108 | # like Textile, Markdown, etc. 109 | # 110 | # There are several plugins included with the system and you 111 | # can write your own in perl. Specify the path to load plugins 112 | # from here. 113 | # 114 | plugin-path = ./plugins/ 115 | # 116 | ## 117 | 118 | 119 | ## 120 | # 121 | # Templer supports including files via the 'read_file' function, along 122 | # with the built-in support that HTML::Template has for file inclusion 123 | # via: 124 | # 125 | # 126 | # 127 | # The latter case you may specify a search-path for file inclusion 128 | # via the include-path setting. 129 | # 130 | # include-path = include/:include/local/ 131 | # 132 | include-path = ./includes 133 | # 134 | ## 135 | 136 | 137 | 138 | ## 139 | # 140 | # If you wish to execute commands after building the 141 | # site you may include an arbitrary number of keys called 142 | # post-build. 143 | # 144 | # post-build = echo Finished build at $(date) 145 | # 146 | ## 147 | 148 | 149 | 150 | # 151 | # Anything below this is a global variable, accessible by name in your 152 | # templates. 153 | # 154 | # For example this: 155 | # 156 | copyright = Steve Kemp 157 | # 158 | # Means you can use this in your layout, or your page text: 159 | # 160 | # 161 | # 162 | # Similarly you might wish to include a last-modified date in the layout 163 | # and this could be achieved by wruting this: 164 | # 165 | #

Page last rebuilt at

166 | # 167 | # Providing you uncomment this line: 168 | # 169 | # date = run_command( date '+%A %e %B %Y' ) 170 | # 171 | # 172 | 173 | 174 | -------------------------------------------------------------------------------- /examples/complex/input/dark.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: silver; 3 | color: black; 4 | } 5 | -------------------------------------------------------------------------------- /examples/complex/input/file.skx: -------------------------------------------------------------------------------- 1 | title: File Reading 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | ---- 5 | 6 | 7 |

File Reading

8 |
9 |

We've already seen how a page can include a variable in the 10 | input and have this be inserted into the output.

11 |

There is a special kind of per-page variable, using read_file, which sets 12 | the value of a variable to the contents of the named file:

13 |
14 | title: File Reading
15 | src: read_file( file.wgn )
16 | sidebar: read_file( sidebar.inc )
17 | ----
18 | ..
19 | 
20 |

As a helper if you use "read_file( SELF )", then "SELF" will be 21 | replaced with the page being read.

22 |
23 | 24 | 25 |

File Inclusion

26 |
27 |

As an alternative to naming a part of your template, and expanding the content 28 | within it you can of course use the HTML::Template file inclusion construct:

29 |
30 |   <!-- tmpl_include name='path.inc' -->
31 | 
32 |

The inclusion path is relative to the directory containing the file being processed.

33 |
34 | -------------------------------------------------------------------------------- /examples/complex/input/glob.skx: -------------------------------------------------------------------------------- 1 | title: File Patterns 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | files: file_glob( "img/*.jpg" ) 5 | ---- 6 | 7 | 8 |

File Patterns

9 |
10 |

There is a special kind of per-page variable, using file_glob, which sets 11 | up a data-structure which is used for a list. Lists are documented fully in the 12 | HTML::Template documentation, but in brief if you have this structure:

13 |
14 | index.wgn
15 | IMAGE1.jpg
16 | IMAGE2.jpg
17 | IMAGE3.jpg
18 | 
19 |

You can setup a loop to hold the names of each file via:

20 |
21 | files: file_glob( "IMAGE*.jpg" )
22 | 
23 |

Then this, in your template, will allow you to see each of them:

24 |
25 |   <!-- tmpl_loop name='files' -->
26 |    <img src= "<!-- tmpl_var name='file' -->">
27 |   <!-- /tmpl_loop -->
28 | 
29 |

Here is a live example:

30 | 31 | 32 |

33 | 34 | 35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /examples/complex/input/img/cat1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skx/templer/1b224a3358027804e8197cb3d1cfe0d3e765d49a/examples/complex/input/img/cat1.jpg -------------------------------------------------------------------------------- /examples/complex/input/img/cat2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skx/templer/1b224a3358027804e8197cb3d1cfe0d3e765d49a/examples/complex/input/img/cat2.jpg -------------------------------------------------------------------------------- /examples/complex/input/index.skx: -------------------------------------------------------------------------------- 1 | title: Templer 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | ---- 5 | 6 | 7 |

8 |
9 |

This collection of pages is designed to fully demonstrate how you'd use Templer, to generate a website.

10 |

A templer site is created by a collection of input files, and a layout file.

11 |

The general course of processing involves:

12 |
    13 |
  • Reading each input page.
  • 14 |
  • Expanding variables/content.
  • 15 |
  • Inserting the rendered content into the layout.
  • 16 |
17 |

A site itself then consists of a series of input pages (i.e. your content) and at least one layout. An input page might well decide to use a different layout which is just fine.

18 |
19 | 20 |

Quick Links

21 |
22 |
23 |
Site Structure
24 |

A brief discussion on site-layout.

25 |
A Page
26 |

What is a page? What can I do with it?.

27 |
Variable expansion
28 |

Demonstration of simple variable expansion.

29 |
Conditional variables
30 |

How to have conditional parts of your layout.

31 |
Variables containing file contents
32 |

A variable can contain either a literal value or the contents of a file.

33 |
Variables matching filenames
34 |

A variable can also be used to create a loop, based on a glob pattern. Ideal for creating a gallery, for example.

35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /examples/complex/input/layout.skx: -------------------------------------------------------------------------------- 1 | title: A Layout 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | ---- 5 | 6 | 7 |

A Layout

8 |
9 |

A layout is a simple HTML::Template file which contains place-holders for your 10 | content to go into. The most basic layout would look like this:

11 |
12 | <html>
13 |  <head>
14 |   <title><!-- tmpl_var name='title' escape='html' --></title>
15 |  </head>
16 |  <body>
17 |  <!-- tmpl_var name='content' -->
18 |  </body>
19 | </html>
20 | 
21 |

Templer will insert the contents of each page into the region named "content", and 22 | in this example we've assumed that each page will define the variable "title". Together the content and the title complete the page.

23 |

You can be significantly more sophisticated than just replacing the content and the title of the output page though - by including optional components as we've done with this example.

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/complex/input/page.skx: -------------------------------------------------------------------------------- 1 | title: A Page 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | ---- 5 | 6 | 7 |

A Page

8 |
9 |

A page is nothing more than a file which contains content you wish to publish.

10 |

A page contains two things:

11 |
12 |
A header
13 |

This is where you may define per-page variables.

14 |
The content
15 |

This is where you write your content, optionally making use of variables and HTML::Template markup

16 |
17 |

The header of a file is any content which lies between the top of the file and the marker "----". The header consists of lines of the form:

18 |
19 | key: value
20 | key2: value2
21 | 
22 |

Those examples define the variables "key" and "key2" respectively.

23 |
24 |

NOTE: Key names are always transformed to lower-case when encountered.

25 |
26 |

A header is mandatory even if it is empty, but note that you'll almost certainly want to set the title of the output page.

27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /examples/complex/input/sidebar.inc: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/complex/input/structure.skx: -------------------------------------------------------------------------------- 1 | title: Site structure 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | ---- 5 | 6 | 7 |

8 |
9 |

A templer site consists of at least three things:

10 |
11 |
A top-level configuration file.
12 |

This is always the configuration file named ./templer.cfg, you cannot rename this, or specify an alternative.

13 |
A directory containing layouts.
14 |

By default layout templates are loaded from ./layouts, and unless a page 15 | references another layout then the file default.layout will be loaded.

16 |

The default layout name may be changed in the configuration file mentioned above.

17 |
A series of input pages
18 |

These are your pages of content. By default these come from ./input, but 19 | this may be changed in the global configuration file.

20 |
21 |
22 | 23 |

Example Structure

24 |
25 |

This site was built via the following structure:

26 |
27 | ├── input/
28 | │   ├── dark.css
29 | │   ├── file.wgn
30 | │   ├── index.wgn
31 | │   ├── jquery.min.js
32 | │   ├── sidebar.inc
33 | │   ├── structure.wgn
34 | │   ├── ..
35 | │   ├── ..
36 | │   ├── stylesheet.wgn
37 | │   └── variables.wgn
38 | ├── layouts/
39 | │   └── default.layout
40 | ├── output/
41 | └── templer.cfg
42 | 
43 |

Here the "*.wgn" files are the pages to be processed, and the output/ 44 | directory is where our content will be generated and stored.

45 |
46 | -------------------------------------------------------------------------------- /examples/complex/input/style.css: -------------------------------------------------------------------------------- 1 | dt { 2 | font-weight: bold; 3 | } -------------------------------------------------------------------------------- /examples/complex/input/stylesheet.skx: -------------------------------------------------------------------------------- 1 | title: Stylesheet Example 2 | src: read_file( SELF ) 3 | stylesheet: dark.css 4 | sidebar: read_file( sidebar.inc ) 5 | ---- 6 | 7 | 8 |

Conditional Variables

9 |
10 |

If you look at the layout used generate this "site" you'll see this:

11 |
12 |   <!-- tmpl_if name='stylesheet' -->
13 |    <link rel="stylesheet" href="<!-- tmpl_var name='stylesheet' -->"
14 |       type="text/css" />
15 |   <!-- tmpl_else -->
16 |    <link rel="stylesheet" href="style.css" type="text/css" />
17 |   <!-- /tmpl_if -->
18 | 
19 |

Here the code basically says:

20 |
    21 |
  • If there is a variable called "stylesheet" set then use it.
  • 22 |
  • Otherwise include the "style.css" link.
  • 23 |
24 |

If you guessed you could then set a per-page stylesheet-variable you'd be correct.

25 |

This page contains a link to one:

26 |
27 | title: Stylesheet Example
28 | stylesheet: dark.css
29 | ----
30 | ..
31 | 
32 |
-------------------------------------------------------------------------------- /examples/complex/input/variables.skx: -------------------------------------------------------------------------------- 1 | title: Variables 2 | src: read_file( SELF ) 3 | sidebar: read_file( sidebar.inc ) 4 | my_name: Steve Kemp 5 | ---- 6 | 7 |

8 |
9 |

There are two kinds of variables in a templer deployment:

10 |
11 |
Variables which are global.
12 |

These are defined in the configuration file templer.cfg at the top-level of the site.

13 |
Variables which are defined on a per-page basis
14 |

These variables are included in the header of the page, which is the part before the deliminator "----".

15 |
16 |

Variables are included by the standard HTML::Template construct:

17 |
18 | <!-- tmpl_var name='name' --> 19 |
20 |

The input which generated this output page, which you can see at the foot of the page, defines the variable "my_name" and so we can output it via this:

21 |
22 | <!-- tmpl_var name='my_name' --> 23 |
24 |

Here we see that:

25 |
26 | My name is 27 |
28 |
29 | 30 |

Types of variables

31 |
32 |

There are two types of variables permitted:

33 |
34 |
Simple Strings
35 |

These are defined literally as "key: value ..".

36 |
Special values
37 |

There are some special types of variable which are available:

38 |
42 |
43 |
44 | -------------------------------------------------------------------------------- /examples/complex/layouts/default.layout: -------------------------------------------------------------------------------- 1 | 2 | 3 | <!-- tmpl_var name='title' escape='html' --> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | 21 | 22 |
23 |

24 |
25 |

 

26 | 27 | 28 | 31 | 34 | 35 |
29 | 30 | 32 | 33 |
36 | 37 |
38 |

Show the source of this page.

39 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /examples/complex/templer.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## 6 | # 7 | # If you wish to execute commands prior to building the 8 | # site you may include an arbitrary number of keys called 9 | # pre-build. 10 | # 11 | # pre-build = echo Starting build at $(date) 12 | # pre-build = echo Running on $(hostname) 13 | # 14 | ## 15 | 16 | 17 | 18 | 19 | ## 20 | # 21 | # The first section of the configuration file refers to the 22 | # input and output paths. 23 | # 24 | # Templer will process all files matching "*.skx" beneath a 25 | # particular directory. That directory is the input directory. 26 | # 27 | input = ./input/ 28 | # 29 | ## 30 | 31 | 32 | 33 | 34 | ## 35 | # 36 | # Within the input directory we'll process files that match 37 | # a given suffix. 38 | # 39 | # By default this is ".skx", so we'll template-expand files 40 | # named "index.skx", "about.skx", etc. 41 | # 42 | suffix = .skx 43 | # 44 | ## 45 | 46 | 47 | 48 | 49 | ## 50 | # 51 | # If we're working in-place then files will be expanded where 52 | # they are found. 53 | # 54 | # This means that the following files will be created: 55 | # 56 | # ./input/index.skx -> input/index.html 57 | # ./input/foo/index.skx -> input/foo/index.html 58 | # .. 59 | # 60 | # 61 | # in-place = 1 62 | # 63 | ## 64 | 65 | 66 | 67 | ## 68 | # 69 | # The more common way of working is to produce the output in a separate 70 | # directory. 71 | # 72 | # NOTE: If you specify both "in-place=1" and an output directory the former 73 | # will take precedence. 74 | # 75 | # 76 | output = ./output/ 77 | # 78 | ## 79 | 80 | 81 | 82 | 83 | ## 84 | # 85 | # When pages are processed a layout-template will be used to expand the content 86 | # into. 87 | # 88 | # Each page may specify its own layout if it so wishes, but generally we'd 89 | # expect only one layout to exist. 90 | # 91 | # Here we specify both the path to the layout directory and the layout to use 92 | # if none is specified: 93 | # 94 | # 95 | # layout-path = ./layouts/ 96 | # 97 | # layout = default.layout 98 | # 99 | ## 100 | 101 | 102 | 103 | 104 | ## 105 | # 106 | # Templer supports plugins for expanding variable definitions 107 | # inside the input files, or for formating with text systems 108 | # like Textile, Markdown, etc. 109 | # 110 | # There are several plugins included with the system and you 111 | # can write your own in perl. Specify the path to load plugins 112 | # from here. 113 | # 114 | plugin-path = ./plugins/ 115 | # 116 | ## 117 | 118 | 119 | ## 120 | # 121 | # Templer supports including files via the 'read_file' function, along 122 | # with the built-in support that HTML::Template has for file inclusion 123 | # via: 124 | # 125 | # 126 | # 127 | # The latter case you may specify a search-path for file inclusion 128 | # via the include-path setting. 129 | # 130 | # include-path = include/:include/local/ 131 | # 132 | include-path = ./includes 133 | # 134 | ## 135 | 136 | 137 | 138 | ## 139 | # 140 | # If you wish to execute commands after building the 141 | # site you may include an arbitrary number of keys called 142 | # post-build. 143 | # 144 | # post-build = echo Finished build at $(date) 145 | # 146 | ## 147 | 148 | 149 | 150 | # 151 | # Anything below this is a global variable, accessible by name in your 152 | # templates. 153 | # 154 | # For example this: 155 | # 156 | # copyright = Steve Kemp 157 | # 158 | # Means you can use this in your layout, or your page text: 159 | # 160 | # 161 | # 162 | # Similarly you might wish to include a last-modified date in the layout 163 | # and this could be achieved by wruting this: 164 | # 165 | #

Page last rebuilt at

166 | # 167 | # Providing you uncomment this line: 168 | # 169 | # date = run_command( date '+%A %e %B %Y' ) 170 | # 171 | # 172 | 173 | 174 | -------------------------------------------------------------------------------- /examples/simple/input/about.skx: -------------------------------------------------------------------------------- 1 | Title: About The Site 2 | ---- 3 | 4 |

This "site" was created by templer.

5 | -------------------------------------------------------------------------------- /examples/simple/input/foo.skx: -------------------------------------------------------------------------------- 1 | Title: Markdown and perl 2 | format: perl, markdown 3 | ---- 4 | 5 | This is **bold** 6 | 7 | The magic number is _{ 42 }_. 8 | -------------------------------------------------------------------------------- /examples/simple/input/index.skx: -------------------------------------------------------------------------------- 1 | Title: The first page 2 | ---- 3 | 4 |

This is my first page.

5 |

This page is simple.

6 |

Look at my about page?

7 |

The foo page was built via Markdown and Perl.

8 | -------------------------------------------------------------------------------- /examples/simple/input/robots.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skx/templer/1b224a3358027804e8197cb3d1cfe0d3e765d49a/examples/simple/input/robots.txt -------------------------------------------------------------------------------- /examples/simple/layouts/default.layout: -------------------------------------------------------------------------------- 1 | 2 | 3 | <!-- tmpl_var name='title' escape='html' --> 4 | 5 | 6 |
7 |

8 |
9 |

 

10 |
11 | 12 |
13 |
14 |

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/simple/templer.cfg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## 6 | # 7 | # If you wish to execute commands prior to building the 8 | # site you may include an arbitrary number of keys called 9 | # pre-build. 10 | # 11 | # pre-build = echo Starting build at $(date) 12 | # pre-build = echo Running on $(hostname) 13 | # 14 | ## 15 | 16 | 17 | 18 | 19 | ## 20 | # 21 | # The first section of the configuration file refers to the 22 | # input and output paths. 23 | # 24 | # Templer will process all files matching "*.skx" beneath a 25 | # particular directory. That directory is the input directory. 26 | # 27 | input = ./input/ 28 | # 29 | ## 30 | 31 | 32 | 33 | 34 | ## 35 | # 36 | # Within the input directory we'll process files that match 37 | # a given suffix. 38 | # 39 | # By default this is ".skx", so we'll template-expand files 40 | # named "index.skx", "about.skx", etc. 41 | # 42 | # suffix = .skx 43 | # 44 | ## 45 | 46 | 47 | 48 | 49 | ## 50 | # 51 | # If we're working in-place then files will be expanded where 52 | # they are found. 53 | # 54 | # This means that the following files will be created: 55 | # 56 | # ./input/index.skx -> input/index.html 57 | # ./input/foo/index.skx -> input/foo/index.html 58 | # .. 59 | # 60 | # 61 | # in-place = 1 62 | # 63 | ## 64 | 65 | 66 | 67 | ## 68 | # 69 | # The more common way of working is to produce the output in a separate 70 | # directory. 71 | # 72 | # NOTE: If you specify both "in-place=1" and an output directory the former 73 | # will take precedence. 74 | # 75 | # 76 | output = ./output/ 77 | # 78 | ## 79 | 80 | 81 | 82 | 83 | ## 84 | # 85 | # When pages are processed a layout-template will be used to expand the content 86 | # into. 87 | # 88 | # Each page may specify its own layout if it so wishes, but generally we'd 89 | # expect only one layout to exist. 90 | # 91 | # Here we specify both the path to the layout directory and the layout to use 92 | # if none is specified: 93 | # 94 | # 95 | # layout-path = ./layouts/ 96 | # 97 | # layout = default.layout 98 | # 99 | ## 100 | 101 | 102 | 103 | 104 | ## 105 | # 106 | # Templer supports plugins for expanding variable definitions 107 | # inside the input files, or for formating with text systems 108 | # like Textile, Markdown, etc. 109 | # 110 | # There are several plugins included with the system and you 111 | # can write your own in perl. Specify the path to load plugins 112 | # from here. 113 | # 114 | plugin-path = ./plugins/ 115 | # 116 | ## 117 | 118 | 119 | ## 120 | # 121 | # Templer supports including files via the 'read_file' function, along 122 | # with the built-in support that HTML::Template has for file inclusion 123 | # via: 124 | # 125 | # 126 | # 127 | # The latter case you may specify a search-path for file inclusion 128 | # via the include-path setting. 129 | # 130 | # include-path = include/:include/local/ 131 | # 132 | include-path = ./includes 133 | # 134 | ## 135 | 136 | 137 | 138 | ## 139 | # 140 | # If you wish to execute commands after building the 141 | # site you may include an arbitrary number of keys called 142 | # post-build. 143 | # 144 | # post-build = echo Finished build at $(date) 145 | # 146 | ## 147 | 148 | 149 | 150 | # 151 | # Anything below this is a global variable, accessible by name in your 152 | # templates. 153 | # 154 | # For example this: 155 | # 156 | copyright = © Steve Kemp 157 | # 158 | # Means you can use this in your layout, or your page text: 159 | # 160 | # 161 | # 162 | # Similarly you might wish to include a last-modified date in the layout 163 | # and this could be achieved by wruting this: 164 | # 165 | #

Page last rebuilt at

166 | # 167 | # Providing you uncomment this line: 168 | # 169 | # date = run_command( date '+%A %e %B %Y' ) 170 | year=timestamp(%Y) 171 | 172 | # 173 | 174 | 175 | -------------------------------------------------------------------------------- /lib/App/Templer.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | App::Templer - A holder for our version 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Templer; 14 | 15 | print $Templer::VERSION; 16 | 17 | =cut 18 | 19 | =head1 DESCRIPTION 20 | 21 | This package is a simple holder for the VERSION number of our release. 22 | 23 | It exists specifically so that the installation process will succeed, 24 | and we can usefully track versioned releases. 25 | 26 | =cut 27 | 28 | =head1 LICENSE 29 | 30 | This module is free software; you can redistribute it and/or modify it 31 | under the terms of either: 32 | 33 | a) the GNU General Public License as published by the Free Software 34 | Foundation; either version 2, or (at your option) any later version, 35 | or 36 | 37 | b) the Perl "Artistic License". 38 | 39 | =cut 40 | 41 | =head1 AUTHOR 42 | 43 | Steve Kemp 44 | 45 | =cut 46 | 47 | =head1 COPYRIGHT AND LICENSE 48 | 49 | Copyright (C) 2015 Steve Kemp . 50 | 51 | This library is free software. You can modify and or distribute it under 52 | the same terms as Perl itself. 53 | 54 | =cut 55 | 56 | 57 | use strict; 58 | use warnings; 59 | 60 | 61 | package App::Templer; 62 | 63 | 64 | our $VERSION = "1.4"; 65 | 66 | 67 | 1; 68 | -------------------------------------------------------------------------------- /lib/Templer/Global.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Global - Configuration-file parser for templer. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Templer::Global; 14 | 15 | my $site = Templer::Global->new( file => "./templer.cfg" ); 16 | my $suffix = $site->field( "suffix" ); 17 | 18 | =cut 19 | 20 | =head1 DESCRIPTION 21 | 22 | This class is responsible for parsing the top-level templer.cfg file 23 | which we assume will be present in a templer-based site. 24 | 25 | The file is a simple key=value store, with comments being prefixed by 26 | the hash ("#") character, and ignored. 27 | 28 | This object is created when templer is started so that the options may 29 | be parsed/read. Once that happens the options are merged with the 30 | command-line flags, and this object isn't touched again. 31 | 32 | =cut 33 | 34 | =head1 LICENSE 35 | 36 | This module is free software; you can redistribute it and/or modify it 37 | under the terms of either: 38 | 39 | a) the GNU General Public License as published by the Free Software 40 | Foundation; either version 2, or (at your option) any later version, 41 | or 42 | 43 | b) the Perl "Artistic License". 44 | 45 | =cut 46 | 47 | =head1 AUTHOR 48 | 49 | Steve Kemp 50 | 51 | =cut 52 | 53 | =head1 COPYRIGHT AND LICENSE 54 | 55 | Copyright (C) 2012-2015 Steve Kemp . 56 | 57 | This library is free software. You can modify and or distribute it under 58 | the same terms as Perl itself. 59 | 60 | =cut 61 | 62 | =head1 METHODS 63 | 64 | =cut 65 | 66 | 67 | use strict; 68 | use warnings; 69 | 70 | 71 | package Templer::Global; 72 | 73 | 74 | =head2 new 75 | 76 | Constructor. 77 | 78 | Any parameters specified in our single hash-argument are saved away, 79 | the filename specified in the 'file' parameter will be opened and parsed. 80 | 81 | =cut 82 | 83 | sub new 84 | { 85 | my ( $proto, %supplied ) = (@_); 86 | my $class = ref($proto) || $proto; 87 | 88 | my $self = {}; 89 | 90 | # 91 | # Allow user supplied values to override our defaults 92 | # 93 | foreach my $key ( keys %supplied ) 94 | { 95 | $self->{ lc $key } = $supplied{ $key }; 96 | } 97 | 98 | bless( $self, $class ); 99 | $self->_readGlobalCFG( $self->{ 'file' } ) if ( $self->{ 'file' } ); 100 | return $self; 101 | } 102 | 103 | 104 | 105 | =head2 _readGlobalCFG 106 | 107 | Read the specified configuration file. Called by the constructor if 108 | a filename was specified. 109 | 110 | =cut 111 | 112 | sub _readGlobalCFG 113 | { 114 | my ( $self, $filename ) = (@_); 115 | 116 | # 117 | # If the configuration file doesn't exist that's a shame. 118 | # 119 | return if ( !-e $filename ); 120 | 121 | # 122 | # Open the file, making sure we're UTF-8 safe. 123 | # 124 | open( my $handle, "<:utf8", $filename ) or 125 | die "Failed to read '$filename' - $!"; 126 | binmode( $handle, ":utf8" ); 127 | 128 | while ( my $line = <$handle> ) 129 | { 130 | 131 | # strip trailing newline. 132 | $line =~ s/[\r\n]*//g; 133 | 134 | # skip comments. 135 | next if ( $line =~ /^#/ ); 136 | 137 | # If the line is : key = value 138 | if ( $line =~ /^([^=]+)=(.*)$/ ) 139 | { 140 | my $key = $1; 141 | my $val = $2; 142 | $key = lc($key); 143 | $key =~ s/^\s+|\s+$//g; 144 | $val =~ s/^\s+|\s+$//g; 145 | 146 | # 147 | # command expansion? 148 | # 149 | if ( $val =~ /(.*)`([^`]+)`(.*)/ ) 150 | { 151 | 152 | # store 153 | my $pre = $1; 154 | my $cmd = $2; 155 | my $post = $3; 156 | 157 | # get output 158 | my $output = `$cmd`; 159 | chomp($output); 160 | 161 | # build up replacement. 162 | $val = $pre . $output . $post; 163 | } 164 | 165 | # 166 | # If the line is pre/post-build then save the values as an array 167 | # 168 | if ( $key =~ /^(pre|post)-build$/ ) 169 | { 170 | push( @{ $self->{ $key } }, $val ); 171 | } 172 | else 173 | { 174 | 175 | # 176 | # The general case is store the value in the key. 177 | # 178 | $self->{ $key } = $val; 179 | } 180 | print "Templer::Global set: $key => $val\n" 181 | if ( $self->{ 'debug' } ); 182 | } 183 | } 184 | close($handle); 185 | } 186 | 187 | 188 | 189 | =head2 field 190 | 191 | Retrieve a value from the file, by key. 192 | 193 | This is only called by templer to retrieve the pre/post-build 194 | commands to execute. 195 | 196 | =cut 197 | 198 | sub field 199 | { 200 | my ( $self, $field ) = (@_); 201 | return ( $self->{ $field } ); 202 | } 203 | 204 | 205 | =head2 fields 206 | 207 | Retrieve all known key/value pairs. 208 | 209 | This is called by templer to retrieve all global settings, which 210 | can then be merged with its defaults. 211 | 212 | =cut 213 | 214 | sub fields 215 | { 216 | my ($self) = (@_); 217 | 218 | %$self; 219 | } 220 | 221 | 222 | 1; 223 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Breadcrumbs.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Breadcrumbs - A plugin to create breadcrumbs 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | crumbs: Home,Software 14 | ---- 15 |

This is my page content...

16 | 17 | 18 | Here the variable 'crumbs' will be converted into a loop variable called 19 | 'breadcrumbs'. THe links and titls will be set automatically, but if you 20 | need to override them you can do so via: 21 | 22 | title: About my site 23 | crumbs: Home,Software[Soft],Testing[Test] 24 | ---- 25 |

This is my page content...

26 | 27 | 28 | That will result in links to /, /Soft, and /Soft/Test, respectively. With 29 | display names of "Home", "Software" and "Testing". 30 | 31 | =cut 32 | 33 | =head1 DESCRIPTION 34 | 35 | This plugin expands the values of "crumbs", as written in a template, to 36 | a loop-variable with the name "breadcrumbs". 37 | 38 | This can be used in a template like so: 39 | 40 | =for example begin 41 | 42 | 43 |
    44 | 45 |
  • 46 | 47 |
48 | 49 | 50 | =for example end 51 | 52 | This template is an example. 53 | 54 | =cut 55 | 56 | =head1 LICENSE 57 | 58 | This module is free software; you can redistribute it and/or modify it 59 | under the terms of either: 60 | 61 | a) the GNU General Public License as published by the Free Software 62 | Foundation; either version 2, or (at your option) any later version, 63 | or 64 | 65 | b) the Perl "Artistic License". 66 | 67 | =cut 68 | 69 | =head1 AUTHOR 70 | 71 | Steve Kemp 72 | 73 | =cut 74 | 75 | =head1 COPYRIGHT AND LICENSE 76 | 77 | Copyright (C) 2015 Steve Kemp . 78 | 79 | This library is free software. You can modify and or distribute it under 80 | the same terms as Perl itself. 81 | 82 | =cut 83 | 84 | =head1 METHODS 85 | 86 | =cut 87 | 88 | 89 | use strict; 90 | use warnings; 91 | 92 | 93 | package Templer::Plugin::Breadcrumbs; 94 | 95 | 96 | =head2 new 97 | 98 | Constructor. No arguments are required/supported. 99 | 100 | =cut 101 | 102 | sub new 103 | { 104 | my ( $proto, %supplied ) = (@_); 105 | my $class = ref($proto) || $proto; 106 | 107 | my $self = {}; 108 | bless( $self, $class ); 109 | return $self; 110 | } 111 | 112 | 113 | 114 | =head2 expand_variables 115 | 116 | This is the method which is called by the L 117 | to expand the variables contained in a L object. 118 | 119 | This method will expand any variable that is called 'crumbs' into a HTML::Template 120 | loop, suitable for the display of breadcrumbs. 121 | 122 | =cut 123 | 124 | sub expand_variables 125 | { 126 | my ( $self, $site, $page, $data ) = (@_); 127 | 128 | # 129 | # Get the page-variables in the template. 130 | # 131 | my %hash = %$data; 132 | 133 | # 134 | # Look for a value of "read_file" in each key. 135 | # 136 | foreach my $key ( keys %hash ) 137 | { 138 | if ( $key =~ /^crumbs$/i ) 139 | { 140 | my $loop; 141 | 142 | my $val = $hash{ $key }; 143 | my $link = "/"; 144 | 145 | foreach my $path ( split( /,/, $val ) ) 146 | { 147 | $path =~ s/^\s+|\s+$//g; 148 | 149 | if ( $path =~ /Home/i ) 150 | { 151 | } 152 | else 153 | { 154 | if ( $path =~ /(.*)\[(.*)\]/ ) 155 | { 156 | $link .= "/" . $1; 157 | } 158 | else 159 | { 160 | $link .= "/" . $path; 161 | } 162 | } 163 | 164 | $link =~ s/\/\//\//g; 165 | 166 | if ( $path =~ /\[(.*)\]/ ) 167 | { 168 | $path = $1; 169 | } 170 | else 171 | { 172 | $path = ucfirst($path); 173 | } 174 | push( @$loop, 175 | { title => $path, 176 | link => $link 177 | } ); 178 | } 179 | 180 | 181 | $hash{ 'breadcrumbs' } = $loop if ($loop); 182 | delete $hash{ $key }; 183 | } 184 | } 185 | 186 | return ( \%hash ); 187 | } 188 | 189 | 190 | # 191 | # Register the plugin. 192 | # 193 | Templer::Plugin::Factory->new() 194 | ->register_plugin("Templer::Plugin::Breadcrumbs"); 195 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Dollar.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Dollar - A simple shell-like syntax for template variables. 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a layout template filter plugin for C which 11 | allows template variables to be included using a dollar sign followed by an 12 | open brace, the variable name and a closing brace. 13 | 14 | This allows template such as this to be used 15 | 16 | =for example begin 17 | 18 | 19 | 20 | ${title escape="html"} 21 | 22 | 23 | 24 | 25 | 26 | 27 | =for example end 28 | 29 | Everything between the variable name and the closing brace is used B in 30 | the transformation. The third line of the example is for instance transformed 31 | as 32 | 33 | =for example begin 34 | 35 | <tmpl_var name="title" escape="html"> 36 | 37 | =for example end 38 | 39 | =cut 40 | 41 | =head1 LICENSE 42 | 43 | This module is free software; you can redistribute it and/or modify it 44 | under the terms of either: 45 | 46 | a) the GNU General Public License as published by the Free Software 47 | Foundation; either version 2, or (at your option) any later version, 48 | or 49 | 50 | b) the Perl "Artistic License". 51 | 52 | =cut 53 | 54 | =head1 AUTHOR 55 | 56 | Bruno Beaufils 57 | 58 | =cut 59 | 60 | =head1 COPYRIGHT AND LICENSE 61 | 62 | Copyright (C) 2015 Bruno Beaufils . 63 | 64 | This library is free software. You can modify and or distribute it under 65 | the same terms as Perl itself. 66 | 67 | =cut 68 | 69 | =head1 METHODS 70 | 71 | =cut 72 | 73 | 74 | use strict; 75 | use warnings; 76 | 77 | 78 | package Templer::Plugin::Dollar; 79 | 80 | 81 | =head2 new 82 | 83 | Constructor. No arguments are supported/expected. 84 | 85 | =cut 86 | 87 | sub new 88 | { 89 | my ( $proto, %supplied ) = (@_); 90 | my $class = ref($proto) || $proto; 91 | 92 | my $self = {}; 93 | bless( $self, $class ); 94 | return $self; 95 | } 96 | 97 | 98 | =head2 available 99 | 100 | This plugin is always available. 101 | 102 | =cut 103 | 104 | sub available 105 | { 106 | return 1; 107 | } 108 | 109 | 110 | =head2 filter 111 | 112 | Filter the given template. 113 | 114 | =cut 115 | 116 | sub filter 117 | { 118 | my ( $self, $str ) = (@_); 119 | 120 | $str =~ s/\$\{([^:\s}]+)([^}]*)\}//g; 121 | 122 | return $str; 123 | } 124 | 125 | Templer::Plugin::Factory->new() 126 | ->register_filter( "dollar", "Templer::Plugin::Dollar" ); 127 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Factory.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Factory - A simple plugin class 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a singleton within which plugin classes may 11 | be registered and retrieved. 12 | 13 | The plugins used by C are of two forms: 14 | 15 | =over 8 16 | 17 | =item formatters 18 | 19 | These plugins operate upon the text contained in L objects 20 | and transform the input into HTML. 21 | 22 | =item variable expanders 23 | 24 | These plugins, also operating on L objects, are allowed 25 | the opportunity to modify, update, replace, or delete the various per-page 26 | variables. 27 | 28 | =back 29 | 30 | Plugins of each type register themselves by calling the appropriate methods 31 | in this class. 32 | 33 | =cut 34 | 35 | =head1 LICENSE 36 | 37 | This module is free software; you can redistribute it and/or modify it 38 | under the terms of either: 39 | 40 | a) the GNU General Public License as published by the Free Software 41 | Foundation; either version 2, or (at your option) any later version, 42 | or 43 | 44 | b) the Perl "Artistic License". 45 | 46 | =cut 47 | 48 | =head1 AUTHOR 49 | 50 | Steve Kemp 51 | 52 | =cut 53 | 54 | =head1 COPYRIGHT AND LICENSE 55 | 56 | Copyright (C) 2012-2015 Steve Kemp . 57 | 58 | This library is free software. You can modify and or distribute it under 59 | the same terms as Perl itself. 60 | 61 | =cut 62 | 63 | =head1 METHODS 64 | 65 | =cut 66 | 67 | 68 | use strict; 69 | use warnings; 70 | 71 | 72 | package Templer::Plugin::Factory; 73 | 74 | 75 | my $singleton; 76 | 77 | 78 | # 79 | # Look for plugins beneath the `Templer::Plugin` namespace. 80 | # 81 | use Module::Pluggable 82 | search_path => ['Templer::Plugin'], 83 | require => 1; 84 | 85 | 86 | # 87 | # Invoking `plugins` ensures that we've actually loaded our plugins 88 | # by the time this module is loaded. 89 | # 90 | my @plugins = plugins(); 91 | 92 | 93 | 94 | 95 | =head2 new 96 | 97 | Constructor. 98 | 99 | This class is a singleton, so this method will either construct 100 | an instance of this class or return the global instance. 101 | 102 | =cut 103 | 104 | sub new 105 | { 106 | my $class = shift; 107 | $singleton ||= bless {}, $class; 108 | } 109 | 110 | 111 | 112 | =head2 load_plugins 113 | 114 | This method loads "*.pm" from the given directory. 115 | 116 | =cut 117 | 118 | sub load_plugins 119 | { 120 | my ( $self, $directory ) = (@_); 121 | return unless ( -d $directory ); 122 | 123 | foreach my $file ( sort( glob( $directory . "/*.pm" ) ) ) 124 | { 125 | require $file; 126 | } 127 | } 128 | 129 | 130 | =head2 init 131 | 132 | For each loaded plugin invoke the "init" method, if it exists. 133 | 134 | =cut 135 | 136 | sub init 137 | { 138 | my ( $self, $site ) = (@_); 139 | 140 | foreach my $plugin ( @{ $self->{ 'plugins' } } ) 141 | { 142 | if ( UNIVERSAL::can( $plugin, "init" ) ) 143 | { 144 | $plugin->init($site); 145 | } 146 | } 147 | } 148 | 149 | 150 | =head2 register_formatter 151 | 152 | This method should be called by all formatting plugins to register 153 | themselves. The two arguments are the name of the input-format, 154 | and the class-name which may be instantiated to process that kind 155 | of input. 156 | 157 | L and L are 158 | example classes. 159 | 160 | =cut 161 | 162 | sub register_formatter 163 | { 164 | my ( $self, $name, $obj ) = (@_); 165 | 166 | die "No name" unless ($name); 167 | $name = lc($name); 168 | $self->{ 'formatters' }{ $name } = $obj; 169 | } 170 | 171 | 172 | 173 | =head2 register_filter 174 | 175 | This method should be called by all template filter plugins to register 176 | themselves. The two arguments are the name of the template-filter, 177 | and the class-name which may be instantiated to process that kind 178 | of input. 179 | 180 | L and L are 181 | example classes. 182 | 183 | =cut 184 | 185 | sub register_filter 186 | { 187 | my ( $self, $name, $obj ) = (@_); 188 | 189 | die "No name" unless ($name); 190 | $name = lc($name); 191 | $self->{ 'filters' }{ $name } = $obj; 192 | } 193 | 194 | 195 | 196 | =head2 register_plugin 197 | 198 | This method should be called by all variable-expanding plugins to register 199 | themselves. The expected argument is the class-name which may be instantiated 200 | to expand variables. 201 | 202 | L, L, and 203 | L are examples of such plugins. 204 | 205 | NOTE: The plugin is instantiated immediately, and kept alive for the duration 206 | of a templer-run. 207 | 208 | =cut 209 | 210 | sub register_plugin 211 | { 212 | my ( $self, $obj ) = (@_); 213 | 214 | push( @{ $self->{ 'plugins' } }, $obj->new() ); 215 | } 216 | 217 | 218 | 219 | =head2 expand_variables 220 | 221 | Expand variables via all loaded plugins. 222 | 223 | =cut 224 | 225 | sub expand_variables 226 | { 227 | my ( $self, $site, $page, $data ) = (@_); 228 | 229 | my $out; 230 | 231 | foreach my $plugin ( @{ $self->{ 'plugins' } } ) 232 | { 233 | if ( UNIVERSAL::can( $plugin, "expand_variables" ) ) 234 | { 235 | my %in = %$data; 236 | $out = $plugin->expand_variables( $site, $page, \%in ); 237 | $data = \%$out; 238 | } 239 | } 240 | return ($data); 241 | } 242 | 243 | 244 | 245 | =head2 cleanup 246 | 247 | For each loaded plugin invoke the "cleanup" method, if it exists. 248 | 249 | This can be useful if you wish a plugin to generate a site-map, or similar. 250 | 251 | =cut 252 | 253 | sub cleanup 254 | { 255 | my ($self) = (@_); 256 | 257 | foreach my $plugin ( @{ $self->{ 'plugins' } } ) 258 | { 259 | if ( UNIVERSAL::can( $plugin, "cleanup" ) ) 260 | { 261 | $plugin->cleanup(); 262 | } 263 | } 264 | } 265 | 266 | 267 | 268 | =head2 formatter 269 | 270 | Return a new instance of the formatter class with the given name. 271 | 272 | C is returned if no such plugin is registered. 273 | 274 | =cut 275 | 276 | sub formatter 277 | { 278 | my ( $self, $name ) = (@_); 279 | 280 | die "No name" unless ($name); 281 | $name = lc($name); 282 | 283 | # 284 | # Lookup the formatter by name, if it is found 285 | # then instantiate the clsee. 286 | # 287 | my $obj = $self->{ 'formatters' }{ $name } || undef; 288 | $obj = $obj->new() if ($obj); 289 | 290 | return ($obj); 291 | } 292 | 293 | 294 | 295 | =head2 formatters 296 | 297 | Return the names of each registered formatter-plugin, this is only 298 | used by the test-suite. 299 | 300 | =cut 301 | 302 | sub formatters 303 | { 304 | my ($self) = (@_); 305 | 306 | keys( %{ $self->{ 'formatters' } } ); 307 | } 308 | 309 | 310 | 311 | =head2 filter 312 | 313 | Return a new instance of the filter class with the given name. 314 | 315 | C is returned if no such plugin is registered. 316 | 317 | =cut 318 | 319 | sub filter 320 | { 321 | my ( $self, $name ) = (@_); 322 | 323 | die "No name" unless ($name); 324 | $name = lc($name); 325 | 326 | # 327 | # Lookup the filter by name, if it is found 328 | # then instantiate the class. 329 | # 330 | my $obj = $self->{ 'filters' }{ $name } || undef; 331 | $obj = $obj->new() if ($obj); 332 | 333 | return ($obj); 334 | } 335 | 336 | 337 | 338 | =head2 filters 339 | 340 | Return the names of each registered filter-plugin, this is only 341 | used by the test-suite. 342 | 343 | =cut 344 | 345 | sub filters 346 | { 347 | my ($self) = (@_); 348 | 349 | keys( %{ $self->{ 'filters' } } ); 350 | } 351 | 352 | 353 | 354 | 1; 355 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/FileContents.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::FileContents - A plugin to read file contents. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | passwd: read_file( /etc/passwd ) 14 | ---- 15 |

This is my password file:

16 | 17 | 18 | =cut 19 | 20 | =head1 DESCRIPTION 21 | 22 | This plugin reads the contents of files from the local system, 23 | and allows files to be included inline in templates. 24 | 25 | =cut 26 | 27 | =head1 LICENSE 28 | 29 | This module is free software; you can redistribute it and/or modify it 30 | under the terms of either: 31 | 32 | a) the GNU General Public License as published by the Free Software 33 | Foundation; either version 2, or (at your option) any later version, 34 | or 35 | 36 | b) the Perl "Artistic License". 37 | 38 | =cut 39 | 40 | =head1 AUTHOR 41 | 42 | Steve Kemp 43 | 44 | =cut 45 | 46 | =head1 COPYRIGHT AND LICENSE 47 | 48 | Copyright (C) 2012-2015 Steve Kemp . 49 | 50 | This library is free software. You can modify and or distribute it under 51 | the same terms as Perl itself. 52 | 53 | =cut 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | 60 | use strict; 61 | use warnings; 62 | 63 | 64 | package Templer::Plugin::FileContents; 65 | 66 | use File::Basename; 67 | 68 | 69 | 70 | =head2 71 | 72 | Constructor. No arguments are required/supported. 73 | 74 | =cut 75 | 76 | sub new 77 | { 78 | my ( $proto, %supplied ) = (@_); 79 | my $class = ref($proto) || $proto; 80 | 81 | my $self = {}; 82 | bless( $self, $class ); 83 | return $self; 84 | } 85 | 86 | 87 | 88 | =head2 expand_variables 89 | 90 | This is the method which is called by the L 91 | to expand the variables contained in a L object. 92 | 93 | Variables are written in the file in the form "key: value", and are 94 | internally stored within the Page object as a hash. 95 | 96 | This method iterates over each key & value and updates any that 97 | seem to refer to file-inclusion. 98 | 99 | =cut 100 | 101 | sub expand_variables 102 | { 103 | my ( $self, $site, $page, $data ) = (@_); 104 | 105 | # 106 | # Get the page-variables in the template. 107 | # 108 | my %hash = %$data; 109 | 110 | # 111 | # Look for a value of "read_file" in each key. 112 | # 113 | foreach my $key ( keys %hash ) 114 | { 115 | if ( $hash{ $key } =~ /^read_file\((.*)\)/ ) 116 | { 117 | 118 | # 119 | # Get the filename specified. 120 | # 121 | my $file = $1; 122 | 123 | # 124 | # Strip leading/trailing quotes and whitespace. 125 | # 126 | $file =~ s/['"]//g; 127 | $file =~ s/^\s+|\s+$//g; 128 | 129 | 130 | # 131 | # Are we reading the input page itself? 132 | # 133 | if ( $file eq "SELF" ) 134 | { 135 | $file = $page->source(); 136 | } 137 | elsif ( $file =~ /^\// ) 138 | { 139 | 140 | # File is absolute - no handling. 141 | } 142 | else 143 | { 144 | 145 | # 146 | # Relative file. We want to look for the file based upon the 147 | # global include-path with the current directory appended. 148 | # 149 | # In the case of multiple matches the last one 150 | # wins. i.e. The non-global one. 151 | # 152 | # 153 | 154 | # 155 | # Get the global variables from the configuration object. 156 | # 157 | my $dirs = $site->get("include-path"); 158 | my @dirs = @$dirs; 159 | 160 | # 161 | # Append the directory from the input page. 162 | # 163 | push( @dirs, File::Basename::dirname( $page->source() ) ); 164 | 165 | # 166 | # Iterate 167 | # 168 | foreach my $dir (@dirs) 169 | { 170 | if ( -e $dir . "/" . $file ) 171 | { 172 | $file = $dir . "/" . $file; 173 | } 174 | } 175 | } 176 | 177 | $hash{ $key } = $self->file_contents($file); 178 | 179 | # 180 | # The page dependency also includes the filename now. 181 | # 182 | $page->add_dependency($file); 183 | } 184 | } 185 | 186 | return ( \%hash ); 187 | } 188 | 189 | 190 | # 191 | # Return the contents of the named file. 192 | # 193 | sub file_contents 194 | { 195 | my ( $self, $name ) = (@_); 196 | 197 | my $content = ""; 198 | 199 | if ( -e $name ) 200 | { 201 | open( my $handle, "<:utf8", $name ) or 202 | return ""; 203 | 204 | binmode( $handle, ":utf8" ); 205 | 206 | while ( my $line = <$handle> ) 207 | { 208 | $content .= $line; 209 | } 210 | close($handle); 211 | } 212 | else 213 | { 214 | print "WARNING: Attempting to read a file that doesn't exist: $name\n"; 215 | } 216 | 217 | $content; 218 | } 219 | 220 | # 221 | # Register the plugin. 222 | # 223 | Templer::Plugin::Factory->new() 224 | ->register_plugin("Templer::Plugin::FileContents"); 225 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/FileGlob.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::FileGlob - A plugin to expand file globs. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: Images of cats 13 | images: file_glob( img/candid*.jpg ) 14 | ---- 15 |
  • 16 | 17 |
  • Animal & Pet Photography, Edinburgh
  • 18 | 19 | 20 | 21 | =cut 22 | 23 | =head1 DESCRIPTION 24 | 25 | This plugin operates on file-patterns and populates loops refering 26 | to the specified pattern. 27 | 28 | The intended use-case is inline-gallery generation, but more uses 29 | would surely be discovered. 30 | 31 | For each loop created there will be the variables: 32 | 33 | =over 8 34 | 35 | =item file 36 | 37 | The name of the file. 38 | 39 | =item height 40 | 41 | The height of the image, if the file is an image and L is available. 42 | 43 | =item width 44 | 45 | The width of the image, if the file is an image and L is available. 46 | 47 | =item contents 48 | 49 | The content of the file if the file is not an image and not a templer input file. 50 | 51 | =item dirname 52 | 53 | The directory part of the file name. 54 | 55 | =item basename 56 | 57 | The basename of the file (without extension). 58 | 59 | =item extension 60 | 61 | The extension (everything after the last period) of the file name. 62 | 63 | =back 64 | 65 | If matching files are templer input files then all templer variables are also 66 | populated. 67 | 68 | =cut 69 | 70 | =head1 LICENSE 71 | 72 | This module is free software; you can redistribute it and/or modify it 73 | under the terms of either: 74 | 75 | a) the GNU General Public License as published by the Free Software 76 | Foundation; either version 2, or (at your option) any later version, 77 | or 78 | 79 | b) the Perl "Artistic License". 80 | 81 | =cut 82 | 83 | =head1 AUTHOR 84 | 85 | Steve Kemp 86 | 87 | =cut 88 | 89 | =head1 COPYRIGHT AND LICENSE 90 | 91 | Copyright (C) 2012-2018 Steve Kemp . 92 | 93 | This library is free software. You can modify and or distribute it under 94 | the same terms as Perl itself. 95 | 96 | =cut 97 | 98 | =head1 METHODS 99 | 100 | =cut 101 | 102 | 103 | use strict; 104 | use warnings; 105 | 106 | 107 | package Templer::Plugin::FileGlob; 108 | 109 | use Cwd; 110 | use File::Basename; 111 | 112 | 113 | =head2 114 | 115 | Constructor. No arguments are required/supported. 116 | 117 | =cut 118 | 119 | sub new 120 | { 121 | my ( $proto, %supplied ) = (@_); 122 | my $class = ref($proto) || $proto; 123 | 124 | my $self = {}; 125 | bless( $self, $class ); 126 | return $self; 127 | } 128 | 129 | 130 | 131 | =head2 expand_variables 132 | 133 | This is the method which is called by the L 134 | to expand the variables contained in a L object. 135 | 136 | Variables are written in the file in the form "key: value", and are 137 | internally stored within the Page object as a hash. 138 | 139 | This method iterates over each key & value and updates any that 140 | seem to refer to file-globs. 141 | 142 | =cut 143 | 144 | sub expand_variables 145 | { 146 | my ( $self, $site, $page, $data ) = (@_); 147 | 148 | # 149 | # Get the page-variables in the template. 150 | # 151 | my %hash = %$data; 152 | 153 | # 154 | # Look for a value of "file_glob" in each key. 155 | # 156 | foreach my $key ( keys %hash ) 157 | { 158 | if ( $hash{ $key } =~ /^file_glob\((.*)\)/ ) 159 | { 160 | 161 | # 162 | # Populate an array of hash-refs referring to files which match 163 | # a particular glob. 164 | # 165 | # Could be used for many things, will be used for image-gallaries. 166 | # 167 | 168 | # 169 | # Get the pattern and strip leading/trailing quotes and whitespace. 170 | # 171 | my $pattern = $1; 172 | $pattern =~ s/['"]//g; 173 | $pattern =~ s/^\s+|\s+$//g; 174 | 175 | # 176 | # Make sure we're relative to the directory containing 177 | # the actual input-page. 178 | # 179 | # This way globs will match what the author expected. 180 | # 181 | my $dirName = $page->source(); 182 | if ( $dirName =~ /^(.*)\/(.*)$/ ) 183 | { 184 | $dirName = $1; 185 | } 186 | my $pwd = Cwd::cwd(); 187 | chdir( $dirName . "/" ); 188 | 189 | # 190 | # The value we'll set the variable to. 191 | # 192 | my $ref; 193 | 194 | # 195 | # The suffix of files that are Templer input-files. 196 | # 197 | my $suffix = $site->{ suffix }; 198 | 199 | 200 | # 201 | # Run the glob. 202 | # 203 | foreach my $file ( glob($pattern) ) 204 | { 205 | 206 | # 207 | # The page dependency also includes the matching filename now. 208 | # 209 | if ( $file =~ m{^/} ) 210 | { 211 | $page->add_dependency($file); 212 | } 213 | else 214 | { 215 | $page->add_dependency( $dirName . "/" . $file ); 216 | } 217 | 218 | # 219 | # Data reference - moved here so we can add height/width if the 220 | # glob refers to an image, and if we have Image::Size installed. 221 | # 222 | my %meta = ( file => $file ); 223 | 224 | # 225 | # Populate filename parts. 226 | # 227 | my ( $basename, $dirname, $extension ) = fileparse($file); 228 | ( $meta{ 'dirname' } = $dirname ) =~ s{/$}{}; 229 | ( $meta{ 'basename' } = $basename ) =~ s{(.*)\.([^.]*)$}{$1}; 230 | $meta{ 'extension' } = $2; 231 | 232 | # 233 | # If the file is an image AND we have Image::Size 234 | # then populate the height/width too. 235 | # 236 | if ( $file =~ /\.(jpe?g|png|gif)$/i ) 237 | { 238 | my $module = "use Image::Size;"; 239 | ## no critic (Eval) 240 | eval($module); 241 | ## use critic 242 | if ( !$@ ) 243 | { 244 | if ( -e $file ) 245 | { 246 | ( $meta{ 'width' }, $meta{ 'height' } ) = 247 | imgsize($file); 248 | } 249 | else 250 | { 251 | print 252 | "WARNING: Attempting to invoke Image::Size on a file that doesn't exist: $file\n"; 253 | } 254 | } 255 | } 256 | elsif ( ($suffix) && ( $file =~ /$suffix$/i ) ) 257 | { 258 | 259 | # 260 | # If the file is a Templer input file 261 | # then populate templer variables 262 | # 263 | my $pageClass = ref $page; 264 | my $globPage = $pageClass->new( file => $file ); 265 | while ( my ( $k, $v ) = each %{ $globPage } ) 266 | { 267 | $meta{ $k } = $v; 268 | } 269 | } 270 | else 271 | { 272 | 273 | # 274 | # If it isn't an image we'll make the content available 275 | # 276 | if ( open( my $handle, "<:utf8", $file ) ) 277 | { 278 | binmode( $handle, ":utf8" ); 279 | while ( my $line = <$handle> ) 280 | { 281 | $meta{ 'contents' } .= $line; 282 | } 283 | close($handle); 284 | } 285 | } 286 | 287 | push( @$ref, \%meta ); 288 | } 289 | 290 | # 291 | # If we found at least one file then we'll update 292 | # the variable value to refer to the populated structure. 293 | # 294 | if ($ref) 295 | { 296 | $hash{ $key } = $ref; 297 | } 298 | else 299 | { 300 | print 301 | "WARNING: pattern '$pattern' matched zero files for page " . 302 | $page->source() . "\n"; 303 | delete $hash{ $key }; 304 | } 305 | 306 | # 307 | # Restore the PWD. 308 | # 309 | chdir($pwd); 310 | } 311 | } 312 | 313 | return ( \%hash ); 314 | } 315 | 316 | 317 | # 318 | # Register the plugin. 319 | # 320 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::FileGlob"); 321 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/HTML.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::HTML - A simple (nop) HTML-formatting plugin 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a formatter plugin for C which allows 11 | pages to be written using HTML. 12 | 13 | It is provided as NOP. 14 | 15 | This allows input such as this to be executed: 16 | 17 | =for example begin 18 | 19 | Title: This is my page 20 | format: html 21 | ---- 22 |

    This is my page text

    23 | 24 | =for example end 25 | 26 | =cut 27 | 28 | =head1 LICENSE 29 | 30 | This module is free software; you can redistribute it and/or modify it 31 | under the terms of either: 32 | 33 | a) the GNU General Public License as published by the Free Software 34 | Foundation; either version 2, or (at your option) any later version, 35 | or 36 | 37 | b) the Perl "Artistic License". 38 | 39 | =cut 40 | 41 | =head1 AUTHOR 42 | 43 | Steve Kemp 44 | 45 | =cut 46 | 47 | =head1 COPYRIGHT AND LICENSE 48 | 49 | Copyright (C) 2012-2015 Steve Kemp . 50 | 51 | This library is free software. You can modify and or distribute it under 52 | the same terms as Perl itself. 53 | 54 | =cut 55 | 56 | =head1 METHODS 57 | 58 | =cut 59 | 60 | 61 | use strict; 62 | use warnings; 63 | 64 | 65 | package Templer::Plugin::HTML; 66 | 67 | 68 | =head2 new 69 | 70 | Constructor. No arguments are supported/expected. 71 | 72 | =cut 73 | 74 | sub new 75 | { 76 | my ( $proto, %supplied ) = (@_); 77 | my $class = ref($proto) || $proto; 78 | 79 | my $self = {}; 80 | bless( $self, $class ); 81 | return $self; 82 | } 83 | 84 | 85 | =head2 available 86 | 87 | This plugin is always available. 88 | 89 | =cut 90 | 91 | sub available 92 | { 93 | return 1; 94 | } 95 | 96 | 97 | =head2 format 98 | 99 | Format the given text: In our case return it unmodified. 100 | 101 | =cut 102 | 103 | sub format 104 | { 105 | my ( $self, $str, $data ) = (@_); 106 | 107 | return ($str); 108 | } 109 | 110 | Templer::Plugin::Factory->new() 111 | ->register_formatter( "html", "Templer::Plugin::HTML" ); 112 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Hash.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Hash - Create SHA1 hashes from file contents. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | hash: hash_file( "slaughter-2.7.tar.gz" ) 14 | ---- 15 |

    has hash

    16 | 17 | 18 | =cut 19 | 20 | =head1 DESCRIPTION 21 | 22 | Given a use of hash_file two variables will be populated, one containing the 23 | path to the file, and one containing the hash. 24 | 25 | =cut 26 | 27 | =head1 LICENSE 28 | 29 | This module is free software; you can redistribute it and/or modify it 30 | under the terms of either: 31 | 32 | a) the GNU General Public License as published by the Free Software 33 | Foundation; either version 2, or (at your option) any later version, 34 | or 35 | 36 | b) the Perl "Artistic License". 37 | 38 | =cut 39 | 40 | =head1 AUTHOR 41 | 42 | Steve Kemp 43 | 44 | =cut 45 | 46 | =head1 COPYRIGHT AND LICENSE 47 | 48 | Copyright (C) 2015-2015 Steve Kemp . 49 | 50 | This library is free software. You can modify and or distribute it under 51 | the same terms as Perl itself. 52 | 53 | =cut 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | 60 | use strict; 61 | use warnings; 62 | 63 | 64 | package Templer::Plugin::Hash; 65 | 66 | 67 | =head2 new 68 | 69 | Constructor. No arguments are required/supported. 70 | 71 | =cut 72 | 73 | sub new 74 | { 75 | my ( $proto, %supplied ) = (@_); 76 | my $class = ref($proto) || $proto; 77 | 78 | my $self = {}; 79 | bless( $self, $class ); 80 | return $self; 81 | } 82 | 83 | 84 | 85 | =head2 expand_variables 86 | 87 | This is the method which is called by the L 88 | to expand the variables contained in a L object. 89 | 90 | This method will expand any variable that has a value of 'hash_file(.*)' 91 | into two variables accessible from your template. 92 | 93 | =cut 94 | 95 | sub expand_variables 96 | { 97 | my ( $self, $site, $page, $data ) = (@_); 98 | 99 | # 100 | # Get the page-variables in the template. 101 | # 102 | my %hash = %$data; 103 | 104 | # 105 | # Look for a value of "read_file" in each key. 106 | # 107 | foreach my $key ( keys %hash ) 108 | { 109 | if ( $hash{ $key } =~ /^hash_file\((.*)\)/ ) 110 | { 111 | 112 | # 113 | # Get the filename specified. 114 | # 115 | my $file = $1; 116 | 117 | # 118 | # Strip leading/trailing quotes and whitespace. 119 | # 120 | $file =~ s/['"]//g; 121 | $file =~ s/^\s+|\s+$//g; 122 | 123 | # 124 | # If the file is unqualified then make it refer to the 125 | # path of the source file. 126 | # 127 | my $dirName = $page->source(); 128 | if ( $dirName =~ /^(.*)\/(.*)$/ ) 129 | { 130 | $dirName = $1; 131 | } 132 | my $pwd = Cwd::cwd(); 133 | chdir( $dirName . "/" ); 134 | 135 | # 136 | # 137 | # Setup the two new variables. 138 | # 139 | $hash{ $key . "_src" } = $file; 140 | 141 | my $sha1 = $self->hash_file($file); 142 | $hash{ $key . "_hash" } = $sha1; 143 | 144 | if ( $site->{ 'verbose' } ) 145 | { 146 | print "Hash of $file is $sha1\n"; 147 | } 148 | 149 | 150 | # 151 | # Delete the original one. 152 | # 153 | delete $hash{ $key }; 154 | 155 | 156 | # 157 | # Restore the PWD. 158 | # 159 | chdir($pwd); 160 | 161 | } 162 | } 163 | 164 | return ( \%hash ); 165 | } 166 | 167 | # 168 | # Return the SHA1 hash of the file contents. 169 | # 170 | sub hash_file 171 | { 172 | my ( $self, $file ) = (@_); 173 | 174 | my $hash = undef; 175 | 176 | foreach my $module (qw! Digest::SHA Digest::SHA1 !) 177 | { 178 | 179 | # If we succeeded in calculating the hash we're done. 180 | next if ( defined($hash) ); 181 | 182 | # Attempt to load the module 183 | my $eval = "use $module;"; 184 | 185 | ## no critic (Eval) 186 | eval($eval); 187 | ## use critic 188 | 189 | # 190 | # Loaded module, with no errors. 191 | # 192 | if ( !$@ ) 193 | { 194 | my $object = $module->new; 195 | 196 | open my $handle, "<", $file or 197 | die "Failed to read $file to hash contents with $module - $!"; 198 | $object->addfile($handle); 199 | close($handle); 200 | 201 | $hash = $object->hexdigest(); 202 | } 203 | } 204 | 205 | unless ( defined $hash ) 206 | { 207 | die "Failed to calculate hash of $file - internal error."; 208 | } 209 | 210 | return ($hash); 211 | } 212 | 213 | 214 | # 215 | # Register the plugin. 216 | # 217 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::Hash"); 218 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Markdown.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Markdown - A simple markdown-formatting plugin 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a formatter plugin for C which allows 11 | pages to be written using markdown. 12 | 13 | This allows input such as this to be executed: 14 | 15 | =for example begin 16 | 17 | Title: This is my page 18 | format: markdown 19 | ---- 20 | **Bold** 21 | _italic_ 22 | Content! 23 | 24 | =for example end 25 | 26 | =cut 27 | 28 | =head1 LICENSE 29 | 30 | This module is free software; you can redistribute it and/or modify it 31 | under the terms of either: 32 | 33 | a) the GNU General Public License as published by the Free Software 34 | Foundation; either version 2, or (at your option) any later version, 35 | or 36 | 37 | b) the Perl "Artistic License". 38 | 39 | =cut 40 | 41 | =head1 AUTHOR 42 | 43 | Steve Kemp 44 | 45 | =cut 46 | 47 | =head1 COPYRIGHT AND LICENSE 48 | 49 | Copyright (C) 2012-2015 Steve Kemp . 50 | 51 | This library is free software. You can modify and or distribute it under 52 | the same terms as Perl itself. 53 | 54 | =cut 55 | 56 | =head1 METHODS 57 | 58 | =cut 59 | 60 | 61 | use strict; 62 | use warnings; 63 | 64 | 65 | package Templer::Plugin::Markdown; 66 | 67 | 68 | =head2 new 69 | 70 | Constructor. No arguments are supported/expected. 71 | 72 | =cut 73 | 74 | sub new 75 | { 76 | my ( $proto, %supplied ) = (@_); 77 | my $class = ref($proto) || $proto; 78 | 79 | my $self = {}; 80 | bless( $self, $class ); 81 | return $self; 82 | } 83 | 84 | 85 | =head2 available 86 | 87 | This plugin is available if we've got the L module. 88 | 89 | =cut 90 | 91 | sub available 92 | { 93 | my $str = "use Text::Markdown;"; 94 | 95 | ## no critic (Eval) 96 | eval($str); 97 | ## use critic 98 | 99 | return ( $@ ? undef : 1 ); 100 | } 101 | 102 | 103 | =head2 format 104 | 105 | Format the given text. 106 | 107 | =cut 108 | 109 | sub format 110 | { 111 | my ( $self, $str, $data ) = (@_); 112 | 113 | if ( $self->available() ) 114 | { 115 | Text::Markdown::markdown($str); 116 | } 117 | else 118 | { 119 | warn 120 | "Markdown formatting disabled as the Text::Markdown module isn't present.\n"; 121 | 122 | $str; 123 | } 124 | } 125 | 126 | Templer::Plugin::Factory->new() 127 | ->register_formatter( "markdown", "Templer::Plugin::Markdown" ); 128 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Perl.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Perl - A simple inline-perl plugin 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a formatter plugin for C which allows 11 | inline code to be executed as the page is generated. 12 | 13 | This is carried out via the L module, and allows input 14 | such as this to be executed: 15 | 16 | =for example begin 17 | 18 | Title: This is my page 19 | format: perl 20 | name: Steve 21 | ---- 22 |

    The sum of 1 + 1 is { 1 + 1 }.

    23 |

    My name is { $name }

    24 | 25 | =for example end 26 | 27 | =cut 28 | 29 | =head1 LICENSE 30 | 31 | This module is free software; you can redistribute it and/or modify it 32 | under the terms of either: 33 | 34 | a) the GNU General Public License as published by the Free Software 35 | Foundation; either version 2, or (at your option) any later version, 36 | or 37 | 38 | b) the Perl "Artistic License". 39 | 40 | =cut 41 | 42 | =head1 AUTHOR 43 | 44 | Steve Kemp 45 | 46 | =cut 47 | 48 | =head1 COPYRIGHT AND LICENSE 49 | 50 | Copyright (C) 2012-2015 Steve Kemp . 51 | 52 | This library is free software. You can modify and or distribute it under 53 | the same terms as Perl itself. 54 | 55 | =cut 56 | 57 | =head1 METHODS 58 | 59 | =cut 60 | 61 | 62 | use strict; 63 | use warnings; 64 | 65 | 66 | package Templer::Plugin::Perl; 67 | 68 | 69 | 70 | =head2 new 71 | 72 | Constructor. No arguments are supported/expected. 73 | 74 | =cut 75 | 76 | sub new 77 | { 78 | my ( $proto, %supplied ) = (@_); 79 | my $class = ref($proto) || $proto; 80 | 81 | my $self = {}; 82 | bless( $self, $class ); 83 | return $self; 84 | } 85 | 86 | 87 | =head2 available 88 | 89 | This plugin is available if we've got the L module. 90 | 91 | =cut 92 | 93 | sub available 94 | { 95 | my $str = "use Text::Template;"; 96 | 97 | ## no critic (Eval) 98 | eval($str); 99 | ## use critic 100 | 101 | return ( $@ ? undef : 1 ); 102 | } 103 | 104 | 105 | =head2 format 106 | 107 | Format the given text. 108 | 109 | =cut 110 | 111 | sub format 112 | { 113 | my ( $self, $str, $data ) = (@_); 114 | 115 | if ( $self->available() ) 116 | { 117 | my $template = 118 | Text::Template->new( TYPE => "STRING", 119 | SOURCE => $str ); 120 | return ( $template->fill_in( HASH => $data ) ); 121 | } 122 | else 123 | { 124 | warn 125 | "Perl formatting disabled as the Text::Template module isn't present.\n"; 126 | $str; 127 | } 128 | } 129 | 130 | Templer::Plugin::Factory->new() 131 | ->register_formatter( "perl", "Templer::Plugin::Perl" ); 132 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/RSS.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::RSS - A plugin to include RSS feeds in pages. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | feed: rss(4, http://blog.steve.org.uk/index.rss ) 14 | ---- 15 |

    This is my page content.

    16 |
      17 | 18 |
    • 19 | 20 |
    21 | 22 | 23 | Here the variable 'feed' will contain the first four elements of the 24 | RSS feed my blog produces. 25 | 26 | The feed entries will contain the following three attributes 27 | 28 | =over 8 29 | 30 | =item author 31 | The author of the post. 32 | 33 | =item link 34 | The link to the post. 35 | 36 | =item title 37 | The title of the popst 38 | 39 | =back 40 | 41 | =cut 42 | 43 | =head1 DESCRIPTION 44 | 45 | This plugin uses L to extract remote RSS feeds and allow 46 | them to be included in your site bodies - if that module is not 47 | available then the plugin will disable itself. 48 | 49 | =cut 50 | 51 | =head1 LICENSE 52 | 53 | This module is free software; you can redistribute it and/or modify it 54 | under the terms of either: 55 | 56 | a) the GNU General Public License as published by the Free Software 57 | Foundation; either version 2, or (at your option) any later version, 58 | or 59 | 60 | b) the Perl "Artistic License". 61 | 62 | =cut 63 | 64 | =head1 AUTHOR 65 | 66 | Steve Kemp 67 | 68 | =cut 69 | 70 | =head1 COPYRIGHT AND LICENSE 71 | 72 | Copyright (C) 2015 Steve Kemp . 73 | 74 | This library is free software. You can modify and or distribute it under 75 | the same terms as Perl itself. 76 | 77 | =cut 78 | 79 | =head1 METHODS 80 | 81 | =cut 82 | 83 | 84 | use strict; 85 | use warnings; 86 | 87 | 88 | package Templer::Plugin::RSS; 89 | 90 | 91 | =head2 new 92 | 93 | Constructor. No arguments are required/supported. 94 | 95 | =cut 96 | 97 | sub new 98 | { 99 | my ( $proto, %supplied ) = (@_); 100 | my $class = ref($proto) || $proto; 101 | 102 | my $self = {}; 103 | bless( $self, $class ); 104 | return $self; 105 | } 106 | 107 | 108 | 109 | =head2 expand_variables 110 | 111 | This is the method which is called by the L 112 | to expand the variables contained in a L object. 113 | 114 | This method will expand any variable that has a defintion of the 115 | form "rss( NN, http... )" and replace the variable definition 116 | with the result of fetching that RSS feed. 117 | 118 | =cut 119 | 120 | sub expand_variables 121 | { 122 | my ( $self, $site, $page, $data ) = (@_); 123 | 124 | 125 | # 126 | # Get the page-variables in the template. 127 | # 128 | my %hash = %$data; 129 | 130 | 131 | # 132 | # Load XML::Feed if we can 133 | # 134 | my $module = "use XML::Feed"; 135 | 136 | ## no critic (Eval) 137 | eval($module); 138 | ## use critic 139 | 140 | # 141 | # If there were errors loading the module then we're done. 142 | # 143 | return ( \%hash ) if ($@); 144 | 145 | # 146 | # Look for a value of "read_file" in each key. 147 | # 148 | foreach my $key ( keys %hash ) 149 | { 150 | if ( $hash{ $key } =~ /^rss\(\s?([0-9]+)\s?,\s?(https?:\/\/.*)\s?\)/ ) 151 | { 152 | my $count = $1; 153 | my $link = $2; 154 | 155 | $link =~ s/^\s+|\s+$//g; 156 | $count =~ s/^\s+|\s+$//g; 157 | 158 | # remove the variable. 159 | delete( $hash{ $key } ); 160 | 161 | # try to parse the feed. 162 | my $feed = XML::Feed->parse( URI->new($link) ); 163 | 164 | if ($feed) 165 | { 166 | my $tmp; 167 | 168 | # loop over entries. 169 | for my $entry ( $feed->entries ) 170 | { 171 | if ( $count > 0 ) 172 | { 173 | push( @$tmp, 174 | { link => $entry->link, 175 | author => $entry->author, 176 | title => $entry->title 177 | } ); 178 | } 179 | $count = $count - 1; 180 | } 181 | 182 | # store the expanded feed. 183 | $hash{ $key } = $tmp; 184 | } 185 | else 186 | { 187 | print "WARNING: Failed to fetch $link\n"; 188 | } 189 | } 190 | } 191 | 192 | return ( \%hash ); 193 | } 194 | 195 | 196 | # 197 | # Register the plugin. 198 | # 199 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::RSS"); 200 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Redis.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Redis - A plugin to retrieve values from Redis 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | count: redis_get( "total_count" ) 14 | ---- 15 |

    There are ponies.

    16 | 17 | =cut 18 | 19 | =head1 DESCRIPTION 20 | 21 | This plugin allows template variables to be values retrieved from 22 | a redis store. 23 | 24 | It is assumed that redis will be running on the localhost, if it is 25 | not you may set the environmental variable C to point 26 | to your IP:port pair. 27 | 28 | =cut 29 | 30 | =head1 LICENSE 31 | 32 | This module is free software; you can redistribute it and/or modify it 33 | under the terms of either: 34 | 35 | a) the GNU General Public License as published by the Free Software 36 | Foundation; either version 2, or (at your option) any later version, 37 | or 38 | 39 | b) the Perl "Artistic License". 40 | 41 | =cut 42 | 43 | =head1 AUTHOR 44 | 45 | Steve Kemp 46 | 47 | =cut 48 | 49 | =head1 COPYRIGHT AND LICENSE 50 | 51 | Copyright (C) 2015 Steve Kemp . 52 | 53 | This library is free software. You can modify and or distribute it under 54 | the same terms as Perl itself. 55 | 56 | =cut 57 | 58 | =head1 METHODS 59 | 60 | =cut 61 | 62 | 63 | use strict; 64 | use warnings; 65 | 66 | 67 | package Templer::Plugin::Redis; 68 | 69 | 70 | =head2 71 | 72 | Constructor. No arguments are required/supported. 73 | 74 | =cut 75 | 76 | sub new 77 | { 78 | my ( $proto, %supplied ) = (@_); 79 | my $class = ref($proto) || $proto; 80 | 81 | my $self = {}; 82 | 83 | 84 | bless( $self, $class ); 85 | 86 | my $module = "use Redis;"; 87 | 88 | ## no critic (Eval) 89 | eval($module); 90 | ## use critic 91 | 92 | if ( !$@ ) 93 | { 94 | 95 | # 96 | # OK the module was loaded, but Redis might not be 97 | # running locally, or accessible remotely so in those 98 | # cases we'll be disabled. 99 | # 100 | # NOTE: Redis will use $ENV{'REDIS_SERVER'} if that is 101 | # set, otherwise defaulting to 127.0.0.1:6379. 102 | # 103 | eval {$self->{ 'redis' } = new Redis()}; 104 | } 105 | 106 | return $self; 107 | } 108 | 109 | 110 | =head2 expand_variables 111 | 112 | This is the method which is called by the L 113 | to expand the variables contained in a L object. 114 | 115 | Variables are written in the file in the form "key: value", and are 116 | internally stored within the Page object as a hash. 117 | 118 | This method iterates over each key & value and updates any that 119 | seem to refer to redis-fetches. 120 | 121 | =cut 122 | 123 | sub expand_variables 124 | { 125 | my ( $self, $site, $page, $data ) = (@_); 126 | 127 | # 128 | # Get the page-variables in the template. 129 | # 130 | my %hash = %$data; 131 | 132 | # 133 | # Look for a value of "redis_get" in each key. 134 | # 135 | foreach my $key ( keys %hash ) 136 | { 137 | if ( $hash{ $key } =~ /^redis_get\((.*)\)/ ) 138 | { 139 | 140 | # 141 | # The lookup value. 142 | # 143 | my $rkey = $1; 144 | 145 | # 146 | # Strip leading/trailing whitespace and quotes 147 | # 148 | $rkey =~ s/^\s+|\s+$//g; 149 | $rkey =~ s/^["']|['"]$//g; 150 | 151 | # 152 | # If we have redis, and it is alive/connected, then use it. 153 | # 154 | if ( $self->{ 'redis' } && 155 | $self->{ 'redis' }->ping() ) 156 | { 157 | $hash{ $key } = $self->{ 'redis' }->get($rkey); 158 | } 159 | } 160 | 161 | } 162 | 163 | # 164 | # Return. 165 | # 166 | return ( \%hash ); 167 | } 168 | 169 | 170 | # 171 | # Register the plugin. 172 | # 173 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::Redis"); 174 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/RootPath.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::RootPath - A plugin to add path to web-root to variables. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | css: path_to(css) 14 | ---- 15 |

    CSS files are stored in . 16 | 17 | This is mainly useful as global variables used in the default layout. 18 | 19 | =cut 20 | 21 | =head1 DESCRIPTION 22 | 23 | This plugin allows template variables (considered as absolute path from the 24 | web-root) to be set to a correct relative path from any page of the website. 25 | 26 | An empty call to C may be used to compute the relative path to the 27 | web-root. 28 | 29 | =cut 30 | 31 | =head1 LICENSE 32 | 33 | This module is free software; you can redistribute it and/or modify it 34 | under the terms of either: 35 | 36 | a) the GNU General Public License as published by the Free Software 37 | Foundation; either version 2, or (at your option) any later version, 38 | or 39 | 40 | b) the Perl "Artistic License". 41 | 42 | =cut 43 | 44 | =head1 AUTHOR 45 | 46 | Bruno BEAUFILS 47 | 48 | =cut 49 | 50 | =head1 COPYRIGHT AND LICENSE 51 | 52 | Copyright (C) 2015 Bruno BEAUFILS . 53 | 54 | This library is free software. You can modify and or distribute it under 55 | the same terms as Perl itself. 56 | 57 | =cut 58 | 59 | =head1 METHODS 60 | 61 | =cut 62 | 63 | 64 | use strict; 65 | use warnings; 66 | 67 | 68 | package Templer::Plugin::RootPath; 69 | 70 | 71 | =head2 72 | 73 | Constructor. No arguments are required/supported. 74 | 75 | =cut 76 | 77 | sub new 78 | { 79 | my ( $proto, %supplied ) = (@_); 80 | my $class = ref($proto) || $proto; 81 | 82 | my $self = {}; 83 | bless( $self, $class ); 84 | return $self; 85 | } 86 | 87 | 88 | =head2 expand_variables 89 | 90 | This is the method which is called by the L 91 | to expand the variables contained in a L object. 92 | 93 | Variables are written in the file in the form "key: value", and are 94 | internally stored within the Page object as a hash. 95 | 96 | This method iterates over each key & value and updates any that 97 | seem to refer to file-paths. 98 | 99 | =cut 100 | 101 | sub expand_variables 102 | { 103 | my ( $self, $site, $page, $data ) = (@_); 104 | 105 | # 106 | # Get the page-variables in the template. 107 | # 108 | my %hash = %$data; 109 | 110 | # 111 | # Compute the path to the web-root 112 | # 113 | return ( \%hash ) if ( !$page ); 114 | 115 | my $root_path = $page->source() || ""; 116 | my $input = $site->get("input") || ""; 117 | $root_path =~ s{^$input}{./}; # Change leading input path 118 | $root_path =~ s{[^/]+$}{}; # Remove trailing pagename 119 | $root_path =~ s{/[^/]+}{/..}g; # Replace directories by .. 120 | $root_path =~ s{/$}{}; # Remove trailing / 121 | $root_path =~ s{^./}{}; # Remove leading ./ if still there 122 | 123 | 124 | # 125 | # Look for a value of "path_to" in each key. 126 | # 127 | foreach my $key ( keys %hash ) 128 | { 129 | if ( $hash{ $key } =~ /^path_to\((.*)\)/ ) 130 | { 131 | my $path = $1; 132 | 133 | # 134 | # Strip leading/trailing whitespace. 135 | # 136 | $path =~ s/^\s+|\s+$//g; 137 | 138 | # 139 | # Ensure path is absolute 140 | # 141 | if ($path) 142 | { 143 | $path = "/$path"; 144 | $path =~ s{^/+}{/}; 145 | } 146 | 147 | # 148 | # Store specified path starting from web-root. 149 | # 150 | $hash{ $key } = "$root_path$path"; 151 | } 152 | } 153 | 154 | # 155 | # Return. 156 | # 157 | return ( \%hash ); 158 | } 159 | 160 | 161 | # 162 | # Register the plugin. 163 | # 164 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::RootPath"); 165 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/ShellCommand.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::ShellCommand - A plugin to execute commands. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | hostname: run_command( hostname ) 14 | uptime: run_command( uptime ) 15 | ---- 16 |

    This is , with uptime of 17 | .

    18 | 19 | =cut 20 | 21 | =head1 DESCRIPTION 22 | 23 | This plugin allows template variables to be set to the output of 24 | executing shell-commands. 25 | 26 | =cut 27 | 28 | =head1 LICENSE 29 | 30 | This module is free software; you can redistribute it and/or modify it 31 | under the terms of either: 32 | 33 | a) the GNU General Public License as published by the Free Software 34 | Foundation; either version 2, or (at your option) any later version, 35 | or 36 | 37 | b) the Perl "Artistic License". 38 | 39 | =cut 40 | 41 | =head1 AUTHOR 42 | 43 | Steve Kemp 44 | 45 | =cut 46 | 47 | =head1 COPYRIGHT AND LICENSE 48 | 49 | Copyright (C) 2012-2015 Steve Kemp . 50 | 51 | This library is free software. You can modify and or distribute it under 52 | the same terms as Perl itself. 53 | 54 | =cut 55 | 56 | =head1 METHODS 57 | 58 | =cut 59 | 60 | 61 | use strict; 62 | use warnings; 63 | 64 | 65 | package Templer::Plugin::ShellCommand; 66 | 67 | 68 | =head2 69 | 70 | Constructor. No arguments are required/supported. 71 | 72 | =cut 73 | 74 | sub new 75 | { 76 | my ( $proto, %supplied ) = (@_); 77 | my $class = ref($proto) || $proto; 78 | 79 | my $self = {}; 80 | bless( $self, $class ); 81 | return $self; 82 | } 83 | 84 | 85 | =head2 expand_variables 86 | 87 | This is the method which is called by the L 88 | to expand the variables contained in a L object. 89 | 90 | Variables are written in the file in the form "key: value", and are 91 | internally stored within the Page object as a hash. 92 | 93 | This method iterates over each key & value and updates any that 94 | seem to refer to shell commands. 95 | 96 | =cut 97 | 98 | sub expand_variables 99 | { 100 | my ( $self, $site, $page, $data ) = (@_); 101 | 102 | # 103 | # Get the page-variables in the template. 104 | # 105 | my %hash = %$data; 106 | 107 | # 108 | # Look for a value of "run_command" in each key. 109 | # 110 | foreach my $key ( keys %hash ) 111 | { 112 | if ( $hash{ $key } =~ /^run_command\((.*)\)/ ) 113 | { 114 | 115 | # 116 | # Run a system command, and capture the output. 117 | # 118 | my $cmd = $1; 119 | 120 | # 121 | # Strip leading/trailing whitespace. 122 | # 123 | $cmd =~ s/^\s+|\s+$//g; 124 | 125 | $hash{ $key } = `$cmd`; 126 | } 127 | 128 | } 129 | 130 | # 131 | # Return. 132 | # 133 | return ( \%hash ); 134 | } 135 | 136 | 137 | # 138 | # Register the plugin. 139 | # 140 | Templer::Plugin::Factory->new() 141 | ->register_plugin("Templer::Plugin::ShellCommand"); 142 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/SiteMap.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::SiteMap - Generate a SiteMap automatically. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | This plugin must be enabled by adding two settings in the 11 | global configuration file; the name of the file to generate 12 | and the prefix of the output URLs. 13 | 14 | The following is a good example: 15 | 16 | =for example begin 17 | 18 | sitemap_file = /sitemap.xml 19 | sitemap_base = http://example.com/ 20 | 21 | =for example end 22 | 23 | =cut 24 | 25 | =head1 DESCRIPTION 26 | 27 | This plugin will generate a simple C file including 28 | references to all pages which C knows about. 29 | 30 | =cut 31 | 32 | =head1 LICENSE 33 | 34 | This module is free software; you can redistribute it and/or modify it 35 | under the terms of either: 36 | 37 | a) the GNU General Public License as published by the Free Software 38 | Foundation; either version 2, or (at your option) any later version, 39 | or 40 | 41 | b) the Perl "Artistic License". 42 | 43 | =cut 44 | 45 | =head1 AUTHOR 46 | 47 | Steve Kemp 48 | 49 | =cut 50 | 51 | =head1 COPYRIGHT AND LICENSE 52 | 53 | Copyright (C) 2016 Steve Kemp . 54 | 55 | This library is free software. You can modify and or distribute it under 56 | the same terms as Perl itself. 57 | 58 | =cut 59 | 60 | =head1 METHODS 61 | 62 | =cut 63 | 64 | 65 | 66 | use strict; 67 | use warnings; 68 | 69 | use POSIX qw(strftime); 70 | 71 | package Templer::Plugin::SiteMap; 72 | 73 | 74 | =head2 new 75 | 76 | Constructor. No arguments are required/supported. 77 | 78 | =cut 79 | 80 | sub new 81 | { 82 | my ( $proto, %supplied ) = (@_); 83 | my $class = ref($proto) || $proto; 84 | 85 | my $self = {}; 86 | bless( $self, $class ); 87 | return $self; 88 | } 89 | 90 | 91 | =head2 init 92 | 93 | Initialisation function, which merely saves a reference to the 94 | L object. 95 | 96 | =cut 97 | 98 | 99 | sub init 100 | { 101 | my( $self, $site ) = ( @_); 102 | 103 | $self->{ 'site' } ||= $site; 104 | } 105 | 106 | 107 | =head2 cleanup 108 | 109 | This method is invoked when site-generation is complete, and this 110 | is where we generate the sitemap, if our two required configuration 111 | values are present in the configuration file. 112 | 113 | If configuration-variables are not setup then we do nothing. 114 | 115 | =cut 116 | 117 | sub cleanup 118 | { 119 | my ($self) = (@_); 120 | 121 | # 122 | # Gain access to our expected configuration values. 123 | # 124 | my $file = $self->{'site'}->{'sitemap_file'}; 125 | my $base = $self->{'site'}->{'sitemap_base'}; 126 | my $path = $self->{'site'}->{'output'}; 127 | 128 | return unless ($file && $base && $path ); 129 | 130 | # 131 | # The pages we know about. 132 | # 133 | my $pages = $self->{ 'site' }->{ 'output-files' }; 134 | 135 | # 136 | # Open the sitemap file 137 | # 138 | open( my $map, ">", $path . $file ); 139 | print $map < 141 | 146 | 147 | $base 148 | 0.75 149 | daily 150 | 151 | EOF 152 | 153 | 154 | # 155 | # For each page 156 | # 157 | foreach my $page (@$pages) 158 | { 159 | my $url = substr( $page, length($path) ); 160 | print $map < 162 | $base$url 163 | 0.50 164 | weekly 165 | 166 | EOF 167 | } 168 | 169 | print $map < 171 | EOF 172 | close($map); 173 | } 174 | 175 | # 176 | # Register the plugin. 177 | # 178 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::SiteMap"); 179 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Strict.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Strict - A XML-like strict syntax for templates tags. 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a layout template filter plugin for C which 11 | allows empty-element template tags to be written as in XML. 12 | 13 | This allows the following syntax 14 | 15 | =over 16 | 17 | =item C<> 18 | 19 | =item C<> 20 | 21 | =item C<> 22 | 23 | =back 24 | 25 | =cut 26 | 27 | =head1 LICENSE 28 | 29 | This module is free software; you can redistribute it and/or modify it 30 | under the terms of either: 31 | 32 | a) the GNU General Public License as published by the Free Software 33 | Foundation; either version 2, or (at your option) any later version, 34 | or 35 | 36 | b) the Perl "Artistic License". 37 | 38 | =cut 39 | 40 | =head1 AUTHOR 41 | 42 | Bruno Beaufils 43 | 44 | =cut 45 | 46 | =head1 COPYRIGHT AND LICENSE 47 | 48 | Copyright (C) 2015 Bruno Beaufils . 49 | 50 | This library is free software. You can modify and or distribute it under 51 | the same terms as Perl itself. 52 | 53 | =cut 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | 60 | use strict; 61 | use warnings; 62 | 63 | 64 | package Templer::Plugin::Strict; 65 | 66 | 67 | =head2 new 68 | 69 | Constructor. No arguments are supported/expected. 70 | 71 | =cut 72 | 73 | sub new 74 | { 75 | my ( $proto, %supplied ) = (@_); 76 | my $class = ref($proto) || $proto; 77 | 78 | my $self = {}; 79 | bless( $self, $class ); 80 | return $self; 81 | } 82 | 83 | 84 | =head2 available 85 | 86 | This plugin is always available. 87 | 88 | =cut 89 | 90 | sub available 91 | { 92 | return 1; 93 | } 94 | 95 | 96 | =head2 filter 97 | 98 | Filter the given template. 99 | 100 | =cut 101 | 102 | sub filter 103 | { 104 | my ( $self, $str ) = (@_); 105 | 106 | $str =~ s{\/]*)/\>}{}ig; 107 | $str =~ s{\/]*)/\>}{}ig; 108 | $str =~ s{\}{}ig; 109 | 110 | return $str; 111 | } 112 | 113 | Templer::Plugin::Factory->new() 114 | ->register_filter( "strict", "Templer::Plugin::Strict" ); 115 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/Textile.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::Textile - A simple textile-formatting plugin. 5 | 6 | =cut 7 | 8 | =head1 DESCRIPTION 9 | 10 | This class implements a formatter plugin for C which allows 11 | pages to be written using textile. 12 | 13 | This allows input such as this to be executed: 14 | 15 | =for example begin 16 | 17 | Title: This is my page 18 | format: textile 19 | ---- 20 | **Bold** 21 | _italic_ 22 | Content! 23 | 24 | =for example end 25 | 26 | =cut 27 | 28 | =head1 LICENSE 29 | 30 | This module is free software; you can redistribute it and/or modify it 31 | under the terms of either: 32 | 33 | a) the GNU General Public License as published by the Free Software 34 | Foundation; either version 2, or (at your option) any later version, 35 | or 36 | 37 | b) the Perl "Artistic License". 38 | 39 | =cut 40 | 41 | =head1 AUTHOR 42 | 43 | Steve Kemp 44 | 45 | =cut 46 | 47 | =head1 COPYRIGHT AND LICENSE 48 | 49 | Copyright (C) 2012-2015 Steve Kemp . 50 | 51 | This library is free software. You can modify and or distribute it under 52 | the same terms as Perl itself. 53 | 54 | =cut 55 | 56 | =head1 METHODS 57 | 58 | =cut 59 | 60 | 61 | use strict; 62 | use warnings; 63 | 64 | 65 | package Templer::Plugin::Textile; 66 | 67 | 68 | =head2 new 69 | 70 | Constructor. No arguments are supported/expected. 71 | 72 | =cut 73 | 74 | 75 | sub new 76 | { 77 | my ( $proto, %supplied ) = (@_); 78 | my $class = ref($proto) || $proto; 79 | 80 | my $self = {}; 81 | bless( $self, $class ); 82 | return $self; 83 | } 84 | 85 | 86 | =head2 available 87 | 88 | This plugin is available if we've got the L module. 89 | 90 | =cut 91 | 92 | sub available 93 | { 94 | my $str = "use Text::Textile;"; 95 | 96 | ## no critic (Eval) 97 | eval($str); 98 | ## use critic 99 | 100 | return ( $@ ? undef : 1 ); 101 | } 102 | 103 | 104 | =head2 format 105 | 106 | Format the given text. 107 | 108 | =cut 109 | 110 | sub format 111 | { 112 | my ( $self, $str, $data ) = (@_); 113 | 114 | if ( $self->available() ) 115 | { 116 | Text::Textile::textile($str); 117 | } 118 | else 119 | { 120 | warn 121 | "Textile formatting disabled as the Text::Textile module isn't present.\n"; 122 | 123 | $str; 124 | } 125 | } 126 | 127 | Templer::Plugin::Factory->new() 128 | ->register_formatter( "textile", "Templer::Plugin::Textile" ); 129 | -------------------------------------------------------------------------------- /lib/Templer/Plugin/TimeStamp.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Plugin::TimeStamp - A plugin to get TimeStamp of source files 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | The following is a good example use of this plugin 11 | 12 | title: About my site 13 | mtime: timestamp(%Y-%m-%d %H:M:%S) 14 | ---- 15 |

    This file has been last modified on . 16 | 17 | =cut 18 | 19 | =head1 DESCRIPTION 20 | 21 | This plugin allows template variables to be set to the source file 22 | modification time. The parameter passed to the function are passed as is to 23 | strftime with leading and trailing spaces removed. 24 | 25 | =cut 26 | 27 | =head1 LICENSE 28 | 29 | This module is free software; you can redistribute it and/or modify it 30 | under the terms of either: 31 | 32 | a) the GNU General Public License as published by the Free Software 33 | Foundation; either version 2, or (at your option) any later version, 34 | or 35 | 36 | b) the Perl "Artistic License". 37 | 38 | =cut 39 | 40 | =head1 AUTHOR 41 | 42 | Bruno BEAUFILS 43 | 44 | =cut 45 | 46 | =head1 COPYRIGHT AND LICENSE 47 | 48 | Copyright (C) 2015 Bruno BEAUFILS . 49 | 50 | This library is free software. You can modify and or distribute it under 51 | the same terms as Perl itself. 52 | 53 | =cut 54 | 55 | =head1 METHODS 56 | 57 | =cut 58 | 59 | 60 | use strict; 61 | use warnings; 62 | 63 | use POSIX qw{strftime}; 64 | 65 | 66 | package Templer::Plugin::TimeStamp; 67 | 68 | 69 | =head2 70 | 71 | Constructor. No arguments are required/supported. 72 | 73 | =cut 74 | 75 | sub new 76 | { 77 | my ( $proto, %supplied ) = (@_); 78 | my $class = ref($proto) || $proto; 79 | 80 | my $self = {}; 81 | bless( $self, $class ); 82 | return $self; 83 | } 84 | 85 | 86 | =head2 expand_variables 87 | 88 | This is the method which is called by the L 89 | to expand the variables contained in a L object. 90 | 91 | Variables are written in the file in the form "key: value", and are 92 | internally stored within the Page object as a hash. 93 | 94 | This method iterates over each key & value and updates any that 95 | seem to refer to timestamps. 96 | 97 | =cut 98 | 99 | sub expand_variables 100 | { 101 | my ( $self, $site, $page, $data ) = (@_); 102 | 103 | # 104 | # Get the page-variables in the template. 105 | # 106 | my %hash = %$data; 107 | 108 | # 109 | # The mtime of the page-source. Cached. 110 | # 111 | my $mtime; 112 | 113 | # 114 | # Look for a value of "timestamp" in each key. 115 | # 116 | foreach my $key ( keys %hash ) 117 | { 118 | if ( $hash{ $key } =~ /^timestamp\((.*)\)/ ) 119 | { 120 | 121 | # 122 | # The format to use. 123 | # 124 | my $ts = $1; 125 | 126 | # 127 | # Strip leading/trailing whitespace. 128 | # 129 | $ts =~ s/^\s+|\s+$//g; 130 | 131 | # 132 | # Get mtime of the source file - if we've not already done so. 133 | # 134 | if ( !$mtime ) 135 | { 136 | $mtime = ( stat( $page->source() ) )[9]; 137 | } 138 | 139 | # 140 | # Store formatted modification time 141 | # 142 | my @mtime = localtime($mtime); 143 | $hash{ $key } = POSIX::strftime $ts, @mtime; 144 | } 145 | } 146 | 147 | # 148 | # Return. 149 | # 150 | return ( \%hash ); 151 | } 152 | 153 | 154 | # 155 | # Register the plugin. 156 | # 157 | Templer::Plugin::Factory->new()->register_plugin("Templer::Plugin::TimeStamp"); 158 | -------------------------------------------------------------------------------- /lib/Templer/Site/Asset.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Site::Asset - An interface to a site asset. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Templer::Site::Asset; 14 | 15 | my $page = Templer::Site::Asset->new( file => "./input/robots.txt" ); 16 | 17 | =cut 18 | 19 | =head1 DESCRIPTION 20 | 21 | An asset is anything beneath the input directory which is *not* a page. 22 | 23 | Assuming we're not running in "in-place" mode then assets are copied 24 | over to a suitable filename in the output tree. 25 | 26 | In C the page objects are created by the L module. 27 | 28 | =cut 29 | 30 | =head1 LICENSE 31 | 32 | This module is free software; you can redistribute it and/or modify it 33 | under the terms of either: 34 | 35 | a) the GNU General Public License as published by the Free Software 36 | Foundation; either version 2, or (at your option) any later version, 37 | or 38 | 39 | b) the Perl "Artistic License". 40 | 41 | =cut 42 | 43 | =head1 AUTHOR 44 | 45 | Steve Kemp 46 | 47 | =cut 48 | 49 | =head1 COPYRIGHT AND LICENSE 50 | 51 | Copyright (C) 2012-2015 Steve Kemp . 52 | 53 | This library is free software. You can modify and or distribute it under 54 | the same terms as Perl itself. 55 | 56 | =cut 57 | 58 | =head1 METHODS 59 | 60 | =cut 61 | 62 | 63 | 64 | use strict; 65 | use warnings; 66 | 67 | 68 | package Templer::Site::Asset; 69 | 70 | 71 | 72 | =head2 new 73 | 74 | The constructor. 75 | 76 | The single appropriate argument is the hash-key "file", pointing to the 77 | file on-disk. 78 | 79 | =cut 80 | 81 | sub new 82 | { 83 | my ( $proto, %supplied ) = (@_); 84 | my $class = ref($proto) || $proto; 85 | 86 | my $self = {}; 87 | 88 | # 89 | # Allow user supplied values to override our defaults 90 | # 91 | foreach my $key ( keys %supplied ) 92 | { 93 | $self->{ lc $key } = $supplied{ $key }; 94 | } 95 | 96 | bless( $self, $class ); 97 | return $self; 98 | } 99 | 100 | 101 | =head2 source 102 | 103 | Return the filename we were built from. This is the value passed 104 | in the constructor. 105 | 106 | =cut 107 | 108 | sub source 109 | { 110 | my ($self) = (@_); 111 | $self->{ "file" }; 112 | } 113 | 114 | 115 | 116 | 1; 117 | -------------------------------------------------------------------------------- /lib/Templer/Site/New.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Site::New - Create a new templer site 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Templer::Site::New; 14 | 15 | my $site = Templer::Site::New->new(); 16 | $site->create( "/tmp/foo" ); 17 | 18 | =cut 19 | 20 | =head1 DESCRIPTION 21 | 22 | This class allows a new C site to be created on-disk. This 23 | involves creating a new input tree, stub configuration file, etc. 24 | 25 | The content of the new site, and the directory names, are taken from 26 | the DATA section of this class. 27 | 28 | =cut 29 | 30 | =head1 LICENSE 31 | 32 | This module is free software; you can redistribute it and/or modify it 33 | under the terms of either: 34 | 35 | a) the GNU General Public License as published by the Free Software 36 | Foundation; either version 2, or (at your option) any later version, 37 | or 38 | 39 | b) the Perl "Artistic License". 40 | 41 | =cut 42 | 43 | =head1 AUTHOR 44 | 45 | Steve Kemp 46 | 47 | =cut 48 | 49 | =head1 COPYRIGHT AND LICENSE 50 | 51 | Copyright (C) 2012-2015 Steve Kemp . 52 | 53 | This library is free software. You can modify and or distribute it under 54 | the same terms as Perl itself. 55 | 56 | =cut 57 | 58 | =head1 METHODS 59 | 60 | =cut 61 | 62 | 63 | 64 | use strict; 65 | use warnings; 66 | 67 | 68 | package Templer::Site::New; 69 | 70 | use File::Path qw(mkpath); 71 | 72 | 73 | =head2 new 74 | 75 | The constructor. No arguments are required/recognized. 76 | 77 | =cut 78 | 79 | sub new 80 | { 81 | my $class = shift; 82 | bless {}, $class; 83 | } 84 | 85 | 86 | =head2 create 87 | 88 | Create a new site in the given directory. 89 | 90 | This method parses and processes the DATA section of this very module, 91 | to know which files/directories to create. 92 | 93 | =cut 94 | 95 | sub create 96 | { 97 | my ( $self, $base, $force ) = (@_); 98 | 99 | # 100 | # Forced defaults to false, if not specified. 101 | # 102 | $force = 0 if ( !defined($force) ); 103 | 104 | 105 | # 106 | # Files we created 107 | # 108 | my $created = 0; 109 | 110 | my $name = undef; 111 | my $marker = undef; 112 | my $tmp = undef; 113 | 114 | # 115 | # Process our data-section. 116 | # 117 | while ( my $line = ) 118 | { 119 | chomp($line); 120 | 121 | # 122 | # Making a directory? 123 | # 124 | if ( $line =~ /^mkdir(.*)/ ) 125 | { 126 | my $dir = $1; 127 | $dir =~ s/^\s+|\s+$//g; 128 | $dir = $base . "/" . $dir; 129 | 130 | if ( !-d $dir ) 131 | { 132 | File::Path::mkpath( $dir, { verbose => 0 } ); 133 | } 134 | 135 | } 136 | elsif ( !$name && 137 | !$marker && 138 | ( $line =~ /file\s+([^\s]+)\s+([^\s]+)/ ) ) 139 | { 140 | 141 | # 142 | # Writing to a file? 143 | # 144 | $name = $1; 145 | $marker = $2; 146 | $tmp = undef; 147 | 148 | } 149 | else 150 | { 151 | 152 | # 153 | # If we have a filename to write to, then append to the temporary 154 | # contents - unless we've found the EOF marker. 155 | # 156 | if ( $name && $marker ) 157 | { 158 | if ( $line eq $marker ) 159 | { 160 | my $create = 1; 161 | if ( -e $base . "/" . $name ) 162 | { 163 | $create = 0 unless ($force); 164 | } 165 | 166 | if ($create) 167 | { 168 | my $dst = $base . "/" . $name; 169 | 170 | open my $handle, ">:utf8", $dst or 171 | die "Failed to write to '$dst' - $!"; 172 | print $handle $tmp; 173 | close($handle); 174 | 175 | $created += 1; 176 | } 177 | else 178 | { 179 | print "WARNING: Refusing to over-write $base/$name\n"; 180 | } 181 | 182 | $name = undef; 183 | $marker = undef; 184 | $tmp = undef; 185 | } 186 | else 187 | { 188 | $tmp .= $line . "\n"; 189 | } 190 | } 191 | } 192 | 193 | } 194 | $created; 195 | } 196 | 197 | 198 | 1; 199 | 200 | 201 | __DATA__ 202 | mkdir input 203 | 204 | mkdir output 205 | 206 | mkdir plugins 207 | 208 | mkdir layouts 209 | 210 | mkdir includes 211 | 212 | file input/robots.txt EOF 213 | User-agent: * 214 | Crawl-delay: 10 215 | Disallow: /cgi-bin 216 | Disallow: /stats 217 | EOF 218 | 219 | file input/index.wgn EOF 220 | title: Welcome! 221 | ---- 222 |

    Welcome to my site.

    223 | EOF 224 | 225 | file input/about.wgn EOF 226 | title: About my site 227 | ---- 228 |

    This is my site, it was generated by templer.

    229 | EOF 230 | 231 | file layouts/default.layout EOF 232 | 234 | 235 | 236 | 237 | <!-- tmpl_var name='title' escape='html' --> 238 | 239 | Untitled Page 240 | 241 | 242 | 243 | 244 |

    This is site was generated by templer on .

    245 | 246 | 247 | EOF 248 | 249 | file templer.cfg EOF 250 | ## 251 | # 252 | # The first section of the configuration file refers to the 253 | # input and output paths. 254 | # 255 | # Templer will process all files matching "*.skx" beneath a 256 | # particular directory. That directory is the input directory. 257 | # 258 | input = ./input/ 259 | # 260 | ## 261 | 262 | 263 | 264 | ## 265 | # 266 | # Within the input directory we'll process files that match 267 | # a given suffix. 268 | # 269 | # By default this is ".skx", so we'll template-expand files 270 | # named "index.skx", "about.skx", etc. 271 | # 272 | suffix = .wgn 273 | # 274 | ## 275 | 276 | 277 | ## 278 | # 279 | # By default all pages will be written in HTML. 280 | # 281 | # If you have the appropriate depedencies installed you can instead 282 | # write your input pages in textile/markdown. Just add to the page 283 | # 284 | # Title: my title 285 | # Format: textile 286 | # ---- 287 | # ... your content here .. 288 | # 289 | # If all pages are going to be setup in one format you may prefer 290 | # to change this default 291 | # 292 | format = html 293 | # format = markdown 294 | # format = perl 295 | # format = textile 296 | # 297 | ## 298 | # 299 | 300 | 301 | 302 | ## 303 | # 304 | # If we're working in-place then files will be expanded where 305 | # they are found. 306 | # 307 | # This means that the following files will be created: 308 | # 309 | # ./input/index.skx -> input/index.html 310 | # ./input/foo/index.skx -> input/foo/index.html 311 | # .. 312 | # 313 | # 314 | # in-place = 1 315 | # 316 | ## 317 | 318 | 319 | 320 | ## 321 | # 322 | # The more common way of working is to produce the output in a separate 323 | # directory. 324 | # 325 | # NOTE: If you specify both "in-place=1" and an output directory the former 326 | # will take precedence. 327 | # 328 | # 329 | output = ./output/ 330 | # 331 | ## 332 | 333 | 334 | 335 | ## 336 | # 337 | # When pages are processed a layout-template will be used to expand the content 338 | # into. 339 | # 340 | # Each page may specify its own layout if it so wishes, but generally we'd 341 | # expect only one layout to exist. 342 | # 343 | # Here we specify both the path to the layout directory and the layout to use 344 | # if none is specified: 345 | # 346 | # 347 | layout-path = ./layouts/ 348 | layout = default.layout 349 | # 350 | ## 351 | 352 | 353 | ## 354 | # 355 | # When pages are processed a layout-template will be used to expand the content 356 | # into. 357 | # 358 | # Each page may specify its own layout if it so wishes, but generally we'd 359 | # expect only one layout to exist. 360 | # 361 | # Here we specify both the path to the layout directory and the layout to use 362 | # if none is specified: 363 | # 364 | # 365 | # layout-path = ./layouts/ 366 | # 367 | # layout = default.layout 368 | # 369 | ## 370 | 371 | 372 | 373 | 374 | ## 375 | # 376 | # Templer supports plugins for expanding variable definitions 377 | # inside the input files, or for formating with text systems 378 | # like Textile, Markdown, etc. 379 | # 380 | # There are several plugins included with the system and you 381 | # can write your own in perl. Specify the path to load plugins 382 | # from here. 383 | # 384 | plugin-path = ./plugins/ 385 | # 386 | ## 387 | 388 | 389 | ## 390 | # 391 | # Templer supports including files via the 'read_file' function, along 392 | # with the built-in support that HTML::Template has for file inclusion 393 | # via: 394 | # 395 | # 396 | # 397 | # In both cases you may specify a search-path for file inclusion 398 | # via the include-path setting: 399 | # 400 | # include-path = include/:include/local/ 401 | # 402 | # Given the choice you should prefer the templer-provided file-inclusion 403 | # method over the HTML::Template facility, because this will force pages to 404 | # be rebuilt when the included-files are changed. 405 | # 406 | # Using a HTML::Template include-file you'll need to explicitly force a 407 | # rebuild if you modify an included file, but not the parent. 408 | # 409 | # 410 | include-path = ./includes 411 | # 412 | ## 413 | 414 | 415 | 416 | 417 | # 418 | # Anything below this is a global variable, accessible by name in your 419 | # templates. 420 | # 421 | # For example this: 422 | # 423 | # copyright = © Steve Kemp 2012 424 | # 425 | # Can be used in your template, or you page text via: 426 | # 427 | # 428 | # 429 | # Similarly you might wish to include a last-modified date in the layout 430 | # and this could be achieved by wruting this: 431 | # 432 | #

    Page last rebuilt at

    433 | # 434 | # Providing you uncomment this line: 435 | # 436 | date = run_command( date '+%A %e %B %Y' ) 437 | # 438 | # 439 | # 440 | ## 441 | EOF 442 | 443 | -------------------------------------------------------------------------------- /lib/Templer/Site/Page.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Site::Page - An interface to a site page. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Templer::Site::Page; 14 | 15 | my $page = Templer::Site::Page->new( file => "./input/foo.wgn" ); 16 | 17 | =cut 18 | 19 | =head1 DESCRIPTION 20 | 21 | A page is any non-directory beneath the input-directory which matches the 22 | pattern specified by the user (defaults to "*.skx"). 23 | 24 | Pages are processed via the L module to create the suitable 25 | output. 26 | 27 | In C the page objects are created by the L module. 28 | 29 | =cut 30 | 31 | =head1 LICENSE 32 | 33 | This module is free software; you can redistribute it and/or modify it 34 | under the terms of either: 35 | 36 | a) the GNU General Public License as published by the Free Software 37 | Foundation; either version 2, or (at your option) any later version, 38 | or 39 | 40 | b) the Perl "Artistic License". 41 | 42 | =cut 43 | 44 | =head1 AUTHOR 45 | 46 | Steve Kemp 47 | 48 | =cut 49 | 50 | =head1 COPYRIGHT AND LICENSE 51 | 52 | Copyright (C) 2012-2015 Steve Kemp . 53 | 54 | This library is free software. You can modify and or distribute it under 55 | the same terms as Perl itself. 56 | 57 | =cut 58 | 59 | =head1 METHODS 60 | 61 | =cut 62 | 63 | 64 | use strict; 65 | use warnings; 66 | 67 | 68 | package Templer::Site::Page; 69 | 70 | 71 | 72 | =head2 new 73 | 74 | The constructor. 75 | 76 | The single appropriate argument is the hash-key "file", pointing to the 77 | page-file on-disk. 78 | 79 | =cut 80 | 81 | sub new 82 | { 83 | my ( $proto, %supplied ) = (@_); 84 | my $class = ref($proto) || $proto; 85 | 86 | my $self = {}; 87 | 88 | # 89 | # Allow user supplied values to override our defaults 90 | # 91 | foreach my $key ( keys %supplied ) 92 | { 93 | $self->{ lc $key } = $supplied{ $key }; 94 | } 95 | 96 | bless( $self, $class ); 97 | $self->_parse_page( $self->{ 'file' } ) if ( $self->{ 'file' } ); 98 | return $self; 99 | } 100 | 101 | 102 | =head2 _parse_page 103 | 104 | Read the file, and parse the header/content. 105 | 106 | This is an internal method. 107 | 108 | =cut 109 | 110 | sub _parse_page 111 | { 112 | my ( $self, $filename ) = (@_); 113 | 114 | open( my $handle, "<:utf8", $filename ) or 115 | die "Failed to read '$filename' - $!"; 116 | binmode( $handle, ":utf8" ); 117 | 118 | my $header = 1; 119 | 120 | while ( my $line = <$handle> ) 121 | { 122 | 123 | # strip trailing newline. 124 | $line =~ s/[\r\n]*//g; 125 | 126 | if ($header) 127 | { 128 | if ( $line =~ /^([^:]+):(.*)$/ ) 129 | { 130 | my $key = $1; 131 | my $val = $2; 132 | $key = lc($key); 133 | $key =~ s/^\s+|\s+$//g; 134 | $val =~ s/^\s+|\s+$//g; 135 | 136 | $self->{ $key } = $val; 137 | print "Templer::Site::Page set: $key => $val\n" 138 | if ( $self->{ 'debug' } ); 139 | } 140 | if ( $line =~ /^----[\r\n]*$/ ) 141 | { 142 | $header = undef; 143 | } 144 | } 145 | else 146 | { 147 | $self->{ 'content' } .= $line . "\n"; 148 | } 149 | } 150 | 151 | # 152 | # If we're still in the header at the end of the file 153 | # then something has gone wrong. 154 | # 155 | if ($header) 156 | { 157 | print "WARNING: No header found in $filename\n"; 158 | } 159 | 160 | close($handle); 161 | } 162 | 163 | 164 | 165 | 166 | =head2 content 167 | 168 | Return the body of the page. 169 | 170 | Here we perform the textile/markdown expansion if possible via the use 171 | plugins loaded by L. 172 | 173 | =cut 174 | 175 | sub content 176 | { 177 | my ( $self, $data ) = (@_); 178 | 179 | # 180 | # The content we read from the page. 181 | # 182 | my $content = $self->{ 'content' }; 183 | my $format = $self->{ 'format' } || $data->{ 'format' } || undef; 184 | 185 | # 186 | # Do we have a formatter plugin for this type? 187 | # 188 | # Many formatters might be specified 189 | # 190 | if ($format) 191 | { 192 | 193 | # 194 | # The plugin-factory. 195 | # 196 | my $factory = Templer::Plugin::Factory->new(); 197 | 198 | # 199 | # For each formatter. 200 | # 201 | foreach my $fmt ( split( /,/, $format ) ) 202 | { 203 | $fmt =~ s/^\s+|\s+$//g; 204 | next unless ($fmt); 205 | 206 | my $helper = $factory->formatter($fmt); 207 | $content = $helper->format( $content, $data ) if ($helper); 208 | } 209 | } 210 | return $content; 211 | } 212 | 213 | 214 | =head2 field 215 | 216 | Retrieve a field from the header of the page. 217 | 218 | In the following example file "foo", "bar" and "title" are fields: 219 | 220 | Foo: Testing .. 221 | Bar: file_glob( "*.gif" ) 222 | Title: This is my page title. 223 | ----- 224 |

    This is my page content ..

    225 | 226 | =cut 227 | 228 | sub field 229 | { 230 | my ( $self, $field ) = (@_); 231 | return ( $self->{ $field } ); 232 | } 233 | 234 | 235 | 236 | =head2 fields 237 | 238 | Return all known fields/values from the page. 239 | 240 | =cut 241 | 242 | sub fields 243 | { 244 | my ($self) = (@_); 245 | 246 | return (%$self); 247 | } 248 | 249 | 250 | =head2 source 251 | 252 | Return the filename we were built from. This is the value passed 253 | in the constructor. 254 | 255 | =cut 256 | 257 | sub source 258 | { 259 | my ($self) = (@_); 260 | $self->field("file"); 261 | } 262 | 263 | 264 | 265 | =head2 layout 266 | 267 | Return the layout-template to use for this page, if one has been set. 268 | 269 | =cut 270 | 271 | sub layout 272 | { 273 | my ($self) = (@_); 274 | $self->field("layout"); 275 | } 276 | 277 | 278 | =head2 dependencies 279 | 280 | Return the dependencies of the current page. 281 | 282 | =cut 283 | 284 | sub dependencies 285 | { 286 | my ($self) = (@_); 287 | 288 | $self->{ 'dependencies' } ? @{ $self->{ 'dependencies' } } : (); 289 | } 290 | 291 | 292 | =head2 add_dependency 293 | 294 | Add a dependency to the current page. This is used so that the file-inclusion 295 | plugin can add such a thing. 296 | 297 | =cut 298 | 299 | sub add_dependency 300 | { 301 | my ( $self, $file ) = (@_); 302 | push( @{ $self->{ 'dependencies' } }, $file ); 303 | } 304 | 305 | 306 | 1; 307 | -------------------------------------------------------------------------------- /lib/Templer/Timer.pm: -------------------------------------------------------------------------------- 1 | 2 | =head1 NAME 3 | 4 | Templer::Timer - A utility class for recording build-time. 5 | 6 | =cut 7 | 8 | =head1 SYNOPSIS 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Templer::Timer; 14 | 15 | my $obj = Templer::Timer->new(); 16 | sleep( 3 ); 17 | 18 | print $obj->elapsed(); 19 | 20 | =cut 21 | 22 | =head1 DESCRIPTION 23 | 24 | This class is a simple utility for reporting the time elapsed since 25 | the object was created. 26 | 27 | It is used to report on the build-time of a templer site. 28 | 29 | =cut 30 | 31 | =head1 LICENSE 32 | 33 | This module is free software; you can redistribute it and/or modify it 34 | under the terms of either: 35 | 36 | a) the GNU General Public License as published by the Free Software 37 | Foundation; either version 2, or (at your option) any later version, 38 | or 39 | 40 | b) the Perl "Artistic License". 41 | 42 | =cut 43 | 44 | =head1 AUTHOR 45 | 46 | Steve Kemp 47 | 48 | =cut 49 | 50 | =head1 COPYRIGHT AND LICENSE 51 | 52 | Copyright (C) 2015 Steve Kemp . 53 | 54 | This library is free software. You can modify and or distribute it under 55 | the same terms as Perl itself. 56 | 57 | =cut 58 | 59 | 60 | use strict; 61 | use warnings; 62 | 63 | 64 | package Templer::Timer; 65 | 66 | 67 | 68 | =begin doc 69 | 70 | Constructor. 71 | 72 | Record the creation time of this object. 73 | 74 | =end doc 75 | 76 | =cut 77 | 78 | sub new 79 | { 80 | my ( $proto, %supplied ) = (@_); 81 | my $class = ref($proto) || $proto; 82 | 83 | my $self = {}; 84 | $self->{ 'start' } = time(); 85 | 86 | bless( $self, $class ); 87 | return $self; 88 | } 89 | 90 | 91 | 92 | =begin doc 93 | 94 | Return the time elapsed since this object was created. 95 | 96 | =end doc 97 | 98 | =cut 99 | 100 | sub elapsed 101 | { 102 | my ($self) = (@_); 103 | 104 | my $now = time; 105 | my $elapsed = $now - $self->{ 'start' }; 106 | 107 | if ( $elapsed < 1 ) 108 | { 109 | $elapsed = "less than 1 second"; 110 | } 111 | elsif ( $elapsed == 1 ) 112 | { 113 | $elapsed = "1 second"; 114 | } 115 | else 116 | { 117 | $elapsed = $elapsed . " seconds"; 118 | } 119 | $elapsed; 120 | } 121 | 122 | 1; 123 | -------------------------------------------------------------------------------- /t/style-no-tabs.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # 3 | # Test that none of our scripts contain any literal TAB characters. 4 | # 5 | # Steve 6 | # -- 7 | 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More; 13 | 14 | ## no critic (Eval) 15 | eval "use Test::NoTabs;"; 16 | ## use critic 17 | 18 | plan skip_all => "Test::NoTabs required for testing." if $@; 19 | 20 | all_perl_files_ok( qw/lib bin/); 21 | -------------------------------------------------------------------------------- /t/style-no-trailing-whitespace.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # 3 | # Ensure that none of our scripts contain trailing whitespace. 4 | # 5 | # Steve 6 | # -- 7 | 8 | 9 | use strict; 10 | use File::Find; 11 | use Test::More qw( no_plan ); 12 | 13 | 14 | # 15 | # Find all the files beneath the current directory, 16 | # and call 'checkFile' with the name. 17 | # 18 | find( { wanted => \&checkFile, no_chdir => 1 }, '.' ); 19 | 20 | 21 | 22 | # 23 | # Check a file. 24 | # 25 | # 26 | sub checkFile 27 | { 28 | 29 | # The file. 30 | my $file = $File::Find::name; 31 | 32 | # We don't care about directories 33 | return if ( !-f $file ); 34 | 35 | # Nor about backup files. 36 | return if ( $file =~ /~$/ ); 37 | 38 | # or Makefiles 39 | return if ( $file =~ /Makefile/ ); 40 | 41 | # Nor about files which start with ./debian/ 42 | return if ( $file =~ /^\.\/debian\// ); 43 | 44 | # See if it is a shell/perl file. 45 | my $isShell = 0; 46 | my $isPerl = 0; 47 | 48 | # Read the file. 49 | open( my $handle, "<", $file ) or 50 | die "Failed to read $file - $!"; 51 | foreach my $line (<$handle>) 52 | { 53 | if ( ( $line =~ /\/bin\/sh/ ) || 54 | ( $line =~ /\/bin\/bash/ ) ) 55 | { 56 | $isShell = 1; 57 | } 58 | if ( $line =~ /\/usr\/bin\/perl/ ) 59 | { 60 | $isPerl = 1; 61 | } 62 | } 63 | close($handle); 64 | 65 | # 66 | # We don't care about files which are neither perl nor shell. 67 | # 68 | if ( $isShell || $isPerl ) 69 | { 70 | 71 | # 72 | # Count trailing whitespace.. 73 | # 74 | my $count = countTrailing($file); 75 | 76 | is( $count, 0, "Script has no trailing whitespace characters: $file" ); 77 | } 78 | } 79 | 80 | 81 | 82 | # 83 | # Count and return the number of lines with trailng whitespace present. 84 | # 85 | sub countTrailing 86 | { 87 | my ($file) = (@_); 88 | my $count = 0; 89 | 90 | open( my $handle, "<", $file ) or 91 | die "Cannot open $file - $!"; 92 | foreach my $line (<$handle>) 93 | { 94 | 95 | # If we found a line with any then increase the count by one 96 | # rather than the number of characters found. 97 | if ( $line =~ /^(.*)([\t ]+)$/ ) 98 | { 99 | $count += 1; 100 | } 101 | } 102 | close($handle); 103 | 104 | return ($count); 105 | } 106 | -------------------------------------------------------------------------------- /t/test-dependencies.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w -I.. 2 | # 3 | # Test that all the Perl modules we require are available. 4 | # 5 | # This list is automatically generated by modules.sh 6 | # 7 | # Steve 8 | # -- 9 | # 10 | 11 | use strict; 12 | use warnings; 13 | 14 | 15 | use Test::More qw( no_plan ); 16 | 17 | 18 | 19 | BEGIN {use_ok('Cwd');} 20 | require_ok('Cwd'); 21 | 22 | BEGIN {use_ok('File::Find');} 23 | require_ok('File::Find'); 24 | 25 | BEGIN {use_ok('File::Path');} 26 | require_ok('File::Path'); 27 | 28 | BEGIN {use_ok('Getopt::Long');} 29 | require_ok('Getopt::Long'); 30 | 31 | BEGIN {use_ok('HTML::Template');} 32 | require_ok('HTML::Template'); 33 | 34 | BEGIN {use_ok('Pod::Find');} 35 | require_ok('Pod::Find'); 36 | 37 | BEGIN {use_ok('Pod::Usage');} 38 | require_ok('Pod::Usage'); 39 | 40 | BEGIN {use_ok('Test::More');} 41 | require_ok('Test::More'); 42 | 43 | BEGIN {use_ok('Test::Exception');} 44 | require_ok('Test::Exception'); 45 | 46 | BEGIN {use_ok('strict');} 47 | require_ok('strict'); 48 | 49 | BEGIN {use_ok('warnings');} 50 | require_ok('warnings'); 51 | -------------------------------------------------------------------------------- /t/test-pod-syntax.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | 3 | # 4 | # Test that the POD we use in our modules is valid. 5 | # 6 | 7 | 8 | use strict; 9 | use Test::More; 10 | ## no critic (Eval) 11 | eval "use Test::Pod 1.00"; 12 | ## use critic 13 | plan skip_all => "Test::Pod 1.00 required for testing POD" if $@; 14 | 15 | # 16 | # Run the test(s). 17 | # 18 | my @poddirs; 19 | if ( -d "t/" ) 20 | { 21 | @poddirs = qw( . ); 22 | } 23 | elsif ( -d "../t" ) 24 | { 25 | @poddirs = qw( ../ ); 26 | } 27 | 28 | 29 | all_pod_files_ok( all_pod_files(@poddirs) ); 30 | -------------------------------------------------------------------------------- /t/test-strict.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Test::More; 7 | 8 | BEGIN 9 | { 10 | my $str = "use Test::Strict;"; 11 | 12 | ## no critic (Eval) 13 | eval($str); 14 | ## use critic 15 | 16 | plan skip_all => "Skipping as Test::Strict isn't installed" 17 | if ($@); 18 | } 19 | 20 | all_perl_files_ok(); 21 | -------------------------------------------------------------------------------- /t/test-templer-asset-copying.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test that the Templer::Site::copyAssets method will do the right 4 | # thing. 5 | # 6 | # Steve 7 | # 8 | 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Test::More qw! no_plan !; 14 | use File::Temp qw! tempdir !; 15 | use File::Path qw! mkpath !; 16 | 17 | 18 | BEGIN {use_ok('Templer::Site');} 19 | require_ok('Templer::Site'); 20 | BEGIN {use_ok('Templer::Site::Asset');} 21 | require_ok('Templer::Site::Asset'); 22 | 23 | 24 | 25 | # 26 | # Make a temporary directory 27 | # 28 | my $tmp = tempdir( CLEANUP => 1 ); 29 | ok( -d $tmp, "We created a temporary directory" ); 30 | note("Temporary directory is: $tmp"); 31 | 32 | # 33 | # Make the input-subdirectory 34 | # 35 | File::Path::mkpath( $tmp . "/input", { verbose => 0, mode => oct(755) } ); 36 | ok( -d $tmp, "We created an input/ directory" ); 37 | 38 | # 39 | # Create some assets. 40 | # 41 | createFile( $tmp . "/input/index.skx" ); 42 | createFile( $tmp . "/input/logo.png" ); 43 | createFile( $tmp . "/input/.htaccess" ); 44 | createFile( $tmp . "/input/it's ugly.png" ); 45 | createFile( $tmp . '/input/it' . "'" . 's ugly\.png' ); 46 | createFile( $tmp . '/input/it' . "'" . 's ugly".png' ); 47 | createFile( $tmp . '/input/it' . "'" . 's ugly$.png' ); 48 | createFile( $tmp . '/input/it' . "'" . 's ugly`.png' ); 49 | createFile( $tmp . '/input/it"s ugly.png' ); 50 | 51 | # 52 | # Now create the Templer::Site object. 53 | # 54 | my $site = Templer::Site->new( input => "$tmp/input/", 55 | output => "$tmp/output", 56 | suffix => ".skx", 57 | ); 58 | 59 | 60 | # 61 | # Setup the output directory, etc. 62 | # 63 | $site->init(); 64 | ok( -d "$tmp/output", "The output directory was created" ); 65 | 66 | # 67 | # Copy the assets from input/ to output/ 68 | # 69 | $site->copyAssets(); 70 | 71 | 72 | ok( -e $tmp . "/output/logo.png", "Asset copied successfully" ); 73 | ok( -e $tmp . "/output/.htaccess", "Asset copied successfully" ); 74 | ok( -e $tmp . "/output/it's ugly.png", "Asset copied successfully" ); 75 | ok( -e $tmp . '/output/it' . "'" . 's ugly\.png', "Asset copied successfully" ); 76 | ok( -e $tmp . '/output/it' . "'" . 's ugly".png', "Asset copied successfully" ); 77 | ok( -e $tmp . '/output/it' . "'" . 's ugly$.png', "Asset copied successfully" ); 78 | ok( -e $tmp . '/output/it' . "'" . 's ugly`.png', "Asset copied successfully" ); 79 | ok( -e $tmp . '/output/it"s ugly.png', "Asset copied successfully" ); 80 | ok( !-e $tmp . "/output/index.skx", "Page source not copied" ); 81 | 82 | 83 | sub createFile 84 | { 85 | my ($file) = (@_); 86 | 87 | ok( !-e $file, "The file didn't exist prior to creation" ); 88 | 89 | note("File to be created is : $file"); 90 | 91 | open( my $handle, ">", $file ) or 92 | die "Failed to write: $file - $!"; 93 | print $handle "\n"; 94 | close($handle); 95 | 96 | ok( -e $file, "The file was created" ); 97 | } 98 | -------------------------------------------------------------------------------- /t/test-templer-page-expansion.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test we only expand the variables in a page once. 4 | # 5 | # Steve 6 | # 7 | 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More qw! no_plan !; 13 | 14 | 15 | # 16 | # Simple class is a plugin that appends "BOO" to the value of any key. 17 | # 18 | package Simple::Class; 19 | 20 | sub new 21 | { 22 | my $class = shift; 23 | bless {}, $class; 24 | } 25 | 26 | sub expand_variables 27 | { 28 | my ( $self, $site, $page, $data ) = (@_); 29 | 30 | my %hash = %$data; 31 | foreach my $key ( keys %hash ) 32 | { 33 | $hash{ $key } = $hash{ $key } . "BOO"; 34 | } 35 | return ( \%hash ); 36 | } 37 | Templer::Plugin::Factory->new()->register_plugin("Simple::Class"); 38 | 39 | 40 | 41 | 42 | package main; 43 | 44 | BEGIN {use_ok('Templer::Site');} 45 | require_ok('Templer::Site'); 46 | BEGIN {use_ok('Templer::Plugin::Factory');} 47 | require_ok('Templer::Plugin::Factory'); 48 | BEGIN {use_ok('Templer::Site::Page');} 49 | require_ok('Templer::Site::Page'); 50 | 51 | 52 | # 53 | # The config object. 54 | # 55 | my $site = Templer::Site->new(); 56 | 57 | 58 | # 59 | # Instantiate the helper. 60 | # 61 | my $factory = Templer::Plugin::Factory->new(); 62 | ok( $factory, "Loaded the factory object." ); 63 | isa_ok( $factory, "Templer::Plugin::Factory" ); 64 | 65 | # 66 | # Create a page 67 | # 68 | my $page = Templer::Site::Page->new( 69 | "foo" => "bar", 70 | "layout" => "default.layout", 71 | "content" => "

    This is my page content.

    ", 72 | "title" => "This is the page title." 73 | ); 74 | 75 | ok( $page, "Page found" ); 76 | isa_ok( $page, "Templer::Site::Page", "And has the correct type" ); 77 | is( $page->layout(), "default.layout", "The page has the correct layout" ); 78 | is( $page->content(), 79 | "

    This is my page content.

    ", 80 | "The page has the correct content" ); 81 | ok( !$page->dependencies(), "The stub page has no dependencies" ); 82 | 83 | # 84 | # Get the title, and ensure it is OK. 85 | # 86 | is( $page->field("title"), 87 | "This is the page title.", 88 | "The page title matches what we expect" ); 89 | 90 | # 91 | # Get the fields - which will be expanded by our plugin. 92 | # 93 | my %original = $page->fields(); 94 | 95 | my $plugin = Templer::Plugin::Factory->new(); 96 | my $ref = $plugin->expand_variables( $site, $page, \%original ); 97 | my %updated = %$ref; 98 | 99 | is( $updated{ 'title' }, 100 | "This is the page title.BOO", 101 | "The field was expanded" 102 | ); 103 | is( $updated{ 'foo' }, "barBOO", "The field was expanded" ); 104 | 105 | -------------------------------------------------------------------------------- /t/test-templer-plugin-factory-formatters.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test we can instantiate the known formatter-plugins, and that 4 | # they work. 5 | # 6 | # Steve 7 | # -- 8 | # 9 | 10 | 11 | use strict; 12 | use warnings; 13 | 14 | use Test::More qw! no_plan !; 15 | use Test::Exception; 16 | 17 | 18 | 19 | # 20 | # Load the factory 21 | # 22 | BEGIN {use_ok('Templer::Plugin::Factory');} 23 | require_ok('Templer::Plugin::Factory'); 24 | 25 | # 26 | # Load the formatter plugins. 27 | # 28 | BEGIN {use_ok('Templer::Plugin::HTML');} 29 | require_ok('Templer::Plugin::HTML'); 30 | 31 | BEGIN {use_ok('Templer::Plugin::Markdown');} 32 | require_ok('Templer::Plugin::Markdown'); 33 | 34 | BEGIN {use_ok('Templer::Plugin::Perl');} 35 | require_ok('Templer::Plugin::Perl'); 36 | 37 | BEGIN {use_ok('Templer::Plugin::Textile');} 38 | require_ok('Templer::Plugin::Textile'); 39 | 40 | 41 | 42 | 43 | # 44 | # Instantiate the helper. 45 | # 46 | my $factory = Templer::Plugin::Factory->new(); 47 | ok( $factory, "Loaded the factory object." ); 48 | isa_ok( $factory, "Templer::Plugin::Factory" ); 49 | 50 | # 51 | # We should have only a small number of known plugins registered. 52 | # 53 | my @known = $factory->formatters(); 54 | is( scalar @known, 4, "There are four known formatters" ); 55 | 56 | # 57 | # The names are what we expect. 58 | # 59 | my @sorted = sort(@known); 60 | is( $sorted[0], "html", "The first is HTML" ); 61 | is( $sorted[1], "markdown", "The second is markdown" ); 62 | is( $sorted[2], "perl", "The third is markdown" ); 63 | is( $sorted[3], "textile", "The fourth is textile" ); 64 | 65 | 66 | # 67 | # Get the HTML plugin by name, testing that case sensitivity 68 | # isn't important. 69 | # 70 | isa_ok( $factory->formatter("html"), "Templer::Plugin::HTML" ); 71 | isa_ok( $factory->formatter("HTML"), "Templer::Plugin::HTML" ); 72 | isa_ok( $factory->formatter("htML"), "Templer::Plugin::HTML" ); 73 | 74 | # 75 | # Get the markdown plugin by name, testing that case sensitivity 76 | # isn't important. 77 | # 78 | isa_ok( $factory->formatter("markdown"), "Templer::Plugin::Markdown" ); 79 | isa_ok( $factory->formatter("MARKDOWN"), "Templer::Plugin::Markdown" ); 80 | isa_ok( $factory->formatter("MARkdown"), "Templer::Plugin::Markdown" ); 81 | 82 | # 83 | # Get the perl plugin by name, testing that case sensitivity 84 | # isn't important. 85 | # 86 | isa_ok( $factory->formatter("perl"), "Templer::Plugin::Perl" ); 87 | isa_ok( $factory->formatter("PERl"), "Templer::Plugin::Perl" ); 88 | isa_ok( $factory->formatter("PERL"), "Templer::Plugin::Perl" ); 89 | 90 | # 91 | # Get the textile plugin by name, testing that case sensitivity 92 | # isn't important. 93 | # 94 | isa_ok( $factory->formatter("textile"), "Templer::Plugin::Textile" ); 95 | isa_ok( $factory->formatter("TEXTILE"), "Templer::Plugin::Textile" ); 96 | isa_ok( $factory->formatter("TEXTile"), "Templer::Plugin::Textile" ); 97 | 98 | 99 | 100 | # 101 | # Unknown plugins are an error 102 | # 103 | foreach my $name (qw! fake test missing unknown !) 104 | { 105 | ok( !$factory->formatter($name), "Unknown plugin fails: $name" ); 106 | } 107 | 108 | 109 | # 110 | # Attempting to load a plugin with a missing name is an error. 111 | # 112 | dies_ok( sub {$factory->formatter(undef)}, "Missing plugin-name causes die()" ); 113 | dies_ok( sub {$factory->formatter("")}, "Missing plugin-name causes die()" ); 114 | 115 | 116 | # 117 | # Input testing 118 | # 119 | my $input = "**STRONG** The number is {42}."; 120 | my $h_out = $factory->formatter("html")->format($input); 121 | my $m_out = $factory->formatter("markdown")->format($input); 122 | my $p_out = $factory->formatter("perl")->format($input); 123 | my $t_out = $factory->formatter("textile")->format($input); 124 | 125 | 126 | # 127 | # The HTML formatter won't make any changes. 128 | # 129 | ok( $factory->formatter("html")->available(), 130 | "HTML Formatter is always available" ); 131 | is( $input, $h_out, "HTML formatter resulted in no changes" ); 132 | 133 | 134 | # 135 | # Markdown 136 | # 137 | if ( $factory->formatter("markdown")->available() ) 138 | { 139 | ok( $m_out =~ /strong/i, "Formatting with markdown worked" ); 140 | } 141 | else 142 | { 143 | is( $input, $m_out, 144 | "When disabled the markdown plugin didn't modify our text" ); 145 | } 146 | 147 | 148 | # 149 | # Perl 150 | # 151 | if ( $factory->formatter("perl")->available() ) 152 | { 153 | ok( $p_out =~ / 42\./, "Formatting with perl worked" ); 154 | } 155 | else 156 | { 157 | is( $input, $m_out, 158 | "When disabled the perl plugin didn't modify our text" ); 159 | } 160 | 161 | 162 | 163 | # 164 | # Textile 165 | # 166 | if ( $factory->formatter("textile")->available() ) 167 | { 168 | ok( $t_out =~ /class="caps"/i, "Formatting with textile worked" ); 169 | } 170 | else 171 | { 172 | is( $input, $t_out, 173 | "When disabled the textile plugin didn't modify our text" ); 174 | } 175 | 176 | -------------------------------------------------------------------------------- /t/test-templer-plugin-factory.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test we can load our singleton class-factory, and 4 | # register/fetch a trivial class with it. 5 | # 6 | # Steve 7 | # 8 | 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Test::More qw! no_plan !; 14 | 15 | 16 | # 17 | # Simple class is just a class that has a new() method / constructor 18 | # we can stash away and retrieve. 19 | # 20 | package Simple::Class; 21 | 22 | sub new 23 | { 24 | my $class = shift; 25 | bless {}, $class; 26 | } 27 | 28 | sub hello 29 | { 30 | return "world"; 31 | } 32 | 33 | 34 | 35 | 36 | package main; 37 | 38 | BEGIN {use_ok('Templer::Plugin::Factory');} 39 | require_ok('Templer::Plugin::Factory'); 40 | 41 | # 42 | # Instantiate the helper. 43 | # 44 | my $factory = Templer::Plugin::Factory->new(); 45 | ok( $factory, "Loaded the factory object." ); 46 | isa_ok( $factory, "Templer::Plugin::Factory" ); 47 | 48 | my $factory2 = Templer::Plugin::Factory->new(); 49 | ok( $factory2, "Loaded the factory object." ); 50 | isa_ok( $factory2, "Templer::Plugin::Factory" ); 51 | 52 | is( $factory, $factory2, "Our singleton is a singleton" ); 53 | 54 | # 55 | # The plugins are auto-loaded, so we'll have formatters. 56 | # 57 | my @known = $factory->formatters(); 58 | is( scalar @known, 4, "There are four known formatters" ); 59 | 60 | # 61 | # Register a formatter. 62 | # 63 | $factory->register_formatter( "tmp", "Simple::Class" ); 64 | @known = $factory->formatters(); 65 | is( scalar @known, 5, "There is now another formatter." ); 66 | 67 | 68 | # 69 | # Get the formatter by name, testing that mixed case works 70 | # 71 | foreach my $name (qw! tmp TMP tMp !) 72 | { 73 | my $f = $factory->formatter($name); 74 | isa_ok( $f, "Simple::Class", "Fetching the plugin by name - $name" ); 75 | is( $f->hello(), "world", "The stub class behaves as expected" ); 76 | } 77 | 78 | # 79 | # TODO: Variable-Plugin tests. 80 | # 81 | -------------------------------------------------------------------------------- /t/test-templer-plugin-filecontents.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ 2 | # 3 | # Test the execution of file inclusion command via our plugin. 4 | # 5 | # NOTE: We have to make a Templer::Page object so that source 6 | # is correct. 7 | # 8 | # Steve 9 | # -- 10 | 11 | 12 | 13 | use strict; 14 | use warnings; 15 | 16 | use Test::More qw! no_plan !; 17 | use File::Temp qw! tempdir !; 18 | 19 | # 20 | # Load the factory 21 | # 22 | BEGIN {use_ok('Templer::Plugin::Factory');} 23 | require_ok('Templer::Plugin::Factory'); 24 | 25 | # 26 | # Load the plugin + dependency 27 | # 28 | BEGIN {use_ok('Templer::Site');} 29 | require_ok('Templer::Site'); 30 | BEGIN {use_ok('Templer::Site::Page');} 31 | require_ok('Templer::Site::Page'); 32 | BEGIN {use_ok('Templer::Plugin::FileContents');} 33 | require_ok('Templer::Plugin::FileContents'); 34 | 35 | 36 | # 37 | # Create a config file 38 | # 39 | my $site = Templer::Site->new(); 40 | 41 | # 42 | # Instantiate the helper. 43 | # 44 | my $factory = Templer::Plugin::Factory->new(); 45 | ok( $factory, "Loaded the factory object." ); 46 | isa_ok( $factory, "Templer::Plugin::Factory" ); 47 | 48 | # 49 | # Create a temporary tree. 50 | # 51 | my $dir = tempdir( CLEANUP => 1 ); 52 | 53 | # 54 | # Create a page. 55 | # 56 | open( my $handle, ">", $dir . "/input.wgn" ); 57 | print $handle <new( file => $dir . "/input.wgn" ); 73 | ok( $page, "We created a page object" ); 74 | isa_ok( $page, "Templer::Site::Page", "Which has the correct type" ); 75 | 76 | 77 | # 78 | # Get the title to be sure 79 | # 80 | is( $page->field("title"), 81 | "This is my page title.", 82 | "The page has the correct title" ); 83 | 84 | # 85 | # Get the data, after plugin-expansion 86 | # 87 | my %original = $page->fields(); 88 | my $ref = $factory->expand_variables( $site, $page, \%original ); 89 | my %updated = %$ref; 90 | 91 | ok( %updated, "Fetching the fields of the page succeeded" ); 92 | ok( $updated{ 'password' }, "The fields contain a file reference" ); 93 | ok( $updated{ 'foo' }, "The fields contain the self-file reference" ); 94 | 95 | # 96 | # Do the file contents look sane? 97 | # 98 | ok( $updated{ 'password' } =~ /root:/, "The password file looks sane" ); 99 | ok( $updated{ 'foo' } =~ /passwd/, "The self-file looks sane" ); 100 | 101 | -------------------------------------------------------------------------------- /t/test-templer-plugin-fileglob.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ 2 | # 3 | # Test the execution of file-globbing command via our plugin. 4 | # 5 | # NOTE: We have to make a Templer::Page object so that source 6 | # is correct. 7 | # 8 | # Steve 9 | # -- 10 | 11 | 12 | 13 | use strict; 14 | use warnings; 15 | 16 | use Test::More qw! no_plan !; 17 | use File::Temp qw! tempdir !; 18 | 19 | # 20 | # Load the factory 21 | # 22 | BEGIN {use_ok('Templer::Site');} 23 | require_ok('Templer::Site'); 24 | BEGIN {use_ok('Templer::Plugin::Factory');} 25 | require_ok('Templer::Plugin::Factory'); 26 | 27 | # 28 | # Load the plugin + dependency 29 | # 30 | BEGIN {use_ok('Templer::Site::Page');} 31 | require_ok('Templer::Site::Page'); 32 | BEGIN {use_ok('Templer::Plugin::FileGlob');} 33 | require_ok('Templer::Plugin::FileGlob'); 34 | 35 | 36 | 37 | # 38 | # Create a config file 39 | # 40 | my $site = Templer::Site->new( suffix => ".skx" ); 41 | 42 | # 43 | # Instantiate the helper. 44 | # 45 | my $factory = Templer::Plugin::Factory->new(); 46 | ok( $factory, "Loaded the factory object." ); 47 | isa_ok( $factory, "Templer::Plugin::Factory" ); 48 | 49 | # 50 | # Create a temporary tree. 51 | # 52 | my $dir = tempdir( CLEANUP => 1 ); 53 | 54 | # 55 | # Create a page. 56 | # 57 | open( my $handle, ">", $dir . "/input.skx" ); 58 | print $handle <", $dir . "/foo/input.skx" ); 77 | print $handle <new( file => $dir . "/input.skx" ); 92 | ok( $page, "We created a page object" ); 93 | isa_ok( $page, "Templer::Site::Page", "Which has the correct type" ); 94 | 95 | 96 | # 97 | # Get the title to be sure 98 | # 99 | is( $page->field("title"), 100 | "This is my page title.", 101 | "The page has the correct title" ); 102 | 103 | # 104 | # Get the data, after plugin-expansion 105 | # 106 | my %original = $page->fields(); 107 | my $ref = $factory->expand_variables( $site, $page, \%original ); 108 | my %updated = %$ref; 109 | 110 | ok( %updated, "Fetching the fields of the page succeeded" ); 111 | ok( $updated{ 'files' }, "The fields contain a file reference" ); 112 | 113 | foreach my $obj ( @{ $updated{ 'files' } } ) 114 | { 115 | my $file = $obj->{ 'file' }; 116 | ok( $obj->{ 'file' }, "The file reference has a name : $obj->{ 'file' }" ); 117 | ok( $obj->{ 'file' } =~ m{^foo/}, " file reference is sane" ); 118 | if ( $file eq "foo/foo.txt" ) 119 | { 120 | is( $obj->{ 'contents' }, "something", " content is propagated" ); 121 | is( $obj->{ 'dirname' }, 'foo', " dirname is captured" ); 122 | is( $obj->{ 'basename' }, 'foo', " basename is captured" ); 123 | is( $obj->{ 'extension' }, 'txt', " extension is captured" ); 124 | } 125 | elsif ( $file eq "foo/ok.txt" ) 126 | { 127 | is( $obj->{ 'contents' }, "something", " content is propagated" ); 128 | is( $obj->{ 'dirname' }, 'foo', " dirname is captured" ); 129 | is( $obj->{ 'basename' }, 'ok', " basename is captured" ); 130 | is( $obj->{ 'extension' }, 'txt', " extension is captured" ); 131 | } 132 | elsif ( $file eq "foo/bar.txt" ) 133 | { 134 | is( $obj->{ 'contents' }, "something", " content is propagated" ); 135 | is( $obj->{ 'dirname' }, 'foo', " dirname is captured" ); 136 | is( $obj->{ 'basename' }, 'bar', " basename is captured" ); 137 | is( $obj->{ 'extension' }, 'txt', " extension is captured" ); 138 | } 139 | elsif ( $file eq "foo/bar" ) 140 | { 141 | is( $obj->{ 'contents' }, "something", " content is propagated" ); 142 | is( $obj->{ 'dirname' }, 'foo', " dirname is captured" ); 143 | is( $obj->{ 'basename' }, 'bar', " basename is captured" ); 144 | is( $obj->{ 'extension' }, undef, " extension is empty" ); 145 | } 146 | elsif ( $file eq "foo/input.skx" ) 147 | { 148 | is( $obj->{ 'variable' }, 149 | 'Value', " input variables are propagated" ); 150 | is( $obj->{ 'dirname' }, 'foo', " dirname is captured" ); 151 | is( $obj->{ 'basename' }, 'input', " basename is captured" ); 152 | is( $obj->{ 'extension' }, 'skx', " extension is captured" ); 153 | } 154 | } 155 | is( scalar( @{ $updated{ 'files' } } ), 156 | 5, "We received the number of files we expected" ); 157 | 158 | # 159 | # All done. 160 | # 161 | 162 | 163 | 164 | 165 | # 166 | # Create a file, given a name. 167 | # 168 | sub createFile 169 | { 170 | my ($filename) = (@_); 171 | 172 | ok( !-e $filename, "The file we're creating is not present" ); 173 | 174 | open( my $handle, ">", $filename ); 175 | print $handle "something"; 176 | close($handle); 177 | 178 | ok( -e $filename, "We created a temporary file" ); 179 | } 180 | -------------------------------------------------------------------------------- /t/test-templer-plugin-filters.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test we can instantiate the known filter-plugins, and that 4 | # they work when Templer::Site use HTTML::Template. 5 | # 6 | # Bruno 7 | # 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More qw! no_plan !; 13 | use Test::Exception; 14 | use File::Temp qw! tempdir !; 15 | 16 | # 17 | # Load the factory 18 | # 19 | BEGIN {use_ok('Templer::Plugin::Factory');} 20 | require_ok('Templer::Plugin::Factory'); 21 | 22 | # 23 | # Load the filter plugins. 24 | # 25 | BEGIN {use_ok('Templer::Plugin::Dollar');} 26 | require_ok('Templer::Plugin::Dollar'); 27 | 28 | BEGIN {use_ok('Templer::Plugin::Strict');} 29 | require_ok('Templer::Plugin::Strict'); 30 | 31 | # 32 | # Load the Template engine 33 | # 34 | BEGIN {use_ok('HTML::Template');} 35 | require_ok('HTML::Template'); 36 | 37 | # 38 | # Load the site manager 39 | # 40 | BEGIN {use_ok('Templer::Site');} 41 | require_ok('Templer::Site'); 42 | 43 | BEGIN {use_ok('Templer::Site::Page');} 44 | require_ok('Templer::Site::Page'); 45 | 46 | BEGIN {use_ok('Templer::Site::Asset');} 47 | require_ok('Templer::Site::Asset'); 48 | 49 | # 50 | # Instantiate the helper. 51 | # 52 | my $factory = Templer::Plugin::Factory->new(); 53 | ok( $factory, "Loaded the factory object." ); 54 | isa_ok( $factory, "Templer::Plugin::Factory" ); 55 | 56 | # 57 | # We should have only a small number of known plugins registered. 58 | # 59 | my @known = $factory->filters(); 60 | is( scalar @known, 2, "There are only two filter" ); 61 | 62 | # 63 | # The names are what we expect. 64 | # 65 | my @sorted = sort(@known); 66 | is( $sorted[0], "dollar", "The first is Dollar" ); 67 | is( $sorted[1], "strict", "The second is Strict" ); 68 | 69 | # 70 | # Get the Dollar plugin by name, testing that case sensitivity 71 | # isn't important. 72 | # 73 | isa_ok( $factory->filter("dollar"), "Templer::Plugin::Dollar" ); 74 | isa_ok( $factory->filter("DOLLAR"), "Templer::Plugin::Dollar" ); 75 | isa_ok( $factory->filter("DollaR"), "Templer::Plugin::Dollar" ); 76 | 77 | # 78 | # Unknown plugins are an error 79 | # 80 | foreach my $name (qw! fake test missing unknown !) 81 | { 82 | ok( !$factory->filter($name), "Unknown plugin fails: $name" ); 83 | } 84 | 85 | # 86 | # Attempting to load a plugin with a missing name is an error. 87 | # 88 | dies_ok( sub {$factory->filter(undef)}, "Missing plugin-name causes die()" ); 89 | dies_ok( sub {$factory->filter("")}, "Missing plugin-name causes die()" ); 90 | 91 | # 92 | # Dollar filter testing 93 | # 94 | my $input = '${title escape=html} ${author} ${email escape="url"}'; 95 | my $h_out = $factory->filter("dollar")->filter($input); 96 | my $h_exp = 97 | ' '; 98 | 99 | ok( $factory->filter("dollar")->available(), 100 | "Dollar Filter is always available" ); 101 | is( $h_out, $h_exp, "Dollar filter make correct change" ); 102 | 103 | # 104 | # Strict filter testing 105 | # 106 | $input = ' '; 107 | $h_out = $factory->filter("strict")->filter($input); 108 | $h_exp = ' '; 109 | 110 | ok( $factory->filter("strict")->available(), 111 | "Strict Filter is always available" ); 112 | is( $h_out, $h_exp, "Strict filter make correct change" ); 113 | 114 | # 115 | # Create a temporary tree. 116 | # 117 | my $dir = tempdir( CLEANUP => 1 ); 118 | mkdir "$dir/input"; 119 | mkdir "$dir/layouts"; 120 | 121 | # 122 | # Create a template file. 123 | # 124 | open( my $handle, ">", $dir . "/layouts/default.layout" ); 125 | print $handle < 127 | 128 | \${content} 129 | EOF 130 | close($handle); 131 | 132 | # 133 | # Create an input page. 134 | # 135 | open( $handle, ">", $dir . "/input/index.skx" ); 136 | print $handle < 0, 150 | "include-path" => "$dir/includes", 151 | "input" => "$dir/input/", 152 | "layout" => "default.layout", 153 | "layout-path" => "$dir/layouts", 154 | "output" => "$dir/output/", 155 | "plugin-path" => "$dir/plugins", 156 | "suffix" => ".skx", 157 | "verbose" => 0, 158 | "debug" => 0, 159 | "force" => 0, 160 | ); 161 | my $site = Templer::Site->new(%data); 162 | ok( $site, "Loaded the site object." ); 163 | isa_ok( $site, "Templer::Site" ); 164 | 165 | # 166 | # Build site process should use template filters in template as well as page 167 | # content 168 | # 169 | $site->init(); 170 | $site->build(); 171 | 172 | local $/ = undef; 173 | open( $handle, "<", $dir . "/output/index.html" ); 174 | $h_out = <$handle>; 175 | close($handle); 176 | 177 | $h_exp = <A simple title 179 | 180 | Something inside 181 | 182 | EOF 183 | 184 | is( $h_out, $h_exp, "Site building filters template correctly" ); 185 | -------------------------------------------------------------------------------- /t/test-templer-plugin-redis.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ 2 | # 3 | # Test the Redis plugin. 4 | # 5 | # This test assumes that redis is installed and running on the localhost. 6 | # 7 | # Steve 8 | # -- 9 | 10 | 11 | 12 | use strict; 13 | use warnings; 14 | 15 | use Test::More; 16 | use File::Temp qw! tempdir !; 17 | 18 | # 19 | # Load the factory 20 | # 21 | BEGIN {use_ok('Templer::Site');} 22 | require_ok('Templer::Site'); 23 | BEGIN {use_ok('Templer::Plugin::Factory');} 24 | require_ok('Templer::Plugin::Factory'); 25 | 26 | # 27 | # Load the plugin + dependency 28 | # 29 | BEGIN {use_ok('Templer::Site::Page');} 30 | require_ok('Templer::Site::Page'); 31 | BEGIN {use_ok('Templer::Plugin::Redis');} 32 | require_ok('Templer::Plugin::Redis'); 33 | 34 | 35 | # 36 | # Should we skip? 37 | # 38 | my $skip = 0; 39 | 40 | 41 | # 42 | # Ensure that we have Redis installed. 43 | # 44 | ## no critic (Eval) 45 | eval "use Redis"; 46 | ## use critic 47 | $skip = 1 if ($@); 48 | 49 | 50 | # 51 | # Connect to redis 52 | # 53 | my $redis; 54 | eval {$redis = new Redis();}; 55 | $skip = 1 if ($@); 56 | $skip = 1 unless ($redis); 57 | $skip = 1 unless ( $redis && $redis->ping() ); 58 | 59 | 60 | SKIP: 61 | { 62 | 63 | if ($skip) 64 | { 65 | done_testing; 66 | exit(0); 67 | } 68 | 69 | 70 | # 71 | # Create a config file 72 | # 73 | my $site = Templer::Site->new(); 74 | 75 | # 76 | # Instantiate the helper. 77 | # 78 | my $factory = Templer::Plugin::Factory->new(); 79 | ok( $factory, "Loaded the factory object." ); 80 | isa_ok( $factory, "Templer::Plugin::Factory" ); 81 | 82 | # 83 | # Create a temporary tree. 84 | # 85 | my $dir = tempdir( CLEANUP => 1 ); 86 | 87 | # 88 | # Create a page. 89 | # 90 | open( my $handle, ">", $dir . "/input.wgn" ); 91 | print $handle <get("steve.kemp"); 106 | 107 | # 108 | # Set a known-value either way. 109 | # 110 | $redis->set( "steve.kemp", "is.me" ); 111 | 112 | # 113 | # Create the page 114 | # 115 | my $page = Templer::Site::Page->new( file => $dir . "/input.wgn" ); 116 | ok( $page, "We created a page object" ); 117 | isa_ok( $page, "Templer::Site::Page", "Which has the correct type" ); 118 | 119 | 120 | # 121 | # Get the title to be sure 122 | # 123 | is( $page->field("title"), 124 | "This is my redis-page title.", 125 | "The page has the correct title" 126 | ); 127 | 128 | # 129 | # Get the data, after plugin-expansion, which should mean that the 130 | # count is populated to something. 131 | # 132 | my %original = $page->fields(); 133 | my $ref = $factory->expand_variables( $site, $page, \%original ); 134 | my %updated = %$ref; 135 | 136 | ok( %updated, "Fetching the fields of the page succeeded" ); 137 | ok( $updated{ 'count' }, "The fields contain a count reference" ); 138 | is( $updated{ 'count' }, "is.me", "The field has the correct value" ); 139 | 140 | # 141 | # If there was previously a value, reset it. 142 | # 143 | if ($val) 144 | { 145 | $redis->set( "steve.kemp", $val ); 146 | } 147 | else 148 | { 149 | 150 | # 151 | # Otherwise cleanup 152 | # 153 | $redis->del("steve.kemp"); 154 | } 155 | 156 | # 157 | # All done. 158 | # 159 | done_testing; 160 | } 161 | -------------------------------------------------------------------------------- /t/test-templer-plugin-rootpath.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ 2 | # 3 | # Test the execution of RootPath plugin. 4 | # 5 | # Bruno 6 | # -- 7 | 8 | use strict; 9 | use warnings; 10 | 11 | use Test::More qw! no_plan !; 12 | use File::Temp qw! tempdir !; 13 | 14 | # 15 | # Load the factory 16 | # 17 | BEGIN {use_ok('Templer::Site');} 18 | require_ok('Templer::Site'); 19 | BEGIN {use_ok('Templer::Plugin::Factory');} 20 | require_ok('Templer::Plugin::Factory'); 21 | 22 | # 23 | # Load the plugin + dependency 24 | # 25 | BEGIN {use_ok('Templer::Site::Page');} 26 | require_ok('Templer::Site::Page'); 27 | BEGIN {use_ok('Templer::Plugin::RootPath');} 28 | require_ok('Templer::Plugin::RootPath'); 29 | 30 | # 31 | # Create a temporary tree. 32 | # 33 | my $dir = tempdir( CLEANUP => 1 ); 34 | 35 | # 36 | # Create a site object. 37 | # 38 | my $site = Templer::Site->new( input => $dir ); 39 | $site->init(); 40 | 41 | # 42 | # Instantiate the helper. 43 | # 44 | my $factory = Templer::Plugin::Factory->new(); 45 | ok( $factory, "Loaded the factory object." ); 46 | isa_ok( $factory, "Templer::Plugin::Factory" ); 47 | 48 | # 49 | # Create a source file at root level 50 | # 51 | open( my $handle, ">", $dir . "/input.skx" ); 52 | print $handle <", $dir . "/1/input.skx" ); 66 | print $handle <new( file => $dir . "/input.skx" ); 78 | ok( $page0, "We created a page object at root level" ); 79 | isa_ok( $page0, "Templer::Site::Page", " Which has the correct type:" ); 80 | is( $page0->field("title"), "a title", " ...and the correct title" ); 81 | 82 | # 83 | # Get the data, after plugin-expansion for page at root level 84 | # 85 | my %original = $page0->fields(); 86 | my $ref = $factory->expand_variables( $site, $page0, \%original ); 87 | my %updated = %$ref; 88 | 89 | ok( %updated, "Fetching the fields of the first page succeeded" ); 90 | ok( $updated{ 'css' }, "There is a path_to(css) variable" ); 91 | is( $updated{ 'css' }, "./css", " Which has the right value" ); 92 | ok( $updated{ 'root' }, "There is a path_to web root" ); 93 | is( $updated{ 'root' }, ".", " Which has the right value" ); 94 | 95 | # 96 | # Create second page and ensure title is there 97 | # 98 | my $page1 = Templer::Site::Page->new( file => $dir . "/1/input.skx" ); 99 | ok( $page1, "We created a page object at level 1 from root" ); 100 | isa_ok( $page1, "Templer::Site::Page", " Which has the correct type:" ); 101 | 102 | # 103 | # Get the data, after plugin-expansion for page at level 1 104 | # 105 | %original = $page1->fields(); 106 | $ref = $factory->expand_variables( $site, $page1, \%original ); 107 | %updated = %$ref; 108 | 109 | ok( %updated, "Fetching the fields of the second page succeeded" ); 110 | ok( $updated{ 'css' }, "There is a path_to(css) variable" ); 111 | is( $updated{ 'css' }, "../css", " Which has the right value" ); 112 | ok( $updated{ 'root' }, "There is a path_to web root" ); 113 | is( $updated{ 'root' }, "..", " Which has the right value" ); 114 | 115 | # 116 | # All done. 117 | # 118 | -------------------------------------------------------------------------------- /t/test-templer-plugin-shellcommand.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ 2 | # 3 | # Test the execution of shell commands via our plugin. 4 | # 5 | # Steve 6 | # -- 7 | 8 | 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Test::More qw! no_plan !; 14 | 15 | # 16 | # Load the factory 17 | # 18 | BEGIN {use_ok('Templer::Site');} 19 | require_ok('Templer::Site'); 20 | BEGIN {use_ok('Templer::Plugin::Factory');} 21 | require_ok('Templer::Plugin::Factory'); 22 | 23 | # 24 | # Load the plugin. 25 | # 26 | BEGIN {use_ok('Templer::Plugin::ShellCommand');} 27 | require_ok('Templer::Plugin::ShellCommand'); 28 | 29 | 30 | 31 | # 32 | # The config object 33 | # 34 | my $site = Templer::Site->new(); 35 | 36 | 37 | # 38 | # Instantiate the helper. 39 | # 40 | my $factory = Templer::Plugin::Factory->new(); 41 | ok( $factory, "Loaded the factory object." ); 42 | isa_ok( $factory, "Templer::Plugin::Factory" ); 43 | 44 | # 45 | # The test data we have. 46 | # 47 | my %input = ( "title" => "This is my page title.", 48 | "foo" => "run_command( /bin/ls /etc )", 49 | "bar" => "baz" 50 | ); 51 | 52 | SKIP: 53 | { 54 | skip "/bin/ls was not found." unless ( -x "/bin/ls" ); 55 | 56 | # 57 | # Expand the variables 58 | # 59 | my $ref = $factory->expand_variables( $site, undef, \%input ); 60 | ok( $ref, "Calling the plugin returned something sane." ); 61 | 62 | # 63 | # Get the updated values which we expect to be unchanged. 64 | # 65 | is( $ref->{ 'title' }, 66 | "This is my page title.", 67 | "After calling the plugin the sane value is unchanged." ); 68 | 69 | is( $ref->{ 'bar' }, 70 | "baz", "After calling the plugin the sane value is unchanged." ); 71 | 72 | # 73 | # Now see if our "foo" value was replaced by the output of the shell 74 | # command. 75 | # 76 | my $shell = $ref->{ 'foo' }; 77 | ok( length($shell), "The shell command execution returned something." ); 78 | ok( $shell =~ /passwd/, "Which looks a little sane." ); 79 | ok( $shell =~ /fstab/, "And a little more sane." ); 80 | } 81 | -------------------------------------------------------------------------------- /t/test-templer-plugin-timestamp.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ 2 | # 3 | # Test the execution of timestamp plugin. 4 | # 5 | # NOTE: We have to make a Templer::Page object so that source 6 | # is correct. 7 | # 8 | # Steve 9 | # -- 10 | 11 | 12 | 13 | use strict; 14 | use warnings; 15 | 16 | use Test::More qw! no_plan !; 17 | use File::Temp qw! tempdir !; 18 | 19 | # 20 | # Load the factory 21 | # 22 | BEGIN {use_ok('Templer::Site');} 23 | require_ok('Templer::Site'); 24 | BEGIN {use_ok('Templer::Plugin::Factory');} 25 | require_ok('Templer::Plugin::Factory'); 26 | 27 | # 28 | # Load the plugin + dependency 29 | # 30 | BEGIN {use_ok('Templer::Site::Page');} 31 | require_ok('Templer::Site::Page'); 32 | BEGIN {use_ok('Templer::Plugin::TimeStamp');} 33 | require_ok('Templer::Plugin::TimeStamp'); 34 | 35 | 36 | 37 | # 38 | # Create a config file 39 | # 40 | my $site = Templer::Site->new(); 41 | 42 | # 43 | # Instantiate the helper. 44 | # 45 | my $factory = Templer::Plugin::Factory->new(); 46 | ok( $factory, "Loaded the factory object." ); 47 | isa_ok( $factory, "Templer::Plugin::Factory" ); 48 | 49 | # 50 | # Create a temporary tree. 51 | # 52 | my $dir = tempdir( CLEANUP => 1 ); 53 | 54 | # 55 | # Create a page. 56 | # 57 | open( my $handle, ">", $dir . "/input.wgn" ); 58 | print $handle <new( file => $dir . "/input.wgn" ); 73 | ok( $page, "We created a page object" ); 74 | isa_ok( $page, "Templer::Site::Page", "Which has the correct type" ); 75 | 76 | 77 | # 78 | # Get the title to be sure 79 | # 80 | is( $page->field("title"), 81 | "This is my page title.", 82 | "The page has the correct title" ); 83 | 84 | # 85 | # Get the data, after plugin-expansion 86 | # 87 | my %original = $page->fields(); 88 | my $ref = $factory->expand_variables( $site, $page, \%original ); 89 | my %updated = %$ref; 90 | 91 | ok( %updated, "Fetching the fields of the page succeeded" ); 92 | ok( $updated{ 'myear' }, "The fields contain a year-reference" ); 93 | ok( $updated{ 'mmon' }, "The fields contain a year-reference" ); 94 | 95 | # 96 | # The year + month from the expanded template 97 | # 98 | my $myear = $updated{ 'myear' }; 99 | my $mmon = $updated{ 'mmon' }; 100 | $mmon =~ s/^0//g; 101 | 102 | 103 | # 104 | # Get the current year/month/day, etc, to compare with the 105 | # plugin-found values. 106 | # 107 | my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = 108 | localtime(time); 109 | $year += 1900; 110 | $mon += 1; 111 | 112 | 113 | # 114 | # Is all OK? 115 | # 116 | is( $myear, $year, "The year is correct" ); 117 | is( $mmon, $mon, "The month is correct" ); 118 | 119 | 120 | 121 | # 122 | # All done. 123 | # 124 | -------------------------------------------------------------------------------- /t/test-templer-site-new.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test we can create a new site. 4 | # 5 | # Steve 6 | # 7 | 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More qw! no_plan !; 13 | use File::Temp qw! tempdir !; 14 | 15 | 16 | # 17 | # Load the module. 18 | # 19 | BEGIN {use_ok('Templer::Site::New');} 20 | require_ok('Templer::Site::New'); 21 | 22 | # 23 | # Instantiate the helper. 24 | # 25 | my $helper = Templer::Site::New->new(); 26 | ok( $helper, "Created the helper object." ); 27 | isa_ok( $helper, "Templer::Site::New", "It is the correct type" ); 28 | 29 | # 30 | # Create a temporary directory to work with. 31 | # 32 | my $dir = tempdir( CLEANUP => 1 ); 33 | ok( -d $dir, "Created temporary directory" ); 34 | 35 | 36 | # 37 | # These are the files/directories we expect to be created. 38 | # 39 | # The hash-values are the type of created thing we expect: 40 | # 41 | # 1 => Directory. 42 | # 0 => File. 43 | my %EXPECTED = ( 44 | "input" => 1, 45 | "output" => 1, 46 | "includes" => 1, 47 | "layouts" => 1, 48 | "input/index.wgn" => 0, 49 | "input/about.wgn" => 0, 50 | "input/robots.txt" => 0, 51 | "layouts/default.layout" => 0, 52 | "templer.cfg" => 0, 53 | 54 | ); 55 | 56 | 57 | 58 | # 59 | # Test that the entries don't exist at the moment. 60 | # 61 | foreach my $file ( sort keys %EXPECTED ) 62 | { 63 | ok( !-e $dir . "/" . $file, 64 | "Before the site was constructed file was missing: $file" ); 65 | } 66 | 67 | 68 | # 69 | # Now create the new site. 70 | # 71 | ok( $helper->create($dir), "Creating the new site succeeded" ); 72 | 73 | 74 | # 75 | # Test that they were present 76 | # 77 | foreach my $file ( sort keys %EXPECTED ) 78 | { 79 | my $type = $EXPECTED{ $file }; 80 | 81 | ok( -e $dir . "/" . $file, "File created, as expected: $file" ); 82 | 83 | if ( $type == 0 ) 84 | { 85 | ok( !-d $dir . "/" . $file, "Entry is a file - $file" ); 86 | } 87 | elsif ( $type == 1 ) 88 | { 89 | ok( -d $dir . "/" . $file, "Entry is a directory - $file" ); 90 | } 91 | else 92 | { 93 | ok( undef, "The test-type made no sense" ); 94 | } 95 | } 96 | 97 | # 98 | # All done. 99 | # 100 | -------------------------------------------------------------------------------- /t/test-templer-site-synopsis.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test that the Templer::Site synopsis code works 4 | # 5 | 6 | 7 | use strict; 8 | use warnings; 9 | 10 | use Test::More qw! no_plan !; 11 | use File::Temp qw! tempdir !; 12 | use File::Path qw! mkpath !; 13 | 14 | 15 | BEGIN {use_ok('Templer::Site');} 16 | require_ok('Templer::Site'); 17 | 18 | 19 | # 20 | # Make a temporary directory 21 | # 22 | my $tmp = tempdir( CLEANUP => 1 ); 23 | ok( -d $tmp, "We created a temporary directory" ); 24 | 25 | # 26 | # Make the input-subdirectory 27 | # 28 | File::Path::mkpath( $tmp . "/input", { verbose => 0, mode => oct(755) } ); 29 | ok( -d $tmp, "We created an input/ directory" ); 30 | open( my $pagefile, '>', $tmp . "/input/test.skx" ); 31 | print $pagefile "test page"; 32 | close( $pagefile); 33 | 34 | open( my $assetfile, '>', $tmp . "/input/test.css" ); 35 | print $assetfile "test asset"; 36 | close( $assetfile); 37 | 38 | 39 | 40 | # 41 | # Now create the Templer::Site object. 42 | # 43 | my $site = Templer::Site->new( suffix => ".skx", 44 | input => "$tmp/input", 45 | output => "$tmp/output" 46 | ); 47 | 48 | # 49 | # Get pages 50 | # 51 | my @pages = $site->pages(); 52 | ok( scalar(@pages) == 1, "one page"); 53 | 54 | # 55 | # Get assets 56 | # 57 | my @assets = $site->assets(); 58 | ok( scalar(@assets) == 1, "one asset"); 59 | -------------------------------------------------------------------------------- /t/test-templer-site.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test that the Templer::Site init method will create an 4 | # output directory if it is missing. 5 | # 6 | # Steve 7 | # 8 | 9 | 10 | use strict; 11 | use warnings; 12 | 13 | use Test::More qw! no_plan !; 14 | use File::Temp qw! tempdir !; 15 | use File::Path qw! mkpath !; 16 | 17 | 18 | BEGIN {use_ok('Templer::Site');} 19 | require_ok('Templer::Site'); 20 | 21 | 22 | # 23 | # Make a temporary directory 24 | # 25 | my $tmp = tempdir( CLEANUP => 1 ); 26 | ok( -d $tmp, "We created a temporary directory" ); 27 | 28 | # 29 | # Make the input-subdirectory 30 | # 31 | File::Path::mkpath( $tmp . "/input", { verbose => 0, mode => oct(755) } ); 32 | ok( -d $tmp, "We created an input/ directory" ); 33 | 34 | # 35 | # Now create the Templer::Site object. 36 | # 37 | my $site = Templer::Site->new( input => "$tmp/input", 38 | "in-place" => 1, 39 | output => "$tmp/output" 40 | ); 41 | 42 | ok( !-d "$tmp/output", 43 | "Before running Templer::Site::init the output directory is missing" ); 44 | $site->init(); 45 | ok( !-d "$tmp/output", "After running Templer::Site::init in-place worked" ); 46 | 47 | # 48 | # Now do it again, but without the in-place mode set. 49 | # 50 | $site = Templer::Site->new( input => "$tmp/input", 51 | output => "$tmp/output" ); 52 | 53 | ok( !-d "$tmp/output", 54 | "Before running Templer::Site::init the output directory is missing" ); 55 | $site->init(); 56 | ok( -d "$tmp/output", 57 | "After running Templer::Site::init the output directory is created." ); 58 | -------------------------------------------------------------------------------- /t/test-templer-synchronization.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -Ilib/ -I../lib/ -w 2 | # 3 | # Test that the Templer::Site::sync method will do the right 4 | # thing. 5 | # 6 | # Bruno 7 | # 8 | 9 | use strict; 10 | use warnings; 11 | 12 | use Test::More qw! no_plan !; 13 | use File::Temp qw! tempdir !; 14 | use File::Path qw! mkpath !; 15 | 16 | # 17 | # Load needed objects 18 | # 19 | BEGIN {use_ok('Templer::Site');} 20 | require_ok('Templer::Site'); 21 | 22 | BEGIN {use_ok('Templer::Plugin::Factory');} 23 | require_ok('Templer::Plugin::Factory'); 24 | 25 | BEGIN {use_ok('Templer::Site::Page');} 26 | require_ok('Templer::Site::Page'); 27 | 28 | BEGIN {use_ok('Templer::Site::Asset');} 29 | require_ok('Templer::Site::Asset'); 30 | 31 | # 32 | # Make a temporary directory 33 | # 34 | my $tmp = tempdir( CLEANUP => 0 ); 35 | ok( -d $tmp, "We created a temporary directory" ); 36 | note("Temporary directory is: $tmp"); 37 | 38 | # 39 | # Make the input-subdirectory 40 | # 41 | File::Path::mkpath( $tmp . "/input", { verbose => 0, mode => oct(755) } ); 42 | ok( -d $tmp . "/input", "We created an input/ directory" ); 43 | 44 | # 45 | # Create a layout file. 46 | # 47 | File::Path::mkpath( $tmp . "/layouts", { verbose => 0, mode => oct(755) } ); 48 | ok( -d $tmp . "/layouts", "We created a layouts directory" ); 49 | 50 | open( my $handle, ">", $tmp . "/layouts/default.layout" ); 51 | print $handle < 53 | EOF 54 | close($handle); 55 | ok( -f $tmp . "/layouts/default.layout", "We created a layout file" ); 56 | 57 | # 58 | # Create a page 59 | # 60 | open( $handle, ">", $tmp . "/input/index.skx" ); 61 | print $handle < 0, 79 | "include-path" => "$tmp/includes", 80 | "input" => "$tmp/input/", 81 | "layout" => "default.layout", 82 | "layout-path" => "$tmp/layouts", 83 | "output" => "$tmp/output/", 84 | "plugin-path" => "$tmp/plugins", 85 | "suffix" => ".skx", 86 | "verbose" => 0, 87 | "debug" => 0, 88 | "force" => 0, 89 | "sync" => 1, 90 | ); 91 | my $site = Templer::Site->new(%data); 92 | isa_ok( $site, "Templer::Site" ); 93 | 94 | # 95 | # Setup the output directory, etc. 96 | # 97 | $site->init(); 98 | ok( -d "$tmp/output", "The output directory was created" ); 99 | 100 | # 101 | # Create garbage directory 102 | # 103 | File::Path::mkpath( $tmp . "/output/garbage", 104 | { verbose => 0, mode => oct(755) } ); 105 | ok( -d $tmp, "We created an output/garbage empty directory" ); 106 | 107 | # 108 | # Create garbage file 109 | # 110 | createFile( $tmp . "/output/garbage.png" ); 111 | ok( -e $tmp . "/output/garbage.png", "We created an output/garbage file" ); 112 | 113 | # 114 | # Generate everything 115 | # 116 | $site->build(); 117 | $site->copyAssets(); 118 | $site->sync(); 119 | 120 | ok( -e $tmp . "/output/index.html", "Page generated correctly" ); 121 | ok( -e $tmp . "/output/logo.png", "Asset copied successfully" ); 122 | ok( -e $tmp . "/output/.htaccess", "Asset copied successfully" ); 123 | ok( !-e $tmp . "/garbage.png", "Garbage file removed correctly" ); 124 | ok( !-e $tmp . "/garbage", "Garbage directory removed correctly" ); 125 | 126 | 127 | sub createFile 128 | { 129 | my ($file) = (@_); 130 | 131 | ok( !-e $file, "The file didn't exist prior to creation" ); 132 | 133 | note("File to be created is : $file"); 134 | 135 | open( my $handle, ">", $file ) or 136 | die "Failed to write: $file - $!"; 137 | print $handle "\n"; 138 | close($handle); 139 | 140 | ok( -e $file, "The file was created" ); 141 | } 142 | -------------------------------------------------------------------------------- /templer.cfg.sample: -------------------------------------------------------------------------------- 1 | ## 2 | ## This is the sample configuration file for a Templer-generated 3 | ## website. 4 | ## 5 | ## Templer is documented on the following github repository: 6 | ## 7 | ## https://github.com/skx/templer/ 8 | ## 9 | ## 10 | 11 | 12 | 13 | ## 14 | # 15 | # If you wish to execute commands prior to building the 16 | # site you may include an arbitrary number of keys called 17 | # pre-build. 18 | # 19 | # pre-build = echo Starting build at $(date) 20 | # pre-build = echo Running on $(hostname) 21 | # 22 | ## 23 | 24 | 25 | 26 | 27 | ## 28 | # 29 | # The first section of the configuration file refers to the 30 | # input and output paths. 31 | # 32 | # Templer will process all files matching "*.skx" beneath a 33 | # particular directory. That directory is the input directory. 34 | # 35 | input = ./input/ 36 | # 37 | ## 38 | 39 | 40 | 41 | 42 | ## 43 | # 44 | # Within the input directory we'll process files that match 45 | # a given suffix. 46 | # 47 | # By default this is ".skx", so we'll template-expand files 48 | # named "index.skx", "about.skx", etc. 49 | # 50 | # suffix = .skx 51 | # 52 | ## 53 | 54 | 55 | ## 56 | # 57 | # By default all pages will be written in HTML. 58 | # 59 | # If you have the appropriate depedencies installed you can instead 60 | # write your input pages in textile/markdown. Just add to the page 61 | # 62 | # Title: my title 63 | # Format: textile 64 | # ---- 65 | # ... your content here .. 66 | # 67 | # If all pages are going to be setup in one format you may prefer 68 | # to change this default 69 | # 70 | format = html 71 | # format = markdown 72 | # format = perl 73 | # format = textile 74 | # 75 | ## 76 | # 77 | 78 | 79 | ## 80 | # 81 | # If we're working in-place then files will be expanded where 82 | # they are found. 83 | # 84 | # This means that the following files will be created: 85 | # 86 | # ./input/index.skx -> input/index.html 87 | # ./input/foo/index.skx -> input/foo/index.html 88 | # .. 89 | # 90 | # 91 | # in-place = 1 92 | # 93 | ## 94 | 95 | 96 | ## 97 | # 98 | # If we're working synchronized then files in output must have a source file 99 | # in input or else they are removed 100 | # 101 | # sync = 1 102 | # 103 | ## 104 | 105 | 106 | ## 107 | # 108 | # The more common way of working is to produce the output in a separate 109 | # directory. 110 | # 111 | # NOTE: If you specify both "in-place=1" and an output directory the former 112 | # will take precedence. 113 | # 114 | # 115 | output = ./output/ 116 | # 117 | ## 118 | 119 | 120 | 121 | 122 | ## 123 | # 124 | # When pages are processed a layout-template will be used to expand the content 125 | # into. 126 | # 127 | # Each page may specify its own layout (and template-filter) if it so wishes, 128 | # but generally we'd expect only one layout to exist. 129 | # 130 | # Here we specify both the path to the layout directory and the layout to use 131 | # if none is specified: 132 | # 133 | # 134 | # layout-path = ./layouts/ 135 | # 136 | # layout = default.layout 137 | # 138 | # Layout template can be filtered in order to escape HTML::Template rigid 139 | # syntax which is annoying with some text-editor 140 | # 141 | # template-filter = dollar, strict 142 | # 143 | ## 144 | 145 | 146 | 147 | 148 | ## 149 | # 150 | # Templer supports plugins for expanding variable definitions 151 | # inside the input files, or for formating with text systems 152 | # like Textile, Markdown, etc. 153 | # 154 | # There are several plugins included with the system and you 155 | # can write your own in perl. Specify the path to load plugins 156 | # from here. 157 | # 158 | plugin-path = ./plugins/ 159 | # 160 | ## 161 | 162 | 163 | ## 164 | # 165 | # Templer supports including files via the 'read_file' function, along 166 | # with the built-in support that HTML::Template has for file inclusion 167 | # via: 168 | # 169 | # 170 | # 171 | # In both cases you may specify a search-path for file inclusion 172 | # via the include-path setting: 173 | # 174 | # include-path = include/:include/local/ 175 | # 176 | # Given the choice you should prefer the templer-provided file-inclusion 177 | # method over the HTML::Template facility, because this will force pages to 178 | # be rebuilt when the included-files are changed. 179 | # 180 | # Using a HTML::Template include-file you'll need to explicitly force a 181 | # rebuild if you modify an included file, but not the parent. 182 | # 183 | # 184 | include-path = ./includes 185 | # 186 | ## 187 | 188 | 189 | 190 | ## 191 | # 192 | # If you wish to execute commands after building the 193 | # site you may include an arbitrary number of keys called 194 | # post-build. 195 | # 196 | # post-build = echo Finished build at $(date) 197 | # 198 | ## 199 | 200 | 201 | 202 | # 203 | # Anything below this is a global variable, accessible by name in your 204 | # templates. 205 | # 206 | # For example this: 207 | # 208 | # copyright = Steve Kemp `date +%Y` 209 | # 210 | # Means you can use this in your layout, or your page text: 211 | # 212 | # 213 | # 214 | # 215 | # Notice that commands, surrounded in backticks (`) are executed and 216 | # their output is substituted inline in the way you might expect. 217 | # In this case we have the current year inserted into the copyright 218 | # string every time the site is rebuilt. 219 | # 220 | # 221 | 222 | --------------------------------------------------------------------------------