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
# {{{ POD documentation
=head1 NAME
site-generator - HPR Site Generator
site-generator - HPR Site Generator
=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
-c, --configuration path to configuration file
-l, --list print list of configured pages
-p, --preview print generated pages to standard out
-q, --quiet suppress progress information while generating pages
-v, --verbose print extended progress information while generating pages
--help print this help message
-a, --all generate all pages defined in configuration file
-c, --configuration path to configuration file
-l, --list print list of configured pages
-p, --preview print generated pages to standard out
-q, --quiet suppress progress information while generating pages
-v, --verbose print extended progress information while generating pages
--help print this help message
Where I<PAGE> is a file name of a web page
or the special I<ALL> (to generate all pages).
Where I<PAGE> is a file name of a web page
or the special I<ALL> (to generate all pages).
Examples:
Examples:
Generate two specific pages:
site-generator index about
Generate two specific pages:
site-generator index about
Generate the whole site:
site-generator --all
Generate the whole site:
site-generator --all
Generate pages based on the same template:
site-generator correspondent=1,3,5..10
Generate pages based on the same template:
site-generator correspondent=1,3,5..10
Generate two specific pages with a different configuration:
site-generator --configuration=site_sqlite.cfg index about
Generate two specific pages with a different configuration:
site-generator --configuration=site_sqlite.cfg index about
=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
With SQLite
* 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`
* 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 SQLite
* 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`
* 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';
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.)
* GetOpt
* Pod::Usage
* Config::Std
* Template
* Template::Plugin::File
* Template::Plugin::DBI
* DBI
* Tie::DBI
* DBD::SQLite or DBD:mysql
* Date::Calc
Install the needed Perl modules using preferred method (distribution packages, CPAN, etc.)
* GetOpt
* Pod::Usage
* Config::Std
* Template
* Template::Plugin::File
* Template::Plugin::DBI
* DBI
* Tie::DBI
* DBD::SQLite or DBD:mysql
* Date::Calc
* Text::CSV_XS
=head1 AUTHOR
Roan Horning <roan.horning@no-spam.gmail.com>
Roan Horning <roan.horning@no-spam.gmail.com>
=head1 LICENSE
site-generator -- a static website generator for HPR
Copyright (C) 2022 Roan Horning
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
@ -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.
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
# }}}
use strict;
use warnings;
use Getopt::Long qw(:config auto_help);
use Pod::Usage;
use Config::Std;
use Text::CSV_XS;
use HTML::Entities qw(encode_entities_numeric);
use Template;
use Data::Dumper;
binmode STDOUT, ":encoding(UTF-8)";
binmode STDERR, ":encoding(UTF-8)";
exit main();
sub main {
# Argument parsing
my $all;
my $configuration_path;
my $preview;
my $verbose;
my $quiet;
GetOptions(
'all' => \$all,
'configuration=s' => \$configuration_path,
'list' => \&print_available_pages,
'preview' => \$preview,
'verbose' => \$verbose,
'quiet' => \$quiet,
) or pod2usage(1);
pod2usage(1) unless @ARGV || $all;
my (@page_args) = @ARGV;
# Argument parsing
my $all;
my $configuration_path;
my $preview;
my $verbose;
my $quiet;
GetOptions(
'all' => \$all,
'configuration=s' => \$configuration_path,
'list' => \&print_available_pages,
'preview' => \$preview,
'verbose' => \$verbose,
'quiet' => \$quiet,
) or pod2usage(1);
pod2usage(1) unless @ARGV || $all;
my (@page_args) = @ARGV;
if ($quiet) {
$verbose = 'quiet';
};
if ($quiet) {
$verbose = 'quiet';
};
if (!$configuration_path) {
$configuration_path = "site.cfg";
}
if (!$configuration_path) {
$configuration_path = "site.cfg";
}
my %config;
if ( -f $configuration_path ) {
# Load config file
read_config $configuration_path => %config;
}
else {
print STDOUT "Could not read configuration file: $configuration_path\n";
exit 1;
}
my %config;
if ( -f $configuration_path ) {
# Load config file
read_config $configuration_path => %config;
}
else {
print STDOUT "Could not read configuration file: $configuration_path\n";
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
if ($all) {
@page_args = keys %config;
#
# Define a TT² vmethod called 'csv_parse', it takes a scalar value and
# returns an arrayref. Also define a filter called 'xml_entity' which
# 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
# from the generated list of pages.
@page_args= grep { $_ ne 'DBI' } @page_args;
@page_args= grep { $_ ne 'root_template' } @page_args;
@page_args= grep { $_ ne 'app_paths' } @page_args;
# If command line option all is set, parse configuration file
# for all pages
if ($all) {
@page_args = keys %config;
};
foreach my $page_arg (@page_args) {
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'};
# 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;
@page_args= grep { $_ ne 'app_paths' } @page_args;
# 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};
}
};
foreach my $page_arg (@page_args) {
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'};
# 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};
}
}
# 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};
}
if ($page_config->{'multipage'} && $page_config->{'multipage'} eq 'true') {
# Empty arrayref bug fixed, so count is reduced by 1
# if (scalar @{$parsed_arg{'ids'}} == 1) {
if (scalar @{$parsed_arg{'ids'}} == 0) {
@{$parsed_arg{'ids'}} = get_ids_from_db($tt, \$page_config);
}
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);
}
}
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.");
}
}
# 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};
}
}
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) {
if (scalar @{$parsed_arg{'ids'}} == 0) {
@{$parsed_arg{'ids'}} = get_ids_from_db($tt, \$page_config);
}
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);
}
}
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.");
return 0;
return 0;
}
sub get_template_html (\%@) {
# 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({
INCLUDE_PATH => $_[1]{templates_path},
OUTPUT_PATH => $_[1]{output_path},
EVAL_PERL => 1,
START_TAG => '<!--%',
END_TAG => '%-->',
PRE_CHOMP => 1,
POST_CHOMP => 1,
CONSTANTS => {
database => $_[0]{database},
driver => $_[0]{driver},
user => $_[0]{user},
password => $_[0]{password},
}
}) || die $Template::ERROR, "\n";
# 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(
{ INCLUDE_PATH => $_[1]{templates_path},
OUTPUT_PATH => $_[1]{output_path},
ENCODING => 'utf8',
EVAL_PERL => 1,
START_TAG => '<!--%',
END_TAG => '%-->',
PRE_CHOMP => 1,
POST_CHOMP => 1,
CONSTANTS => {
database => $_[0]{database},
driver => $_[0]{driver},
user => $_[0]{user},
password => $_[0]{password},
}
}
) || die $Template::ERROR, "\n";
}
sub generate_page {
my ($tt, $config, $preview) = @_;
my $html;
if (!$preview) {
$html = get_filename($$config);
}
$tt->process($$config->{root_template}, $$config, $html)
|| die $tt->error(), "\n";
sub generate_page {
my ( $tt, $config, $preview ) = @_;
my $html;
if ( !$preview ) {
$html = get_filename($$config);
}
$tt->process( $$config->{root_template},
$$config, $html, { binmode => ':utf8' } )
|| die $tt->error(), "\n";
}
sub verbose {
my ($verbose, $message) = @_;
if ($verbose) {
if ($verbose ne 'quiet') {
print STDOUT "$message\n";
}
}
else {
STDOUT->autoflush(1);
print STDOUT ".";
};
my ($verbose, $message) = @_;
if ($verbose) {
if ($verbose ne 'quiet') {
print STDOUT "$message\n";
}
}
else {
STDOUT->autoflush(1);
print STDOUT ".";
};
}
sub parse_page_arg {
my ($page_arg) = @_;
# Split page name from page ids if available.
my ($page, $ids) = split(/=/, $page_arg);
#my @ids = [];
my @ids;
my ($page_arg) = @_;
# Split page name from page ids if available.
my ($page, $ids) = split(/=/, $page_arg);
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.");
}
}
}
return ('page' => $page, 'ids' => [@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.");
}
}
}
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";
# 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";
$tt->process($id_template, $$config, \$selected_ids)
|| die $tt->error(), "\n";
# Starts with a newline and comma
return split(/,/, substr($selected_ids, 2));
# Starts with a newline and comma
return split(/,/, substr($selected_ids, 2));
}
sub get_filename {
my ($config) = @_;
my $filename = "output.html";
my $base_path = "";
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'};
my $padded_index = "";
if (exists $$config{'id'} && $$config{'id'} ne "") {
$padded_index = sprintf("%04d", $$config{'id'});
}
$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;
if ($$config{'filename'}) {
if (substr($$config{'filename'}, -1) eq '/') {
$base_path = $$config{'filename'};
}
else {
$filename = $$config{'filename'};
my $padded_index = "";
if (exists $$config{'id'} && $$config{'id'} ne "") {
$padded_index = sprintf("%04d", $$config{'id'});
}
$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;
}
sub print_available_pages {
# Load config file
read_config "site.cfg" => my %config;
# Load config file
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
# from the generated list of pages.
@page_args= grep { $_ ne 'DBI' } @page_args;
@page_args= grep { $_ ne 'root_template' } @page_args;
# 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;
foreach my $page_arg (@page_args) {
print "$page_arg\n";
}
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
eps.id,
eps.explicit,
eps.date, eps.license, eps.title, eps.summary,
eps.duration, eps.notes, eps.tags,
hosts.hostid,
hosts.host, hosts.email, hosts.local_image,
miniseries.name AS series, miniseries.id AS seriesid
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date <= date(\'now\')
ORDER BY eps.id + 0 DESC'
<!--% query_episodes = 'SELECT
eps.id,
eps.explicit,
eps.date, eps.license, eps.title, eps.summary,
eps.duration, eps.notes, eps.tags,
hosts.hostid,
hosts.host, hosts.email, hosts.local_image,
miniseries.name AS series, miniseries.id AS seriesid
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date <= date(\'now\')
ORDER BY eps.id + 0 DESC'
%-->

View File

@ -1,23 +1,26 @@
<!--% USE DBI(constants.driver, constants.user, constants.password) %-->
<!--% query_hpr_feed = DBI.prepare('
SELECT
eps.id,
eps.explicit,
DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\',
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date < DATE_ADD(NOW(), INTERVAL 1 DAY)
ORDER BY eps.date DESC
LIMIT 10
SELECT
eps.id,
eps.explicit,
DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\',
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid,
assets.size AS length
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
INNER JOIN assets ON eps.id = assets.episode_id
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) %-->
<!--% query_hpr_feed = DBI.prepare('
SELECT
eps.id,
eps.explicit,
strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date,
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date < date(\'now\', \'+1 days\')
ORDER BY eps.date DESC
LIMIT 10
SELECT
eps.id,
eps.explicit,
strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date,
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid,
assets.size AS length
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
INNER JOIN assets ON eps.id = assets.episode_id
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) %-->
<!--% query_hpr_feed = DBI.prepare('
SELECT
eps.id,
eps.explicit,
DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\',
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date < DATE_ADD(NOW(), INTERVAL 1 DAY)
ORDER BY eps.date DESC
SELECT
eps.id,
eps.explicit,
DATE_FORMAT(eps.date, \'%H:%i:%S %d:%m:%Y\') AS \'date\',
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid,
assets.size AS length
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
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) %-->
<!--% query_hpr_feed = DBI.prepare('
SELECT
eps.id,
eps.explicit,
strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date,
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
WHERE eps.date < date(\'now\', \'+1 days\')
ORDER BY eps.date DESC
SELECT
eps.id,
eps.explicit,
strftime(\'%H:%M:%S %d:%m:%Y\', date(eps.date)) AS date,
eps.license, eps.duration,
eps.title, eps.summary, eps.tags,
eps.notes,
hosts.local_image,
hosts.hostid,
hosts.host, hosts.email,
miniseries.name AS series, miniseries.id AS seriesid,
assets.size AS length
FROM eps
INNER JOIN hosts ON eps.hostid = hosts.hostid
INNER JOIN miniseries ON eps.series = miniseries.id
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 %-->
<!--% PROCESS 'shared-utils.tpl.html' %-->
<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0"
xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"
xmlns:atom="http://www.w3.org/2005/Atom"
<rss version="2.0"
xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"
xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" >
<channel>
<title>Hacker Public Radio</title>
@ -22,14 +22,14 @@
<itunes:author>Hacker Public Radio</itunes:author>
<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>
<managingEditor>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</managingEditor>
<managingEditor>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</managingEditor>
<!-- <author>feedback@NOSPAM-hackerpublicradio.org (HPR Feedback)</author> -->
<itunes:owner>
<itunes:owner>
<itunes:name>HPR Volunteer</itunes:name>
<itunes:email>admin@hackerpublicradio.org</itunes:email>
</itunes:owner>
<webMaster>admin@hackerpublicradio.org (HPR Volunteer)</webMaster>
<generator>site-generator</generator>
<webMaster>admin@hackerpublicradio.org (HPR Volunteer)</webMaster>
<generator>site-generator</generator>
<docs>http://www.rssboard.org/rss-specification</docs>
<ttl>43200</ttl>
<skipDays>
@ -39,7 +39,7 @@
<image>
<url>https://www.hackerpublicradio.org/images/hpr_feed_small.png</url>
<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>
<height>164</height>
<width>144</width>
@ -51,7 +51,7 @@
<googleplay:category text="Technology"/>
<atom:link href="https://www.hackerpublicradio.org/<!--% filename %-->" rel="self" type="application/rss+xml" />
<pubDate><!--% format_feed_date(date.now) %--></pubDate>
<!--% INCLUDE $content %-->
</channel>
</rss>

View File

@ -12,7 +12,11 @@ from the series <em><a href="<!--% baseurl %-->series/<!--% zero_pad_left(series
<!--% END %-->
<!--% 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 %-->
<!--% MACRO display_listen_in(eps_id, episode_type) BLOCK %-->

View File

@ -1,23 +1,24 @@
<!--% PROCESS 'shared-utils.tpl.html' %-->
<!--% MACRO display_item(episode, file_extension, audio_mime_type) BLOCK %-->
<!--% USE HTML.Strip %-->
<!--% IF audio_mime_type == "" %-->
<!--% audio_mime_type = 'ogg' %-->
<!--% END %-->
<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>
<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>
<googleplay:author><!--% episode.email %--> (<!--% episode.host %-->)</googleplay:author>
<itunes:author><!--% episode.email %--> (<!--% episode.host %-->)</itunes:author>
<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>
<itunes:summary><![CDATA[<!--% episode.notes %-->]]>
<itunes:summary><![CDATA[<!--% episode.notes.substr(0, 4000) | html_strip | xml_entity %-->]]>
</itunes:summary>
<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 %-->"/>
<guid>http://hackerpublicradio.org/eps/hpr<!--% zero_pad_left(episode.id) %-->.<!--% file_extension %--></guid>
<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>
</item>
<!--% END %-->

View File

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