Fixing issue #140

site-generator:

    Cosmetic adjustments. Additions to the POD documentation.
    Additions to module list.
    Additions to work better with UTF-8.
    Addition of functions 'parse_csv' and 'xml_entity'

templates/queries-episodes-sqlite.tpl.html:
templates/shared-utils.tpl.html:

    Cosmetic changes

templates/rss-query-hpr-mysql.tpl.xml:
templates/rss-query-hpr-sqlite.tpl.xml:
templates/rss-query-hpr_total-mysql.tpl.xml:
templates/rss-query-hpr_total-sqlite.tpl.xml:

    Enhancements to allow the query to collect the audio length from the
    'assets' table. The audio file extension is passed as an argument to
    the 'execute' statement.

templates/rss.tpl.xml:

    Cosmetic changes
    Changed one 'php' URL to 'html'.

templates/shared-episode-summary.tpl.html:

    Change to 'display_tags' macro to turn the 'eps.tags' field into
    a list of links. This works, but needs further development because
    using the tag strings as anchor ids is not reliable.

templates/shared-item.tpl.xml:

    Cosmetic changes.
    Addition of filter 'HTML.strip' which is used as a means of removing
    HTML tags from '<itunes:summary>' strings.
    Using new filter 'xml_entity' which converts all non-ASCII
    characters in the notes to numeric hexadecimal entities for
    '<itunes:summary>'.
    The '<enclosure>' tag now uses 'episode.length' rather than
    'episode.duration' which has been extracted from the 'assets' table.
This commit is contained in:
Dave Morriss 2023-08-19 13:34:50 +01:00
parent 7b6788731b
commit dc138596ea
10 changed files with 464 additions and 377 deletions

View File

@ -1,88 +1,92 @@
#!/usr/bin/perl #!/usr/bin/perl
# {{{ POD documentation
=head1 NAME =head1 NAME
site-generator - HPR Site Generator site-generator - HPR Site Generator
=head1 SYNOPSIS =head1 SYNOPSIS
site-generator [OPTION]... PAGE|PAGE=<comma separated list of ids>... site-generator [OPTION]... PAGE|PAGE=<comma separated list of ids>...
-a, --all generate all pages defined in configuration file -a, --all generate all pages defined in configuration file
-c, --configuration path to configuration file -c, --configuration path to configuration file
-l, --list print list of configured pages -l, --list print list of configured pages
-p, --preview print generated pages to standard out -p, --preview print generated pages to standard out
-q, --quiet suppress progress information while generating pages -q, --quiet suppress progress information while generating pages
-v, --verbose print extended progress information while generating pages -v, --verbose print extended progress information while generating pages
--help print this help message --help print this help message
Where I<PAGE> is a file name of a web page Where I<PAGE> is a file name of a web page
or the special I<ALL> (to generate all pages). or the special I<ALL> (to generate all pages).
Examples: Examples:
Generate two specific pages: Generate two specific pages:
site-generator index about site-generator index about
Generate the whole site: Generate the whole site:
site-generator --all site-generator --all
Generate pages based on the same template: Generate pages based on the same template:
site-generator correspondent=1,3,5..10 site-generator correspondent=1,3,5..10
Generate two specific pages with a different configuration: Generate two specific pages with a different configuration:
site-generator --configuration=site_sqlite.cfg index about site-generator --configuration=site_sqlite.cfg index about
=head1 DESCRIPTION =head1 DESCRIPTION
This is a site generator for the Hacker Public Radio website based upon the Perl Templates Toolkit. This is a site generator for the Hacker Public Radio website based upon the
Perl Template Toolkit.
=head1 INSTALLATION =head1 INSTALLATION
With SQLite With SQLite
* Create the sqlite3 database from the hpr.sql MySQL dump file available on * 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" 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 and should be located in the root of the project directory. The name and
location can be set in the site.cfg file. location can be set in the site.cfg file.
* An "update-hpr.sh" helper script is available in the utils directory. This * 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, script will download the hpr.sql file, convert it to the SQLite hpr.db file,
and regenerate the website using the site-generator. and regenerate the website using the site-generator.
1. `cd` into the root of the project directory 1. `cd` into the root of the project directory
2. Run `./utils/update-hpr.sh` 2. Run `./utils/update-hpr.sh`
* SQLite v3.8.3 or greater is recommended. CTE WITH clauses are used in some template queries. * 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. Must convert WITH clauses to sub-queries when using earlier versions of SQLite.
With MySQL With MySQL
* Create database hpr_hpr in the MySQL server from HPR dump file. * Create database hpr_hpr in the MySQL server from HPR dump file.
- sudo mysql --host=localhost < hpr.sql - sudo mysql --host=localhost < hpr.sql
* Create a user that will be used by the site-generator. * Create a user that will be used by the site-generator.
- Suggested username: hpr-generator - Suggested username: hpr-generator
- CREATE USER 'hpr-generator'@'localhost' IDENTIFIED BY '<password>'; - CREATE USER 'hpr-generator'@'localhost' IDENTIFIED BY '<password>';
* Limit the user's privileges to EXECUTE and SELECT * Limit the user's privileges to EXECUTE and SELECT
- GRANT SELECT ON hpr_hpr.* TO 'hpr-generator'@'localhost'; - GRANT SELECT ON hpr_hpr.* TO 'hpr-generator'@'localhost';
- GRANT EXECUTE 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.) Install the needed Perl modules using preferred method (distribution packages, CPAN, etc.)
* GetOpt * GetOpt
* Pod::Usage * Pod::Usage
* Config::Std * Config::Std
* Template * Template
* Template::Plugin::File * Template::Plugin::File
* Template::Plugin::DBI * Template::Plugin::DBI
* DBI * DBI
* Tie::DBI * Tie::DBI
* DBD::SQLite or DBD:mysql * DBD::SQLite or DBD:mysql
* Date::Calc * Date::Calc
* Text::CSV_XS
=head1 AUTHOR =head1 AUTHOR
Roan Horning <roan.horning@no-spam.gmail.com> Roan Horning <roan.horning@no-spam.gmail.com>
=head1 LICENSE =head1 LICENSE
site-generator -- a static website generator for HPR site-generator -- a static website generator for HPR
Copyright (C) 2022 Roan Horning Copyright (C) 2022 Roan Horning
This program is free software: you can redistribute it and/or modify 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 it under the terms of the GNU Affero General Public License as published by
@ -95,253 +99,323 @@ This is a site generator for the Hacker Public Radio website based upon the Perl
GNU Affero General Public License for more details. GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License 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/>. along with this program. If not, see <https://www.gnu.org/licenses/>.
=cut =cut
# }}}
use strict; use strict;
use warnings; use warnings;
use Getopt::Long qw(:config auto_help); use Getopt::Long qw(:config auto_help);
use Pod::Usage; use Pod::Usage;
use Config::Std; use Config::Std;
use Text::CSV_XS;
use HTML::Entities qw(encode_entities_numeric);
use Template; use Template;
use Data::Dumper; use Data::Dumper;
binmode STDOUT, ":encoding(UTF-8)";
binmode STDERR, ":encoding(UTF-8)";
exit main(); exit main();
sub main { sub main {
# Argument parsing # Argument parsing
my $all; my $all;
my $configuration_path; my $configuration_path;
my $preview; my $preview;
my $verbose; my $verbose;
my $quiet; my $quiet;
GetOptions( GetOptions(
'all' => \$all, 'all' => \$all,
'configuration=s' => \$configuration_path, 'configuration=s' => \$configuration_path,
'list' => \&print_available_pages, 'list' => \&print_available_pages,
'preview' => \$preview, 'preview' => \$preview,
'verbose' => \$verbose, 'verbose' => \$verbose,
'quiet' => \$quiet, 'quiet' => \$quiet,
) or pod2usage(1); ) or pod2usage(1);
pod2usage(1) unless @ARGV || $all; pod2usage(1) unless @ARGV || $all;
my (@page_args) = @ARGV; my (@page_args) = @ARGV;
if ($quiet) { if ($quiet) {
$verbose = 'quiet'; $verbose = 'quiet';
}; };
if (!$configuration_path) { if (!$configuration_path) {
$configuration_path = "site.cfg"; $configuration_path = "site.cfg";
} }
my %config; my %config;
if ( -f $configuration_path ) { if ( -f $configuration_path ) {
# Load config file # Load config file
read_config $configuration_path => %config; read_config $configuration_path => %config;
} }
else { else {
print STDOUT "Could not read configuration file: $configuration_path\n"; print STDOUT "Could not read configuration file: $configuration_path\n";
exit 1; exit 1;
} }
my $tt = get_template_html($config{DBI}, $config{app_paths}); my $tt = get_template_html($config{DBI}, $config{app_paths});
# If command line option all is set, parse configuration file #
# for all pages # Define a TT² vmethod called 'csv_parse', it takes a scalar value and
if ($all) { # returns an arrayref. Also define a filter called 'xml_entity' which
@page_args = keys %config; # numerically encodes non-ASCII characters.
#
$tt->context->define_vmethod( 'scalar', 'csv_parse', \&parse_csv );
$tt->context->define_filter( 'xml_entity', \&xml_entity );
# Remove non page sections of the configuration file # If command line option all is set, parse configuration file
# from the generated list of pages. # for all pages
@page_args= grep { $_ ne 'DBI' } @page_args; if ($all) {
@page_args= grep { $_ ne 'root_template' } @page_args; @page_args = keys %config;
@page_args= grep { $_ ne 'app_paths' } @page_args;
}; # Remove non page sections of the configuration file
foreach my $page_arg (@page_args) { # from the generated list of pages.
my %parsed_arg = parse_page_arg($page_arg); @page_args= grep { $_ ne 'DBI' } @page_args;
if (exists($config{$parsed_arg{'page'}})) { @page_args= grep { $_ ne 'root_template' } @page_args;
my $page_config = $config{$parsed_arg{'page'}}; @page_args= grep { $_ ne 'app_paths' } @page_args;
$page_config->{'page'} = $parsed_arg{'page'};
# Set page's root_template to the default root_template if the };
# page root_template property is not set in the configuration file. foreach my $page_arg (@page_args) {
if (exists $page_config->{'root_template'} == 0) { my %parsed_arg = parse_page_arg($page_arg);
$page_config->{'root_template'} = $config{root_template}{content}; if (exists($config{$parsed_arg{'page'}})) {
} my $page_config = $config{$parsed_arg{'page'}};
$page_config->{'page'} = $parsed_arg{'page'};
# Set all config root_template properties as default page config properties # Set page's root_template to the default root_template if the
# except the previously set root_template content property # page root_template property is not set in the configuration file.
my @root_args = grep { $_ ne 'content' } keys %{$config{root_template}}; if (exists $page_config->{'root_template'} == 0) {
foreach my $root_arg (@root_args) { $page_config->{'root_template'} = $config{root_template}{content};
if (exists $page_config->{$root_arg} == 0) { }
$page_config->{$root_arg} = $config{root_template}{$root_arg};
}
}
if ($page_config->{'multipage'} && $page_config->{'multipage'} eq 'true') { # Set all config root_template properties as default page config properties
# Empty arrayref bug fixed, so count is reduced by 1 # except the previously set root_template content property
# if (scalar @{$parsed_arg{'ids'}} == 1) { my @root_args = grep { $_ ne 'content' } keys %{$config{root_template}};
if (scalar @{$parsed_arg{'ids'}} == 0) { foreach my $root_arg (@root_args) {
@{$parsed_arg{'ids'}} = get_ids_from_db($tt, \$page_config); if (exists $page_config->{$root_arg} == 0) {
} $page_config->{$root_arg} = $config{root_template}{$root_arg};
foreach my $id (@{$parsed_arg{'ids'}}) { }
$page_config->{'id'} = $id; }
verbose ($verbose, "Generating page: $page_config->{'page'} with id: $id");
generate_page($tt, \$page_config, $preview); if ($page_config->{'multipage'} && $page_config->{'multipage'} eq 'true') {
} # Empty arrayref bug fixed, so count is reduced by 1
} # was: if (scalar @{$parsed_arg{'ids'}} == 1) {
else { if (scalar @{$parsed_arg{'ids'}} == 0) {
verbose ($verbose, "Generating page: $page_config->{'page'}"); @{$parsed_arg{'ids'}} = get_ids_from_db($tt, \$page_config);
generate_page($tt, \$page_config, $preview); }
} foreach my $id (@{$parsed_arg{'ids'}}) {
} $page_config->{'id'} = $id;
else { verbose ($verbose, "Generating page: $page_config->{'page'} with id: $id");
verbose (1, "\nWarning: Page $parsed_arg{'page'} is not defined in the configuration file."); generate_page($tt, \$page_config, $preview);
} }
} }
else {
verbose ($verbose, "Generating page: $page_config->{'page'}");
generate_page($tt, \$page_config, $preview);
}
}
else {
verbose (1, "\nWarning: Page $parsed_arg{'page'} is not defined in the configuration file.");
}
}
verbose (1, "\nFinished processing the files."); verbose (1, "\nFinished processing the files.");
return 0; return 0;
} }
sub get_template_html (\%@) { sub get_template_html (\%@) {
# For an HTML based Template file, define the # For an HTML based Template file, define the
# template start and end tags to also function as # template start and end tags to also function as
# HTML comments to make the template file valid HTML. # HTML comments to make the template file valid HTML.
# #
return Template->new({ return Template->new(
INCLUDE_PATH => $_[1]{templates_path}, { INCLUDE_PATH => $_[1]{templates_path},
OUTPUT_PATH => $_[1]{output_path}, OUTPUT_PATH => $_[1]{output_path},
EVAL_PERL => 1, ENCODING => 'utf8',
START_TAG => '<!--%', EVAL_PERL => 1,
END_TAG => '%-->', START_TAG => '<!--%',
PRE_CHOMP => 1, END_TAG => '%-->',
POST_CHOMP => 1, PRE_CHOMP => 1,
CONSTANTS => { POST_CHOMP => 1,
database => $_[0]{database}, CONSTANTS => {
driver => $_[0]{driver}, database => $_[0]{database},
user => $_[0]{user}, driver => $_[0]{driver},
password => $_[0]{password}, user => $_[0]{user},
} password => $_[0]{password},
}) || die $Template::ERROR, "\n"; }
}
) || die $Template::ERROR, "\n";
} }
sub generate_page { sub generate_page {
my ($tt, $config, $preview) = @_; my ( $tt, $config, $preview ) = @_;
my $html; my $html;
if (!$preview) { if ( !$preview ) {
$html = get_filename($$config); $html = get_filename($$config);
} }
$tt->process($$config->{root_template}, $$config, $html) $tt->process( $$config->{root_template},
|| die $tt->error(), "\n"; $$config, $html, { binmode => ':utf8' } )
|| die $tt->error(), "\n";
} }
sub verbose { sub verbose {
my ($verbose, $message) = @_; my ($verbose, $message) = @_;
if ($verbose) { if ($verbose) {
if ($verbose ne 'quiet') { if ($verbose ne 'quiet') {
print STDOUT "$message\n"; print STDOUT "$message\n";
} }
} }
else { else {
STDOUT->autoflush(1); STDOUT->autoflush(1);
print STDOUT "."; print STDOUT ".";
}; };
} }
sub parse_page_arg { sub parse_page_arg {
my ($page_arg) = @_; my ($page_arg) = @_;
# Split page name from page ids if available. # Split page name from page ids if available.
my ($page, $ids) = split(/=/, $page_arg); my ($page, $ids) = split(/=/, $page_arg);
#my @ids = []; my @ids;
my @ids;
if(!$ids) { if(!$ids) {
$ids = ""; $ids = "";
} }
else { else {
# Parse the page ids and push them onto @ids array # Parse the page ids and push them onto @ids array
my @ids_by_comma = split(/\,/, $ids); my @ids_by_comma = split(/\,/, $ids);
foreach my $id_by_comma (@ids_by_comma) { foreach my $id_by_comma (@ids_by_comma) {
my @ids_for_range = split(/\.\./, $id_by_comma); my @ids_for_range = split(/\.\./, $id_by_comma);
if ((scalar @ids_for_range) == 2) { if ((scalar @ids_for_range) == 2) {
push @ids, $ids_for_range[0]..$ids_for_range[1]; push @ids, $ids_for_range[0]..$ids_for_range[1];
} }
elsif ((scalar @ids_for_range) == 1) { elsif ((scalar @ids_for_range) == 1) {
push @ids, $ids_for_range[0]; push @ids, $ids_for_range[0];
} }
else { else {
verbose (1, "\nWarning: Page $page id range $id_by_comma could not be parsed."); verbose (1, "\nWarning: Page $page id range $id_by_comma could not be parsed.");
} }
} }
} }
return ('page' => $page, 'ids' => [@ids]); return ('page' => $page, 'ids' => [@ids]);
} }
sub get_ids_from_db { sub get_ids_from_db {
# Use a template to generate a string of page identifiers. # Use a template to generate a string of page identifiers.
# The template should return the string in the form of # The template should return the string in the form of
# <comma><identifier><comma><identifier>... # <comma><identifier><comma><identifier>...
# #
my ($tt, $config) = @_; my ($tt, $config) = @_;
my $selected_ids = ""; my $selected_ids = "";
my $id_template = "ids-$$config->{'page'}.tpl.html"; my $id_template = "ids-$$config->{'page'}.tpl.html";
$tt->process($id_template, $$config, \$selected_ids) $tt->process($id_template, $$config, \$selected_ids)
|| die $tt->error(), "\n"; || die $tt->error(), "\n";
# Starts with a newline and comma # Starts with a newline and comma
return split(/,/, substr($selected_ids, 2)); return split(/,/, substr($selected_ids, 2));
} }
sub get_filename { sub get_filename {
my ($config) = @_; my ($config) = @_;
my $filename = "output.html"; my $filename = "output.html";
my $base_path = ""; my $base_path = "";
if ($$config{'filename'}) { if ($$config{'filename'}) {
if (substr($$config{'filename'}, -1) eq '/') { if (substr($$config{'filename'}, -1) eq '/') {
$base_path = $$config{'filename'}; $base_path = $$config{'filename'};
} }
else { else {
$filename = $$config{'filename'}; $filename = $$config{'filename'};
my $padded_index = ""; my $padded_index = "";
if (exists $$config{'id'} && $$config{'id'} ne "") { if (exists $$config{'id'} && $$config{'id'} ne "") {
$padded_index = sprintf("%04d", $$config{'id'}); $padded_index = sprintf("%04d", $$config{'id'});
} }
$filename =~ s/\[id\]/$padded_index/; $filename =~ s/\[id\]/$padded_index/;
return $filename; return $filename;
} }
} }
# Default naming if full filename configuration is not supplied. # Default naming if full filename configuration is not supplied.
if ($$config{'multipage'} && $$config{'multipage'} eq 'true') { if ($$config{'multipage'} && $$config{'multipage'} eq 'true') {
my $padded_index = sprintf("%04d", $$config{'id'}); my $padded_index = sprintf("%04d", $$config{'id'});
$filename = "$base_path$$config{'page'}${padded_index}.html"; $filename = "$base_path$$config{'page'}${padded_index}.html";
} }
else { else {
$filename = "$base_path$$config{'page'}.html"; $filename = "$base_path$$config{'page'}.html";
} }
return $filename; return $filename;
} }
sub print_available_pages { sub print_available_pages {
# Load config file # Load config file
read_config "site.cfg" => my %config; read_config "site.cfg" => my %config;
my @page_args = sort (keys %config); my @page_args = sort ( keys %config );
# Remove non page sections of the configuration file # Remove non page sections of the configuration file
# from the generated list of pages. # from the generated list of pages.
@page_args= grep { $_ ne 'DBI' } @page_args; @page_args = grep { $_ ne 'DBI' } @page_args;
@page_args= grep { $_ ne 'root_template' } @page_args; @page_args = grep { $_ ne 'root_template' } @page_args;
foreach my $page_arg (@page_args) { foreach my $page_arg (@page_args) {
print "$page_arg\n"; print "$page_arg\n";
} }
exit; exit;
} }
#=== FUNCTION ================================================================
# NAME: parse_csv
# PURPOSE: Parses a simple string containing CSV data
# PARAMETERS: $csv_in CSV string
# RETURNS: An arrayref containing the parsed CSV elements
# DESCRIPTION: The Text::CSV_XS module instance is created with the option
# 'allow_whitespace' to be forgiving of any spaces around the
# CSV elements and to strip them. Also, 'allow_loose_quotes' is
# forgiving of really messed up CSV.
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub parse_csv {
my ($csv_in) = @_;
my $csv = Text::CSV_XS->new(
{ binary => 1,
auto_diag => 1,
allow_whitespace => 1,
allow_loose_quotes => 1
}
);
my $status = $csv->parse($csv_in);
unless ( $status ) {
warn "Failed to parse '$csv_in'\n" ;
return;
}
my @fields = $csv->fields();
return \@fields;
}
#=== FUNCTION ================================================================
# NAME: xml_entity
# PURPOSE: Static filter to encode Unicode for XML
# PARAMETERS: $text String to be processed
# RETURNS: Processed text
# DESCRIPTION:
# THROWS: No exceptions
# COMMENTS: None
# SEE ALSO: N/A
#===============================================================================
sub xml_entity {
my ($text) = @_;
encode_entities_numeric( $text );
return $text;
}
# vim: syntax=perl:ts=8:sw=4:et:ai:tw=78:fo=tcrqn21:fdm=marker

View File

@ -1,15 +1,15 @@
<!--% query_episodes = 'SELECT <!--% query_episodes = 'SELECT
eps.id, eps.id,
eps.explicit, eps.explicit,
eps.date, eps.license, eps.title, eps.summary, eps.date, eps.license, eps.title, eps.summary,
eps.duration, eps.notes, eps.tags, eps.duration, eps.notes, eps.tags,
hosts.hostid, hosts.hostid,
hosts.host, hosts.email, hosts.local_image, hosts.host, hosts.email, hosts.local_image,
miniseries.name AS series, miniseries.id AS seriesid miniseries.name AS series, miniseries.id AS seriesid
FROM eps FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date <= date(\'now\') WHERE eps.date <= date(\'now\')
ORDER BY eps.id + 0 DESC' ORDER BY eps.id + 0 DESC'
%--> %-->

View File

@ -1,23 +1,26 @@
<!--% USE DBI(constants.driver, constants.user, constants.password) %--> <!--% USE DBI(constants.driver, constants.user, constants.password) %-->
<!--% query_hpr_feed = DBI.prepare(' <!--% query_hpr_feed = DBI.prepare('
SELECT SELECT
eps.id, eps.id,
eps.explicit, eps.explicit,
DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\', DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\',
eps.license, eps.duration, eps.license, eps.duration,
eps.title, eps.summary, eps.tags, eps.title, eps.summary, eps.tags,
eps.notes, eps.notes,
hosts.local_image, hosts.local_image,
hosts.hostid, hosts.hostid,
hosts.host, hosts.email, hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid miniseries.name AS series, miniseries.id AS seriesid,
FROM eps assets.size AS length
INNER JOIN hosts ON eps.hostid = hosts.hostid FROM eps
INNER JOIN miniseries ON eps.series = miniseries.id INNER JOIN hosts ON eps.hostid = hosts.hostid
WHERE eps.date < DATE_ADD(NOW(), INTERVAL 1 DAY) INNER JOIN miniseries ON eps.series = miniseries.id
ORDER BY eps.date DESC INNER JOIN assets ON eps.id = assets.episode_id
LIMIT 10 WHERE eps.date <= UTC_DATE()
AND assets.extension = ?
ORDER BY eps.date DESC
LIMIT 10
') ')
%--> %-->
<!--% feed_result = query_hpr_feed.execute() %--> <!--% feed_result = query_hpr_feed.execute(media_file_extension) %-->

View File

@ -1,23 +1,26 @@
<!--% USE DBI(constants.driver, constants.user, constants.password) %--> <!--% USE DBI(constants.driver, constants.user, constants.password) %-->
<!--% query_hpr_feed = DBI.prepare(' <!--% query_hpr_feed = DBI.prepare('
SELECT SELECT
eps.id, eps.id,
eps.explicit, eps.explicit,
strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date, strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date,
eps.license, eps.duration, eps.license, eps.duration,
eps.title, eps.summary, eps.tags, eps.title, eps.summary, eps.tags,
eps.notes, eps.notes,
hosts.local_image, hosts.local_image,
hosts.hostid, hosts.hostid,
hosts.host, hosts.email, hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid miniseries.name AS series, miniseries.id AS seriesid,
FROM eps assets.size AS length
INNER JOIN hosts ON eps.hostid = hosts.hostid FROM eps
INNER JOIN miniseries ON eps.series = miniseries.id INNER JOIN hosts ON eps.hostid = hosts.hostid
WHERE eps.date < date(\'now\', \'+1 days\') INNER JOIN miniseries ON eps.series = miniseries.id
ORDER BY eps.date DESC INNER JOIN assets ON eps.id = assets.episode_id
LIMIT 10 WHERE eps.date <= date(\'now\')
AND assets.extension = ?
ORDER BY eps.date DESC
LIMIT 10
') ')
%--> %-->
<!--% feed_result = query_hpr_feed.execute() %--> <!--% feed_result = query_hpr_feed.execute(media_file_extension) %-->

View File

@ -1,22 +1,25 @@
<!--% USE DBI(constants.driver, constants.user, constants.password) %--> <!--% USE DBI(constants.driver, constants.user, constants.password) %-->
<!--% query_hpr_feed = DBI.prepare(' <!--% query_hpr_feed = DBI.prepare('
SELECT SELECT
eps.id, eps.id,
eps.explicit, eps.explicit,
DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\', DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\',
eps.license, eps.duration, eps.license, eps.duration,
eps.title, eps.summary, eps.tags, eps.title, eps.summary, eps.tags,
eps.notes, eps.notes,
hosts.local_image, hosts.local_image,
hosts.hostid, hosts.hostid,
hosts.host, hosts.email, hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid miniseries.name AS series, miniseries.id AS seriesid,
FROM eps assets.size AS length
INNER JOIN hosts ON eps.hostid = hosts.hostid FROM eps
INNER JOIN miniseries ON eps.series = miniseries.id INNER JOIN hosts ON eps.hostid = hosts.hostid
WHERE eps.date < DATE_ADD(NOW(), INTERVAL 1 DAY) INNER JOIN miniseries ON eps.series = miniseries.id
ORDER BY eps.date DESC INNER JOIN assets ON eps.id = assets.episode_id
WHERE eps.date < UTC_DATE()
AND assets.extension = ?
ORDER BY eps.date DESC
') ')
%--> %-->
<!--% feed_result = query_hpr_feed.execute() %--> <!--% feed_result = query_hpr_feed.execute(media_file_extension) %-->

View File

@ -1,22 +1,25 @@
<!--% USE DBI(constants.driver, constants.user, constants.password) %--> <!--% USE DBI(constants.driver, constants.user, constants.password) %-->
<!--% query_hpr_feed = DBI.prepare(' <!--% query_hpr_feed = DBI.prepare('
SELECT SELECT
eps.id, eps.id,
eps.explicit, eps.explicit,
strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date, strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date,
eps.license, eps.duration, eps.license, eps.duration,
eps.title, eps.summary, eps.tags, eps.title, eps.summary, eps.tags,
eps.notes, eps.notes,
hosts.local_image, hosts.local_image,
hosts.hostid, hosts.hostid,
hosts.host, hosts.email, hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid miniseries.name AS series, miniseries.id AS seriesid,
FROM eps assets.size AS length
INNER JOIN hosts ON eps.hostid = hosts.hostid FROM eps
INNER JOIN miniseries ON eps.series = miniseries.id INNER JOIN hosts ON eps.hostid = hosts.hostid
WHERE eps.date < date(\'now\', \'+1 days\') INNER JOIN miniseries ON eps.series = miniseries.id
ORDER BY eps.date DESC INNER JOIN assets ON eps.id = assets.episode_id
WHERE eps.date <= date(\'now\')
AND assets.extension = ?
ORDER BY eps.date DESC
') ')
%--> %-->
<!--% feed_result = query_hpr_feed.execute() %--> <!--% feed_result = query_hpr_feed.execute(media_file_extension) %-->

View File

@ -1,9 +1,9 @@
<!--% USE date %--> <!--% USE date %-->
<!--% PROCESS 'shared-utils.tpl.html' %--> <!--% PROCESS 'shared-utils.tpl.html' %-->
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0" <rss version="2.0"
xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"
xmlns:atom="http://www.w3.org/2005/Atom" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" > xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" >
<channel> <channel>
<title>Hacker Public Radio</title> <title>Hacker Public Radio</title>
@ -22,14 +22,14 @@
<itunes:author>Hacker Public Radio</itunes:author> <itunes:author>Hacker Public Radio</itunes:author>
<itunes:keywords>Community Radio, Tech Interviews, Linux, Open, Hobby, Software Freedom</itunes:keywords> <itunes:keywords>Community Radio, Tech Interviews, Linux, Open, Hobby, Software Freedom</itunes:keywords>
<copyright>Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License</copyright> <copyright>Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) License</copyright>
<managingEditor>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</managingEditor> <managingEditor>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</managingEditor>
<!-- <author>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</author> --> <!-- <author>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</author> -->
<itunes:owner> <itunes:owner>
<itunes:name>HPR Volunteer</itunes:name> <itunes:name>HPR Volunteer</itunes:name>
<itunes:email>admin@hackerpublicradio.org</itunes:email> <itunes:email>admin@hackerpublicradio.org</itunes:email>
</itunes:owner> </itunes:owner>
<webMaster>admin@hackerpublicradio.org (HPR Volunteer)</webMaster> <webMaster>admin@hackerpublicradio.org (HPR Volunteer)</webMaster>
<generator>site-generator</generator> <generator>site-generator</generator>
<docs>http://www.rssboard.org/rss-specification</docs> <docs>http://www.rssboard.org/rss-specification</docs>
<ttl>43200</ttl> <ttl>43200</ttl>
<skipDays> <skipDays>
@ -39,7 +39,7 @@
<image> <image>
<url>https://www.hackerpublicradio.org/images/hpr_feed_small.png</url> <url>https://www.hackerpublicradio.org/images/hpr_feed_small.png</url>
<title>Hacker Public Radio</title> <title>Hacker Public Radio</title>
<link>https://www.hackerpublicradio.org/about.php</link> <link>https://www.hackerpublicradio.org/about.html</link>
<description>The Hacker Public Radio Old Microphone Logo</description> <description>The Hacker Public Radio Old Microphone Logo</description>
<height>164</height> <height>164</height>
<width>144</width> <width>144</width>
@ -51,7 +51,7 @@
<googleplay:category text="Technology"/> <googleplay:category text="Technology"/>
<atom:link href="https://www.hackerpublicradio.org/<!--% filename %-->" rel="self" type="application/rss+xml" /> <atom:link href="https://www.hackerpublicradio.org/<!--% filename %-->" rel="self" type="application/rss+xml" />
<pubDate><!--% format_feed_date(date.now) %--></pubDate> <pubDate><!--% format_feed_date(date.now) %--></pubDate>
<!--% INCLUDE $content %--> <!--% INCLUDE $content %-->
</channel> </channel>
</rss> </rss>

View File

@ -12,7 +12,11 @@ from the series <em><a href="<!--% baseurl %-->series/<!--% zero_pad_left(series
<!--% END %--> <!--% END %-->
<!--% MACRO display_tags(tags) BLOCK %--> <!--% MACRO display_tags(tags) BLOCK %-->
<span><label>Tags:</label> <em><!--% tags %--></em>.</span> <span><label>Tags:</label> <em>
<!--% FOREACH tag IN tags.csv_parse %-->
<a href="<!--% absolute_path(baseurl) %-->tags.html#<!--% tag.lower %-->"><!--% tag %--></a><!--% IF loop.count == loop.size %-->.<!--% ELSE %-->,<!--% END %-->
<!--% END %--></em>
</span>
<!--% END %--> <!--% END %-->
<!--% MACRO display_listen_in(eps_id, episode_type) BLOCK %--> <!--% MACRO display_listen_in(eps_id, episode_type) BLOCK %-->

View File

@ -1,23 +1,24 @@
<!--% PROCESS 'shared-utils.tpl.html' %--> <!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% MACRO display_item(episode, file_extension, audio_mime_type) BLOCK %--> <!--% MACRO display_item(episode, file_extension, audio_mime_type) BLOCK %-->
<!--% USE HTML.Strip %-->
<!--% IF audio_mime_type == "" %--> <!--% IF audio_mime_type == "" %-->
<!--% audio_mime_type = 'ogg' %--> <!--% audio_mime_type = 'ogg' %-->
<!--% END %--> <!--% END %-->
<item> <item>
<itunes:explicit><!--% display_explicit_feed(episode.explicit) %--></itunes:explicit> <itunes:explicit><!--% display_explicit_feed(episode.explicit) %--></itunes:explicit>
<googleplay:explicit><!--% display_explicit_feed(episode.explicit) %--></googleplay:explicit> <googleplay:explicit><!--% display_explicit_feed(episode.explicit) %--></googleplay:explicit>
<title>HPR<!--% zero_pad_left(episode.id) %-->: <!--% episode.title %--></title> <title>HPR<!--% zero_pad_left(episode.id) %-->: <!--% episode.title %--></title>
<author><!--% episode.email %--> (<!--% episode.host %-->)</author> <author><!--% episode.email %--> (<!--% episode.host %-->)</author>
<googleplay:author><!--% episode.email %--> (<!--% episode.host %-->)</googleplay:author> <googleplay:author><!--% episode.email %--> (<!--% episode.host %-->)</googleplay:author>
<itunes:author><!--% episode.email %--> (<!--% episode.host %-->)</itunes:author> <itunes:author><!--% episode.email %--> (<!--% episode.host %-->)</itunes:author>
<googleplay:image href="https://www.hackerpublicradio.org/images/hpr_feed_itunes.png"/> <googleplay:image href="https://www.hackerpublicradio.org/images/hpr_feed_itunes.png"/>
<link>https://www.hackerpublicradio.org/eps/hpr/<!--% zero_pad_left(episode.id) %-->/index.html</link> <link>https://www.hackerpublicradio.org/eps/hpr<!--% zero_pad_left(episode.id) %-->/index.html</link>
<description><![CDATA[<!--% episode.notes %-->]]> <description><![CDATA[<!--% episode.notes %-->]]>
</description> </description>
<itunes:summary><![CDATA[<!--% episode.notes %-->]]> <itunes:summary><![CDATA[<!--% episode.notes.substr(0, 4000) | html_strip | xml_entity %-->]]>
</itunes:summary> </itunes:summary>
<pubDate><!--% format_feed_date(episode.date) %--></pubDate> <pubDate><!--% format_feed_date(episode.date) %--></pubDate>
<enclosure url="http://hackerpublicradio.org/eps/hpr<!--% zero_pad_left(episode.id) %-->.<!--% file_extension %-->" length="<!--% episode.duration * 1000 %-->" type="audio/<!--% audio_mime_type %-->"/> <enclosure url="http://hackerpublicradio.org/eps/hpr<!--% zero_pad_left(episode.id) %-->.<!--% file_extension %-->" length="<!--% episode.length %-->" type="audio/<!--% audio_mime_type %-->"/>
<guid>http://hackerpublicradio.org/eps/hpr<!--% zero_pad_left(episode.id) %-->.<!--% file_extension %--></guid> <guid>http://hackerpublicradio.org/eps/hpr<!--% zero_pad_left(episode.id) %-->.<!--% file_extension %--></guid>
</item> </item>
<!--% END %--> <!--% END %-->

View File

@ -1,11 +1,11 @@
<!--% MACRO zero_pad_left(word, pad_length) BLOCK %--> <!--% MACRO zero_pad_left(word, pad_length) BLOCK %-->
<!--% IF pad_length %--> <!--% IF pad_length %-->
<!--% zero_pad_format = "%0${pad_length}s" %--> <!--% zero_pad_format = "%0${pad_length}s" %-->
<!--% ELSE %--> <!--% ELSE %-->
<!--% zero_pad_format = "%04s" %--> <!--% zero_pad_format = "%04s" %-->
<!--% END %--> <!--% END %-->
<!--% USE String(word) %--> <!--% USE String(word) %-->
<!--% String.format(zero_pad_format) %--> <!--% String.format(zero_pad_format) %-->
<!--% END %--> <!--% END %-->
<!--% MACRO display_choice(choice, display_when_true, display_when_false) BLOCK %--> <!--% MACRO display_choice(choice, display_when_true, display_when_false) BLOCK %-->
@ -17,7 +17,7 @@
<!--% seconds = duration_sec % 60 %--> <!--% seconds = duration_sec % 60 %-->
<!--% USE format %--> <!--% USE format %-->
<!--% minutes_only = format("%d") %--> <!--% minutes_only = format("%d") %-->
<!--% minutes = minutes_only(duration_sec / 60) %--> <!--% minutes = minutes_only(duration_sec / 60) %-->
<!--% hours_only = format("%d") %--> <!--% hours_only = format("%d") %-->
<!--% hours = hours_only(minutes / 60) %--> <!--% hours = hours_only(minutes / 60) %-->
<!--% IF hours >= 1 %--> <!--% IF hours >= 1 %-->
@ -57,23 +57,23 @@
<!--% END %--> <!--% END %-->
<!--% MACRO media_path(episode_id, episode_type, media_type, baseurl, media_baseurl) BLOCK %--> <!--% MACRO media_path(episode_id, episode_type, media_type, baseurl, media_baseurl) BLOCK %-->
<!--% IF episode_type == "twat" %--> <!--% IF episode_type == "twat" %-->
<!--% padding = 3 %--> <!--% padding = 3 %-->
<!--% media_folder = "eps/"; padding = 3 %--> <!--% media_folder = "eps/"; padding = 3 %-->
<!--% ELSE %--> <!--% ELSE %-->
<!--% media_folder = "local/" %--> <!--% media_folder = "local/" %-->
<!--% END %--> <!--% END %-->
<!--% IF media_baseurl %--> <!--% IF media_baseurl %-->
<!--% transcription_types = "txt srt vtt" %--> <!--% transcription_types = "txt srt vtt" %-->
<!--% USE String(transcription_types) %--> <!--% USE String(transcription_types) %-->
<!--% USE String(media_baseurl) %--> <!--% USE String(media_baseurl) %-->
<!--% IF transcription_types.search(media_type) && media_baseurl.search('archive.org') %--> <!--% IF transcription_types.search(media_type) && media_baseurl.search('archive.org') %-->
<!--% media_baseurl = "${media_baseurl}hpr\$eps_id/" %--> <!--% media_baseurl = "${media_baseurl}hpr\$eps_id/" %-->
<!--% END %--> <!--% END %-->
<!--% media_folder = "" %--> <!--% media_folder = "" %-->
<!--% media_baseurl = media_baseurl.replace('\$eps_id', zero_pad_left(episode_id)) %--> <!--% media_baseurl = media_baseurl.replace('\$eps_id', zero_pad_left(episode_id)) %-->
<!--% END %--> <!--% END %-->
<!--% media_basepath(baseurl, media_baseurl) %--><!--% media_folder %--><!--% episode_type %--><!--% zero_pad_left(episode_id, padding) %-->.<!--% media_type %--> <!--% media_basepath(baseurl, media_baseurl) %--><!--% media_folder %--><!--% episode_type %--><!--% zero_pad_left(episode_id, padding) %-->.<!--% media_type %-->
<!--% END %--> <!--% END %-->
<!--% MACRO step_navigation(baseurl, links, folder) BLOCK %--> <!--% MACRO step_navigation(baseurl, links, folder) BLOCK %-->
@ -93,7 +93,3 @@
<!--% END %--> <!--% END %-->
<a href="<!--% absolute_path(baseurl) %-->eps/<!--% folder %--><!--% zero_pad_left(links.latest) %-->/index.html" rel="last">Latest &gt;&gt;</a></small> <a href="<!--% absolute_path(baseurl) %-->eps/<!--% folder %--><!--% zero_pad_left(links.latest) %-->/index.html" rel="last">Latest &gt;&gt;</a></small>
<!--% END %--> <!--% END %-->
<!--
vim: syntax=html:ts=8:sw=4:tw=78:et:ai:
-->