{"id":15018480,"url":"https://github.com/skx/templer","last_synced_at":"2025-07-07T18:33:15.242Z","repository":{"id":5742018,"uuid":"6954380","full_name":"skx/templer","owner":"skx","description":"A modular extensible static-site-generator written in perl.","archived":false,"fork":false,"pushed_at":"2022-07-01T05:36:19.000Z","size":690,"stargazers_count":63,"open_issues_count":0,"forks_count":14,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-06-05T19:49:25.217Z","etag":null,"topics":["perl","static-site-generator","templer"],"latest_commit_sha":null,"homepage":"","language":"Perl","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/skx.png","metadata":{"files":{"readme":"README.md","changelog":"ChangeLog","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2012-12-01T13:21:43.000Z","updated_at":"2024-10-17T20:55:15.000Z","dependencies_parsed_at":"2022-08-30T19:20:31.888Z","dependency_job_id":null,"html_url":"https://github.com/skx/templer","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/skx/templer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Ftempler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Ftempler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Ftempler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Ftempler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/skx","download_url":"https://codeload.github.com/skx/templer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/skx%2Ftempler/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264130512,"owners_count":23562041,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["perl","static-site-generator","templer"],"created_at":"2024-09-24T19:52:01.052Z","updated_at":"2025-07-07T18:33:15.114Z","avatar_url":"https://github.com/skx.png","language":"Perl","funding_links":[],"categories":[],"sub_categories":[],"readme":"Templer\n=======\n\nTempler is yet another static site generator, written in Perl.\n\nIt makes use of the\n[HTML::Template](http://search.cpan.org/perldoc?HTML%3A%3ATemplate) module for\nperforming variable expansion within pages and layouts, along with looping and\nconditional-statement handling.\n\nTempler has evolved over time for my own personal use, but I believe\nit is sufficiently generic it could be useful to others.\n\nMy motivation for putting it together came from the desire to change\nseveral hand-made, HTML-coded, sites to something more maintainable such\nthat I could easily change the layout in one place.\n\nThe design evolved over time but the key reason for keeping it around\nis that it differs from many other simple static-generators in several\nways:\n\n* You may define global variables for use in your pages/layouts.\n* A page may define and use page-specific variables.\n* You may change the layout on a per-page basis if you so wish.\n    * This was something that is missing from a lot of competing tools.\n* Conditional variable expansion is supported, via `HTML::Template`.\n* File contents, shell commands, and file-globs may be used in the templates\n    * This allows the trivial creation of galleries, for example.\n    * These are implemented via plugins.\n    * Plugins are documented in the file [PLUGINS.md](PLUGINS.md).\n* You may also embed perl code in your pages.\n\nAnother key point is that the layouts allow for more than a single\nsimple \"content\" block to be placed into them - you can add arbitrary\nnumbers of optional side-menus, for example.\n\nAlthough this tool was written and used with the intent you'd write your\nsite-content in HTML you can write your input pages in Textile or Markdown\nif you prefer (these inputs are supported via [plugins](PLUGINS.md)).\n\n\nConcepts\n--------\n\nA templer site comprises of three things:\n\n* A global configuration file.\n    * This defines the paths to search for input pages, layout templates, plugins, etc.\n    * This may contain global variable declarations.\n    * Please see the example configuration file:\n      [`templer.cfg`](https://raw.github.com/skx/templer/master/templer.cfg.sample).\n* A layout.\n    * This is used to format the output pages, defining the common look and feel.\n* A series of pages \u0026 assets.\n    * Pages have their content processed and inserted into the layout to produce output HTML.\n    * Assets are not processed, but are copied into the output directory literally.\n\nIn general we assume there is a tree like so:\n\n    ├── input\n    │   ├── index.wgn\n    │   ├── ...\n    │   ├── ...\n    │   ├── favicon.ico\n    │   └── robots.txt\n    ├── layouts\n    │   └── default.layout\n    ├── output\n    └── templer.cfg\n\nEvery file in the input directory is either considered to be a page which is converted\ninto HTML, or an asset which is copied into the output-tree with no changes made.\n\nIn the example above `input/index.wgn` would become `output/index.html`.\n\n\u003e **NOTE** The `.wgn` suffix is an example. You can define which suffix is considered a page via the configuration file.\n\nThere is _also_ an \"in-place\" mode.  When working in-place there is no\ndistinct output directory, instead output is written to the same directory in\nwhich is encountered.  Given an input directory you might see this kind of\ntransformation:\n\n    index.wgn           -\u003e index.html\n    about.wgn           -\u003e about.html\n    favicon.ico         [Ignored and left un-modified.]\n    robots.txt          [Ignored and left un-modified.]\n    ..\n\nThere is _also_ a *synchronized* mode. When working synchronized any file in\nthe output directory which do not have a source file in input directory (page\nor asset) is removed each time the site is rebuild.\n\n\nPages\n-----\n\nYour site will be made of pages, which are snippets of HTML you write.  These\nsnippets will be processed and inserted into the layout file before being output\nto disk.\n\nA page is a simple file which contains a header and some content.  This is\na sample page:\n\n    Title:  This is the title page.\n    ----\n    \u003cp\u003eThis is the body of the page\u003c/p\u003e\n\n\nThe header of the page is delimited from the body by four dashes (`----`) and\ncan contain an arbitrary number of variable definitions.\n\n\nSpecial Page Variables\n-----------------------\n\nIn your page you can define, and refer to, an arbitrary number of variables\nbut some names are reserved - and any variable with one of those names will\nbe treated specially:\n\nThe special variable `layout` may be used to specify a different layout\ntemplate for the current page. If there is no per-page layout specified then\nthe global layout declared in the `templer.cfg` file will be used.\n\nThe special variable `template-filter` may be used to specify some filters to\napply on the used layout in order to transform it into valid `HTML::Template`\nfile. If there is no per-page layout filter specified then the global layout\ndeclared in the `templer.cfg` file will be used.\n\nThe special variable `output` may be used to specify an alternative output\nfile.  For example the input file `index.wgn` would normally become\n`index.html`, but you could make it become something else.\n\nThe special variable `format` may be given a value of `textile`, `markdown`, or\n`perl` to enable processing the page body with the appropriate filter.   These\nformatters are implemented as [plugins](PLUGINS.md), and will be available\nassuming their [dependencies are installed](#installation).\n\nTextile and markdown are well-known, and allow you to write your page content naturally.  The perl-formatter is used to allow you to write dynamic content in Perl in your page-body, via the [Text::Template](http://search.cpan.org/perldoc?Text%3A%3ATemplate) module.   Perl code to be executed is wrapped in `{` and `}` characters.  Here is a sample page:\n\n    Title: This page has code in it\n    format: perl\n    ----\n\n    \u003cp\u003eThis page has some code in it.\u003c/p\u003e\n    \u003cp\u003eI am running on { `hostname` }...\u003c/p\u003e\n    \u003cp\u003eI am {\n           my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) =\n                                                               localtime(time);\n           $year += 1900;\n           $year - 1976;\n       } years old.\u003c/p\u003e\n\n\u003e **NOTE**:  Formatters may be chained.  For example \"`format: perl, markdown`\".\n\n\nVariable Definitions\n--------------------\n\nWithin the header of each page you may declare an arbitrary number of per-page\nvariables.  These variable declarations are then available for use within the\npage-body, using the standard  [HTML::Template](http://search.cpan.org/perldoc?HTML%3A%3ATemplate) expansion facilities:\n\n\n    Title:  Page title\n    Name: Steve Kemp\n    ----\n    \u003cp\u003eHello, my name is \u003c!-- tmpl_var name='name' --\u003e.\u003c/p\u003e\n\n\u003e **NOTE**: All variable-names are transformed to lower-case for consistency, which is why we refer to the variable `name` rather than the defined `Name`.\n\nAs well as simple \"name: value\" pairs there are also additional options implemented in [plugins](PLUGINS.md);\n\n* A variable may refer to the contents of a given file.\n    * Using `read_file`.\n* A variable may refer to a list of filenames, matching a pattern.\n    * Using `file_glob`.\n* A variable may contain the output of running a command.\n    * Using `run_command`.\n* A variable may be based on the timestamp of the input page.\n    * Using `timestamp`.\n* A variable may contain the contents of a remote RSS feed.\n    * Using `rss(count, URL)`.\n* A variable may contain the result of a key-lookup from a Redis server.\n    * Using `redis_get('foo')`.\n\nIn addition to declaring variables in a page-header you may also declare\n__global__ variables in your `templer.cfg` file, or upon the command-line\nvia `--define foo=bar`.\n\nDefining global variables is demonstrated in the sample [`templer.cfg`](https://raw.github.com/skx/templer/master/templer.cfg.sample) file.\n\n\nFile Globbing Variables\n-----------------------\n\nWe've already seen simple variables declared by `key: value` in the page header,\nin addition to this you may define a variable that refers to a number of files\nby pattern.\n\nHere is a simple example of creating a gallery which will include files matching\nthe pattern `img/*.jpg`:\n\n    Title: My gallery\n    Images: file_glob( \"img/*.jpg\" )\n    ---\n    \u003c!-- tmpl_if name='images' --\u003e\n      \u003c!-- tmpl_loop name='images' --\u003e\n       \u003cp\u003e\u003cimg src=\"\u003c!-- tmpl_var name='file' --\u003e\"\n               height=\"\u003c!-- tmpl_var name='height' --\u003e\"\n               width=\"\u003c!-- tmpl_var name='width' --\u003e\" /\u003e \u003c/p\u003e\n      \u003c!-- /tmpl_loop --\u003e\n    \u003c!-- tmpl_else --\u003e\n      \u003cp\u003eNo images were found.\u003c/p\u003e\n    \u003c!-- /tmpl_if --\u003e\n\n\u003e **TIP**:  If your images are numbered numerically you can ensure their correct order by doing this:\n\n    Title:  This is my title\n    images: file_glob( img/[0-9].jpg img/1[0-9].jpg )\n    ----\n    \u003cp\u003eMy gallery is now included in ascending numerical order:\u003c/p\u003e\n\n\nThis facility is implemented in the `Templer::Plugin::FileGlob` [plugin](PLUGINS.md).\n\nThe file glob is primarily designed for handling image-galleries, which is why it will set the `height` and `width` attributes if your glob matches `*.jpg`, `*.png`, etc.  However it can also be used for non-images.\n\nIf your glob matches files which are not images it will populate the member `content`, being the text-content of the matching files.  This allows you to include files easily.  For example:\n\n\n    Title: This is my news-page\n    news: file_glob( news-*.txt )\n    ----\n    \u003cp\u003eHere are the recent events:\u003c/p\u003e\n    \u003c!-- tmpl_loop name='news' --\u003e\n    \u003cp\u003e\u003c!-- tmpl_var name='content' --\u003e\u003c/p\u003e\n    \u003c!-- /tmpl_loop --\u003e\n\nThis assumes you have files such as `news-20130912.txt`, etc, and will show the contents of each file in (glob)order.\u003c/p\u003e\n\nIf matching files are templer input files then all templer variables are populated instead of the text-content of the matching files.\n\nIn all cases it will populate `dirname`, `basename` and `extension`, being parts of each matching files name.\n\n\nFile Inclusion\n--------------\n\nThe [HTML::Template](http://search.cpan.org/perldoc?HTML%3A%3ATemplate) module supports file inclusion natively, via the following construct:\n\n    \u003cp\u003eThis is some text.\u003c/p\u003e\n    \u003c!-- tmpl_include name='/etc/passwd' --\u003e\n    \u003cp\u003eThat was my password file.\u003c/p\u003e\n\nIn addition to this you may define a variable to contain the contents of a specified file.  For example:\n\n    Title: This file has my passwords!\n    Passwd: read_file( \"/etc/passwd\" )\n    ----\n    \u003cp\u003ePlease see my passwords:\u003c/p\u003e\n    \u003cpre\u003e\u003c!-- tmpl_var name='passwd' --\u003e\n    \u003c/pre\u003e\n\nThis facility is implemented in the `Templer::Plugin::FileContents` [plugin](PLUGINS.md).\n\nInclude files, whether included via the `read_file` method, or via the native HTML::Template faclity, are searched for in the same fashion:\n\n* If the filename is fully-qualified, then the absolute path-name will be used.\n* Otherwise the include-path will be searched.\n* After the include-path has been searched the file will be looked for in the location relative to the input page location.\n\nThis allows you to place all your include-files in a single directory which is outside your web-root.\n\n\u003e **TIP**: The advantage of choosing to use `read_file` over the native HTML::Template support is that with the former the output page will be automatically rebuilt if you modify the include file.\n\n\nShell Command Execution\n-----------------------\n\nPages may also define variables which receive the value of the output of shell commands.  This is done via definitions like this:\n\n\n    Title: This file is dynamic\n    Host: run_command( \"hostname\" )\n    ----\n    \u003cp\u003eThis page was built upon \u003c!-- tmpl_var name='host' --\u003e.\u003c/p\u003e\n\nThis facility is implemented in the `Templer::Plugin::ShellCommand` [plugin](PLUGINS.md).\n\n\nRemote RSS Feeds\n----------------\n\nPages may use snippets of RSS feeds, limiting them to the given\nnumber of entries.  For example:\n\n    title: About my site\n    feed: rss(4, http://blog.steve.org.uk/index.rss )\n    ----\n    \u003cp\u003eThis page is about my site, here are my recent blog posts:\u003c/p\u003e\n    \u003cul\u003e\n    \u003c!-- tmpl_loop name='feed' --\u003e\n        \u003cli\u003e\u003ca href=\"\u003c!-- tmpl_var name='link' --\u003e\"\u003e\u003c!-- tmpl_var name='title' --\u003e\u003c/a\u003e\u003c/li\u003e\n    \u003c!-- /tmpl_loop --\u003e\n    \u003c/ul\u003e\n\n\nRedis Lookups\n-------------\n\nIf you have a redis-server running upon the local system you may\nconfigure page-variables to retrieve their values via lookups against it.\n\nFor example:\n\n    title: Site Statistics\n    count: redis_get( \"global_count\" )\n    ----\n    \u003cp\u003eThere are \u003c!-- tmpL-var name='count' --\u003e entries.\u003c/p\u003e\n\n\nInstallation\n------------\n\nTempler is developed in a public fashion, here on github, but stable releases\nare also uploaded to CPAN:\n\n* [App::Templer](http://search.cpan.org/dist/App-Templer/)\n\nInstallation should be as simple as any other CPAN-based module:\n\n    $ git clone https://github.com/skx/templer.git\n    $ cd templer\n    $ perl Makefile.PL\n    $ make test\n    $ sudo make install\n\n(If you ever wish to remove the software you may run `sudo make uninstall`.)\n\nThe code makes use of a reasonably large number of modules for its\nimplementation, and you can see a brief overview of [the logical structure](#object-hierarchy) later.)\n\nIf you want to get a standalone `templer` executable which includes all those `Templer` modules, you can use the `standalone` target of the generated `Makefile`:\n\n    $ make standalone\n\nThis will produce a script called `templer` in the base directory so that you should be able to use it alone without copying these used modules anywhere in perl library directories.\n\nThe dependencies are minimal, to ease installation:\n\n* Perl\n* The [Module::Pluggable](http://search.cpan.org/perldoc?Module%3A%3APluggable) module for loading plugins.\n* The [HTML::Template](http://search.cpan.org/perldoc?HTML%3A%3ATemplate) module.\n    *  This may be installed, on a Debian system, with `apt-get install libhtml-template-perl`.\n* The following are optional modules:\n    * The [Image::Size](http://search.cpan.org/perldoc?Image%3A%3ASize) module is used if available whenever you create `file_glob`-using loops of image files.\n        * This will set the attributes `width` and `height` any images added via `file_glob`.\n    * The [Text::Markdown](http://search.cpan.org/perldoc?Text%3A%3AMarkdown) module is required if you wish to write your page bodies in Markdown.\n        *  This may be installed, on a Debian system, with `apt-get install libtext-markdown-perl`.\n    * The [Text::Textile](http://search.cpan.org/perldoc?Text%3A%3ATextile) module is required if you wish to write your page bodies in Textile.\n        *  This may be installed, on a Debian system, with `apt-get install libtext-textile-perl`.\n    * The [Text::Template](http://search.cpan.org/perldoc?Text%3A%3ATemplate) module is required if you wish to include dynamic perl in your input pages.\n        *  This may be installed, on a Debian system, with `apt-get install libtext-template-perl`.\n    * The [Redis](http://search.cpan.org/perldoc?Redis) module is required if you wish to use the Redis plugin.\n        *  This may be installed, on a Debian system, with `apt-get install libredis-perl`.\n    * The [XML::Feed](http://search.cpan.org/perldoc?XML%3A%3AFeed) module is required if you wish to use the RSS plugin.\n        *  This may be installed, on a Debian system, with `apt-get install libxml-feed-perl`.\n\nIf you prefer you can install Debian binary packages from my personal repository:\n\n* [templer repository for Debian GNU/Linux](http://packages.steve.org.uk/templer/)\n\n\nCreating a new site\n-------------------\n\nThere is a supplied script `templer-generate` which will create a new site-structure\nif you give in the name of a directory to create \u0026 write to:\n\n    ~$ templer-generate my-site\n    ~$ tree my-site/\n    my-site/\n    ├── include\n    ├── input\n    │   ├── about.wgn\n    │   ├── index.wgn\n    │   └── robots.txt\n    ├── layouts\n    │   └── default.layout\n    ├── output\n    └── templer.cfg\n\nIf you prefer you may go through the process manually creating a directory,\nadding the [`templer.cfg`](https://raw.github.com/skx/templer/master/templer.cfg.sample)\nto it, and then creating the input tree and layout directory.\n\nThere are several [examples](examples/) provided with the distribution to illustrate the\nsoftware.  These example sites are built automatically every evening and\nuploaded online - so you may easily compare the input and the generated\noutput:\n\n* [simple example source](https://github.com/skx/templer/tree/master/examples/simple)\n    * The online [generated output](http://www.steve.org.uk/Software/templer/examples/simple/output/).\n* [complex example source](https://github.com/skx/templer/tree/master/examples/complex)\n    * The online [generated output](http://www.steve.org.uk/Software/templer/examples/complex/output/).\n    * The generated \"complex\" example is designed to be a standalone introduction to templer.\n\n\nRebuilding a site\n-----------------\n\nIf you're beneath the directory containing your `templer.cfg` file simply\nrun `templer` with no arguments.  You may optionally add flags to control\nwhat happens:\n\n* `templer --verbose`\n    * To see more details of what is happening.\n* `templer --force`\n    * To force a rebuild of the site.\n* `templer --define foo=bar`\n    * Define the variable `foo` for use in your templates.  This will over-ride any setting of foo in the configuration file you've loaded.\n\nIn the general case `templer` should rebuild only the files which are needed\nto be built.  A page will be rebuilt if:\n\n* The page source is edited.\n* The layout the page uses is edited.\n* Any include-file the page includes is edited.\n    * This applies to those includes read via [read_file](#file-inclusion) rather than via `HTML::Template` includes\n\n\u003e Previously it was required that you run `templer` from the top-level of your site, this has now changed.  `templer` will walk upwards from the current working directory and attempt to find the site-root by itself.\n\n\nObject Hierarchy\n----------------\n\nAlthough `templer` is distributed and used as a single script it is written\nusing a series of objects.  Bundling into a single binary allows for easier\ndistribution, installation and usage.\n\nIn brief the control flow goes like this:\n\n* `templer` runs, parses the command line, etc.\n* A `Templer::Global` object is created to read the `templer.cfg` file, or the file passed via `--config=foo`.\n* The options from the command-line and the config file are merged.\n* From this point onwards `Templer::Global` is ignored.\n* A `Templer::Site` object is created, using the merged config values.\n* A `Templer::Timer` object is created to record the build-time.\n* The build process is contained in `Templer::Site::build()`:\n    * A `Templer::Plugin::Factory` object is created to load plugins.\n    * A `Templer::Site::Page` object is created for each appropriate input.\n    * Each page is output.\n    * The plugins are unloaded.\n* The assets are copied via `Templer::Site::copyAssets()`.\n* The output directory is cleaned via `Templer::Site::sync()`.\n* The build-time/build-count is reported and the process is complete.\n\nEach of the modules has a simple test-case associated with it.  To test functionality, especially after making changes, please run the test-suite:\n\n    $ make test\n    prove --shuffle t/\n    t/style-no-tabs.t ........................... ok\n    t/test-dependencies.t ....................... ok\n    ..\n    ..\n    t/test-templer-plugin-filecontents.t ........ ok\n    t/test-templer-site.t ....................... ok\n    t/test-templer-plugin-timestamp.t ........... ok\n    All tests successful.\n    Files=15, Tests=286,  1 wallclock secs ( 0.11 usr  0.01 sys +  0.88 cusr  0.14 csys =  1.14 CPU)\n    Result: PASS\n\nAny test-case failure is a bug, and should be reported as such.\n\n\nProblems\n--------\n\nPlease report any issue via the github repository:\n\n* https://github.com/skx/templer\n\n\nAuthor\n------\n\nSteve Kemp \u003csteve@steve.org.uk\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskx%2Ftempler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskx%2Ftempler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskx%2Ftempler/lists"}