2022-06-28 09:25:36 +00:00
|
|
|
#!/usr/bin/perl
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
2022-07-03 21:14:58 +00:00
|
|
|
site-generator - HPR Site Generator
|
2022-06-28 09:25:36 +00:00
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
2022-07-29 02:04:15 +00:00
|
|
|
site-generator [OPTION]... PAGE|PAGE=<comma separated list of ids>...
|
2022-06-28 09:25:36 +00:00
|
|
|
|
2022-08-20 02:11:38 +00:00
|
|
|
-a, --all generate all pages defined in configuration file
|
2022-08-09 01:30:22 +00:00
|
|
|
-l, --list print list of configured pages
|
2022-07-03 21:14:58 +00:00
|
|
|
-p, --preview print generated pages to standard out
|
2022-08-08 01:49:10 +00:00
|
|
|
-q, --quiet suppress progress information while generating pages
|
|
|
|
-v, --verbose print extended progress information while generating pages
|
2022-06-28 09:25:36 +00:00
|
|
|
--help print this help message
|
|
|
|
|
2022-07-02 02:02:25 +00:00
|
|
|
Where I<PAGE> is a file name of a web page
|
2022-07-29 02:04:15 +00:00
|
|
|
or the special I<ALL> (to generate all pages).
|
2022-06-28 09:25:36 +00:00
|
|
|
|
|
|
|
Examples:
|
|
|
|
|
|
|
|
Generate two specific pages:
|
2022-07-02 16:52:50 +00:00
|
|
|
site-generator index about
|
2022-06-28 09:25:36 +00:00
|
|
|
|
|
|
|
Generate the whole site:
|
2022-08-20 02:11:38 +00:00
|
|
|
site-generator --all
|
2022-06-28 09:25:36 +00:00
|
|
|
|
2022-07-29 02:04:15 +00:00
|
|
|
Generate pages based on the same template:
|
|
|
|
site-generator correspondent=1,3,5..10
|
|
|
|
|
2022-06-28 09:25:36 +00:00
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
2022-07-02 16:52:50 +00:00
|
|
|
This is a site generator for the Hacker Public Radio website based upon the Perl Templates Toolkit.
|
|
|
|
|
|
|
|
=head1 INSTALLATION
|
|
|
|
|
2022-10-23 01:11:57 +00:00
|
|
|
With SQLite
|
2023-03-04 04:53:21 +00:00
|
|
|
* Create the sqlite3 database from the hpr.sql MySQL dump file available on
|
|
|
|
hackerpublicradio.org. The default name for the database file is "hpr.db"
|
|
|
|
and should be located in the root of the project directory. The name and
|
|
|
|
location can be set in the site.cfg file.
|
|
|
|
* An "update-hpr.sh" helper script is available in the utils directory. This
|
|
|
|
script will download the hpr.sql file, convert it to the SQLite hpr.db file,
|
|
|
|
and regenerate the website using the site-generator.
|
|
|
|
1. `cd` into the root of the project directory
|
|
|
|
2. Run `./utils/update-hpr.sh`
|
2022-10-23 01:11:57 +00:00
|
|
|
* SQLite v3.8.3 or greater is recommended. CTE WITH clauses are used in some template queries.
|
|
|
|
Must convert WITH clauses to sub-queries when using earlier versions of SQLite.
|
|
|
|
|
|
|
|
With MySQL
|
|
|
|
* Create database hpr_hpr in the MySQL server from HPR dump file.
|
|
|
|
- sudo mysql --host=localhost < hpr.sql
|
|
|
|
* Create a user that will be used by the site-generator.
|
|
|
|
- Suggested username: hpr-generator
|
|
|
|
- CREATE USER 'hpr-generator'@'localhost' IDENTIFIED BY '<password>';
|
|
|
|
* Limit the user's privileges to EXECUTE and SELECT
|
|
|
|
- GRANT SELECT ON hpr_hpr.* TO 'hpr-generator'@'localhost';
|
|
|
|
- GRANT EXECUTE ON `hpr_hpr`.* TO 'hpr-generator'@'localhost';
|
|
|
|
|
|
|
|
Install the needed Perl modules using preferred method (distribution packages, CPAN, etc.)
|
2022-07-02 16:52:50 +00:00
|
|
|
* GetOpt
|
|
|
|
* Pod::Usage
|
|
|
|
* Config::Std
|
|
|
|
* Template
|
2022-08-04 23:09:32 +00:00
|
|
|
* Template::Plugin::File
|
|
|
|
* Template::Plugin::DBI
|
2022-07-02 16:52:50 +00:00
|
|
|
* DBI
|
2022-08-02 16:44:54 +00:00
|
|
|
* Tie::DBI
|
2022-10-23 01:11:57 +00:00
|
|
|
* DBD::SQLite or DBD:mysql
|
2022-07-18 22:25:25 +00:00
|
|
|
* Date::Calc
|
2022-06-28 09:25:36 +00:00
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
2022-07-03 21:14:58 +00:00
|
|
|
Roan Horning <roan.horning@no-spam.gmail.com>
|
2022-06-28 09:25:36 +00:00
|
|
|
|
2022-07-14 03:16:44 +00:00
|
|
|
=head1 LICENSE
|
|
|
|
|
|
|
|
site-generator -- a static website generator for HPR
|
|
|
|
Copyright (C) 2022 Roan Horning
|
|
|
|
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU Affero General Public License as published by
|
|
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
|
|
(at your option) any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU Affero General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Affero General Public License
|
|
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
2022-06-28 09:25:36 +00:00
|
|
|
=cut
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Getopt::Long qw(:config auto_help);
|
|
|
|
use Pod::Usage;
|
|
|
|
use Config::Std;
|
2022-06-28 14:46:52 +00:00
|
|
|
use Template;
|
2022-07-03 21:14:58 +00:00
|
|
|
use Data::Dumper;
|
2022-06-28 09:25:36 +00:00
|
|
|
|
|
|
|
exit main();
|
|
|
|
|
|
|
|
sub main {
|
|
|
|
|
2022-07-03 21:14:58 +00:00
|
|
|
# Argument parsing
|
2022-08-20 02:11:38 +00:00
|
|
|
my $all;
|
2022-07-03 21:14:58 +00:00
|
|
|
my $preview;
|
|
|
|
my $verbose;
|
2022-08-08 01:49:10 +00:00
|
|
|
my $quiet;
|
2022-07-03 21:14:58 +00:00
|
|
|
GetOptions(
|
2022-08-20 02:11:38 +00:00
|
|
|
'all' => \$all,
|
2022-08-09 01:30:22 +00:00
|
|
|
'list' => \&print_available_pages,
|
2022-07-03 21:14:58 +00:00
|
|
|
'preview' => \$preview,
|
2022-08-08 01:49:10 +00:00
|
|
|
'verbose' => \$verbose,
|
|
|
|
'quiet' => \$quiet,
|
2022-07-03 21:14:58 +00:00
|
|
|
) or pod2usage(1);
|
2022-08-20 02:11:38 +00:00
|
|
|
pod2usage(1) unless @ARGV || $all;
|
2022-07-29 01:59:38 +00:00
|
|
|
my (@page_args) = @ARGV;
|
2022-06-28 09:25:36 +00:00
|
|
|
|
2022-08-08 01:49:10 +00:00
|
|
|
if ($quiet) {
|
|
|
|
$verbose = 'quiet';
|
|
|
|
};
|
|
|
|
|
2022-06-28 09:25:36 +00:00
|
|
|
# Load config file
|
|
|
|
read_config "site.cfg" => my %config;
|
|
|
|
|
2022-06-29 14:42:10 +00:00
|
|
|
my $tt = get_template_html($config{DBI});
|
2022-06-28 09:25:36 +00:00
|
|
|
|
2022-08-20 02:11:38 +00:00
|
|
|
# If command line option all is set, parse configuration file
|
|
|
|
# for all pages
|
|
|
|
if ($all) {
|
2022-07-29 01:59:38 +00:00
|
|
|
@page_args = keys %config;
|
2022-07-02 02:02:25 +00:00
|
|
|
|
|
|
|
# Remove non page sections of the configuration file
|
|
|
|
# from the generated list of pages.
|
2022-07-29 02:04:15 +00:00
|
|
|
@page_args= grep { $_ ne 'DBI' } @page_args;
|
2022-07-29 01:59:38 +00:00
|
|
|
@page_args= grep { $_ ne 'root_template' } @page_args;
|
2022-09-18 20:18:05 +00:00
|
|
|
@page_args= grep { $_ ne 'media_hostname' } @page_args;
|
|
|
|
|
2022-07-02 02:02:25 +00:00
|
|
|
};
|
2022-07-29 01:59:38 +00:00
|
|
|
foreach my $page_arg (@page_args) {
|
2022-07-29 02:04:15 +00:00
|
|
|
my %parsed_arg = parse_page_arg($page_arg);
|
|
|
|
if (exists($config{$parsed_arg{'page'}})) {
|
|
|
|
my $page_config = $config{$parsed_arg{'page'}};
|
|
|
|
$page_config->{'page'} = $parsed_arg{'page'};
|
2022-08-17 23:13:42 +00:00
|
|
|
|
|
|
|
# Set page's root_template to the default root_template if the
|
|
|
|
# page root_template property is not set in the configuration file.
|
|
|
|
if (exists $page_config->{'root_template'} == 0) {
|
|
|
|
$page_config->{'root_template'} = $config{root_template}{content};
|
|
|
|
}
|
|
|
|
|
2022-10-01 17:33:59 +00:00
|
|
|
# Set all config root_template properties as default page config properties
|
|
|
|
# except the previously set root_template content property
|
|
|
|
my @root_args = grep { $_ ne 'content' } keys %{$config{root_template}};
|
|
|
|
foreach my $root_arg (@root_args) {
|
|
|
|
if (exists $page_config->{$root_arg} == 0) {
|
|
|
|
$page_config->{$root_arg} = $config{root_template}{$root_arg};
|
|
|
|
}
|
2022-09-18 20:18:05 +00:00
|
|
|
}
|
|
|
|
|
2022-07-29 02:04:15 +00:00
|
|
|
if ($page_config->{'multipage'} && $page_config->{'multipage'} eq 'true') {
|
|
|
|
if (scalar @{$parsed_arg{'ids'}} == 1) {
|
|
|
|
@{$parsed_arg{'ids'}} = get_ids_from_db($tt, \$page_config);
|
|
|
|
}
|
|
|
|
foreach my $id (@{$parsed_arg{'ids'}}) {
|
2022-07-29 01:59:38 +00:00
|
|
|
$page_config->{'id'} = $id;
|
2022-07-29 02:04:15 +00:00
|
|
|
verbose ($verbose, "Generating page: $page_config->{'page'} with id: $id");
|
2022-07-31 23:25:56 +00:00
|
|
|
generate_page($tt, \$page_config, $preview);
|
2022-07-22 03:58:30 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2022-07-29 01:59:38 +00:00
|
|
|
verbose ($verbose, "Generating page: $page_config->{'page'}");
|
2022-07-31 23:25:56 +00:00
|
|
|
generate_page($tt, \$page_config, $preview);
|
2022-07-22 03:58:30 +00:00
|
|
|
}
|
2022-07-02 02:02:25 +00:00
|
|
|
}
|
|
|
|
else {
|
2022-07-29 02:04:15 +00:00
|
|
|
verbose (1, "\nWarning: Page $parsed_arg{'page'} is not defined in the configuration file.");
|
2022-07-02 02:02:25 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
verbose (1, "\nFinished processing the files.");
|
2022-06-28 09:25:36 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2022-06-29 14:42:10 +00:00
|
|
|
sub get_template_html (\%@) {
|
2022-06-28 14:46:52 +00:00
|
|
|
# For an HTML based Template file, define the
|
|
|
|
# template start and end tags to also function as
|
|
|
|
# HTML comments to make the template file valid HTML.
|
|
|
|
#
|
|
|
|
return Template->new({
|
2022-07-03 21:14:58 +00:00
|
|
|
INCLUDE_PATH => './templates',
|
|
|
|
OUTPUT_PATH => './public_html',
|
|
|
|
EVAL_PERL => 1,
|
|
|
|
START_TAG => '<!--%',
|
|
|
|
END_TAG => '%-->',
|
2022-07-18 01:16:42 +00:00
|
|
|
PRE_CHOMP => 1,
|
|
|
|
POST_CHOMP => 1,
|
2022-07-03 21:14:58 +00:00
|
|
|
CONSTANTS => {
|
2022-11-27 18:11:14 +00:00
|
|
|
database => $_[0]{database},
|
2022-07-03 21:14:58 +00:00
|
|
|
driver => $_[0]{driver},
|
|
|
|
user => $_[0]{user},
|
|
|
|
password => $_[0]{password},
|
|
|
|
}
|
|
|
|
}) || die $Template::ERROR, "\n";
|
2022-06-28 14:46:52 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-06-29 15:03:32 +00:00
|
|
|
sub generate_page {
|
2022-07-31 23:25:56 +00:00
|
|
|
my ($tt, $config, $preview) = @_;
|
2022-07-03 21:14:58 +00:00
|
|
|
my $html;
|
|
|
|
if (!$preview) {
|
2022-07-29 01:59:38 +00:00
|
|
|
$html = get_filename($$config);
|
2022-07-03 21:14:58 +00:00
|
|
|
}
|
2022-07-31 23:25:56 +00:00
|
|
|
$tt->process($$config->{root_template}, $$config, $html)
|
2022-07-03 21:14:58 +00:00
|
|
|
|| die $tt->error(), "\n";
|
2022-06-28 14:46:52 +00:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2022-07-02 02:02:25 +00:00
|
|
|
sub verbose {
|
|
|
|
my ($verbose, $message) = @_;
|
|
|
|
if ($verbose) {
|
2022-08-08 01:49:10 +00:00
|
|
|
if ($verbose ne 'quiet') {
|
|
|
|
print STDOUT "$message\n";
|
|
|
|
}
|
2022-07-02 02:02:25 +00:00
|
|
|
}
|
|
|
|
else {
|
2022-07-31 22:50:34 +00:00
|
|
|
STDOUT->autoflush(1);
|
|
|
|
print STDOUT ".";
|
2022-07-02 02:02:25 +00:00
|
|
|
};
|
|
|
|
}
|
2022-07-22 03:58:30 +00:00
|
|
|
|
2022-07-29 01:59:38 +00:00
|
|
|
sub parse_page_arg {
|
|
|
|
my ($page_arg) = @_;
|
2022-07-22 03:58:30 +00:00
|
|
|
# Split page name from page ids if available.
|
2022-07-29 01:59:38 +00:00
|
|
|
my ($page, $ids) = split(/=/, $page_arg);
|
2022-07-22 03:58:30 +00:00
|
|
|
my @ids = [];
|
|
|
|
|
|
|
|
if(!$ids) {
|
|
|
|
$ids = "";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
# Parse the page ids and push them onto @ids array
|
|
|
|
my @ids_by_comma = split(/\,/, $ids);
|
|
|
|
foreach my $id_by_comma (@ids_by_comma) {
|
|
|
|
my @ids_for_range = split(/\.\./, $id_by_comma);
|
|
|
|
if ((scalar @ids_for_range) == 2) {
|
|
|
|
push @ids, $ids_for_range[0]..$ids_for_range[1];
|
|
|
|
}
|
|
|
|
elsif ((scalar @ids_for_range) == 1) {
|
|
|
|
push @ids, $ids_for_range[0];
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
verbose (1, "\nWarning: Page $page id range $id_by_comma could not be parsed.");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-07-29 02:04:15 +00:00
|
|
|
return ('page' => $page, 'ids' => [@ids]);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub get_ids_from_db {
|
|
|
|
# Use a template to generate a string of page identifiers.
|
|
|
|
# The template should return the string in the form of
|
|
|
|
# <comma><identifier><comma><identifier>...
|
|
|
|
#
|
|
|
|
my ($tt, $config) = @_;
|
|
|
|
my $selected_ids = "";
|
|
|
|
my $id_template = "ids-$$config->{'page'}.tpl.html";
|
|
|
|
|
|
|
|
$tt->process($id_template, $$config, \$selected_ids)
|
|
|
|
|| die $tt->error(), "\n";
|
|
|
|
|
|
|
|
return split(/,/, substr($selected_ids, 1));
|
2022-07-22 03:58:30 +00:00
|
|
|
}
|
|
|
|
|
2022-07-29 01:59:38 +00:00
|
|
|
sub get_filename {
|
|
|
|
my ($config) = @_;
|
|
|
|
my $filename = "output.html";
|
|
|
|
my $base_path = "";
|
|
|
|
|
|
|
|
if ($$config{'filename'}) {
|
|
|
|
if (substr($$config{'filename'}, -1) eq '/') {
|
|
|
|
$base_path = $$config{'filename'};
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$filename = $$config{'filename'};
|
2022-07-31 19:14:03 +00:00
|
|
|
my $padded_index = "";
|
2022-08-20 01:25:14 +00:00
|
|
|
if (exists $$config{'id'}) {
|
2022-07-31 19:14:03 +00:00
|
|
|
$padded_index = sprintf("%04d", $$config{'id'});
|
|
|
|
}
|
2022-07-29 01:59:38 +00:00
|
|
|
$filename =~ s/\[id\]/$padded_index/;
|
|
|
|
return $filename;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
# Default naming if full filename configuration is not supplied.
|
|
|
|
if ($$config{'multipage'} && $$config{'multipage'} eq 'true') {
|
|
|
|
my $padded_index = sprintf("%04d", $$config{'id'});
|
|
|
|
$filename = "$base_path$$config{'page'}${padded_index}.html";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$filename = "$base_path$$config{'page'}.html";
|
|
|
|
}
|
|
|
|
return $filename;
|
|
|
|
}
|
2022-08-09 01:30:22 +00:00
|
|
|
|
|
|
|
sub print_available_pages {
|
|
|
|
# Load config file
|
|
|
|
read_config "site.cfg" => my %config;
|
|
|
|
|
|
|
|
my @page_args = sort (keys %config);
|
|
|
|
|
|
|
|
# Remove non page sections of the configuration file
|
|
|
|
# from the generated list of pages.
|
|
|
|
@page_args= grep { $_ ne 'DBI' } @page_args;
|
|
|
|
@page_args= grep { $_ ne 'root_template' } @page_args;
|
|
|
|
|
|
|
|
foreach my $page_arg (@page_args) {
|
|
|
|
print "$page_arg\n";
|
|
|
|
}
|
|
|
|
exit;
|
|
|
|
}
|