mirror of
https://salsa.debian.org/dskoll/remind.git
synced 2026-04-16 06:18:47 +02:00
1123 lines
31 KiB
Perl
1123 lines
31 KiB
Perl
#!perl
|
|
# SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use Getopt::Long;
|
|
use JSON::MaybeXS;
|
|
use Encode;
|
|
|
|
my %Options;
|
|
|
|
my $Translations = {};
|
|
|
|
my $rem2html_version = '@VERSION@';
|
|
|
|
my($days, $shades, $moons, $classes, $Month, $Year, $Numdays, $Firstwkday, $Mondayfirst, $weeks,
|
|
@Daynames, $Nextmon, $Nextlen, $Prevmon, $Prevlen);
|
|
|
|
my $TIDY_PROGNAME = $0;
|
|
$TIDY_PROGNAME =~ s|^.*/||;
|
|
|
|
# rem2html -- convert the output of "remind -pp" or "remind -ppp" to HTML
|
|
|
|
=head1 NAME
|
|
|
|
rem2html - Convert the output of "remind -pp" or "remind -ppp" to HTML
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
remind -ppp [remind_options] file | rem2html [options]
|
|
|
|
remind -pp [remind_options] file | rem2html [options]
|
|
|
|
You can also use the old interchange format as below, but the -pp
|
|
or -ppp versions are preferred.
|
|
|
|
remind -p [remind_options] file | rem2html [options]
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 4
|
|
|
|
=item --help, -h
|
|
|
|
Print usage information
|
|
|
|
=item --version
|
|
|
|
Print version
|
|
|
|
=item --utf8
|
|
|
|
Assume standard input is encoded in UTF-8; write UTF-8 data to standard
|
|
output.
|
|
|
|
=item --backurl I<url>
|
|
|
|
When producing the small calendar for the previous month, make the
|
|
month name a link to I<url>.
|
|
|
|
=item --forwurl I<url>
|
|
|
|
When producing the small calendar for the next month, make the
|
|
month name a link to I<url>.
|
|
|
|
=item --imgbase I<url>
|
|
|
|
When creating URLs for the stylesheet or external images, use I<url>
|
|
as the base URL.
|
|
|
|
=item --pngs
|
|
|
|
Normally, rem2html uses inline "data:" URLs for the moon phase images,
|
|
yielding a standalone HTML file. The C<--pngs> option makes it use
|
|
external images named firstquarter.png, fullmoon.png, lastquarter.png
|
|
and newmoon.png, which are expected to live in C<--imgbase>.
|
|
|
|
=item --stylesheet I<url.css>
|
|
|
|
Use I<url.css> as the stylesheet. If this option is used,
|
|
I<url.css> is interpreted relative to B<imgbase> I<unless> it starts
|
|
with a "/".
|
|
|
|
=item --nostyle
|
|
|
|
Produce basic HTML that does not use a CSS stylesheet.
|
|
|
|
=item --tableonly
|
|
|
|
Output results as a E<lt>tableE<gt> ... E<lt>/tableE<gt> sequence only
|
|
without any E<lt>htmlE<gt> or E<lt>bodyE<gt> tags.
|
|
|
|
=item --title I<title>
|
|
|
|
Use I<title> as the content between E<lt>titleE<gt> and E<lt>/titleE<gt>
|
|
tags.
|
|
|
|
|
|
=item --prologue I<html_text>
|
|
|
|
Insert I<html_text> right after the E<lt>bodyE<gt> tag.
|
|
|
|
=item --epilogue I<html_text>
|
|
|
|
Insert I<html_text> right before the E<lt>/bodyE<gt> tag.
|
|
|
|
=back
|
|
|
|
=head1 SPECIALS SUPPORTED
|
|
|
|
The rem2html back-end supports the following SPECIAL reminders:
|
|
|
|
=over
|
|
|
|
=item HTML
|
|
|
|
Add an HTML reminder to the calendar. All HTML tags are available.
|
|
|
|
=item HTMLCLASS
|
|
|
|
Add a CSS class to the box representing the trigger date. See
|
|
"HIGHLIGHTING TODAY" for an example
|
|
|
|
=item WEEK, MOON, SHADE, COLOR
|
|
|
|
The standard SPECIALs supported by all back-ends
|
|
|
|
=back
|
|
|
|
=head1 INFO STRINGS SUPPORTED
|
|
|
|
Rem2html supports one INFO string, namely C<Url>. Its value
|
|
should be a URL. If the C<Url> INFO string is supplied for
|
|
a normal reminder, a COLOR special, a MOON special or a WEEK
|
|
special, the corresponding output is turned into a hyperlink.
|
|
|
|
=head1 HIGHLIGHTING TODAY
|
|
|
|
Older versions of rem2html used to highlight today's date with a red outline.
|
|
The current version does not do that by default. If you wish to highlight
|
|
today's date, add the following reminder to your reminders file:
|
|
|
|
REM [realtoday()] SPECIAL HTMLCLASS rem-today
|
|
|
|
=head1 AUTHOR
|
|
|
|
rem2html was written by Dianne Skoll with much inspiration from an
|
|
earlier version by Don Schwarz.
|
|
|
|
=head1 HOME PAGE
|
|
|
|
L<https://dianne.skoll.ca/projects/remind/>
|
|
|
|
=head1 SEE ALSO
|
|
|
|
B<remind>, B<rem2ps>, B<rem2pdf>, B<tkremind>
|
|
=cut
|
|
|
|
sub usage
|
|
{
|
|
my ($exit_status) = @_;
|
|
if (!defined($exit_status)) {
|
|
$exit_status = 1;
|
|
}
|
|
print STDERR <<"EOM";
|
|
$TIDY_PROGNAME: Produce an HTML calendar from the output of "remind -pp[p]"
|
|
|
|
Usage: remind -pp [remind_options] file | rem2html [options]
|
|
or: remind -ppp [remind_options] file | rem2html [options]
|
|
|
|
Options:
|
|
|
|
--help, -h Print usage information
|
|
--utf8 Assume UTF-8 input and write UTF-8 output
|
|
--man Show man page (requires "perldoc")
|
|
--version Print version
|
|
--backurl url Make the title on the previous month's small calendar
|
|
entry a link to <url>
|
|
--forwurl url Same as --backurl, but for the next month's small calendar
|
|
--imgbase url Base URL of images and default stylesheet file
|
|
--pngs Use external .PNG images for moon phases rater than
|
|
inline data: URLs
|
|
--stylesheet url.css URL of CSS stylesheet. If specified, imgbase is NOT
|
|
prepended to url.css
|
|
--nostyle Produce basic HTML that does not use a CSS stylesheet
|
|
--tableonly Output results as a <table> only, no <html>, <body>, etc.
|
|
--title string What to put in <title>...</title> tags
|
|
--prologue html_text Text to insert at the top of the body
|
|
--epilogue html_text Text to insert at the end of the body
|
|
EOM
|
|
exit($exit_status);
|
|
}
|
|
|
|
sub smoosh
|
|
{
|
|
my ($first, $second) = @_;
|
|
return $second unless defined ($first);
|
|
return $second if $first eq '';
|
|
return $second if ($second =~ m|^/|); # Absolute path given for second
|
|
|
|
# Squash multiple slashes
|
|
$first =~ s|/+|/|g;
|
|
|
|
# Special case
|
|
return "/$second" if ($first eq '/');
|
|
|
|
# Delete trailing slash
|
|
$first =~ s|/$||;
|
|
|
|
return "$first/$second";
|
|
}
|
|
|
|
sub parse_options
|
|
{
|
|
local $SIG{__WARN__} = sub { print STDERR "$TIDY_PROGNAME: $_[0]\n"; };
|
|
if (!GetOptions(\%Options, "help|h",
|
|
"man",
|
|
"utf8",
|
|
"pngs",
|
|
"version",
|
|
"stylesheet=s",
|
|
"nostyle",
|
|
"backurl=s",
|
|
"forwurl=s",
|
|
"title=s",
|
|
"prologue=s",
|
|
"epilogue=s",
|
|
"imgbase=s",
|
|
"tableonly")) {
|
|
usage(1);
|
|
}
|
|
$Options{title} ||= 'HTML Calendar';
|
|
|
|
my $stylesheet = $Options{stylesheet};
|
|
if ($stylesheet) {
|
|
$Options{stylesheet} = smoosh($Options{imgbase}, $stylesheet);
|
|
}
|
|
if ($Options{utf8}) {
|
|
binmode(STDIN, ':encoding(UTF-8)');
|
|
binmode(STDOUT, ':encoding(UTF-8)');
|
|
}
|
|
}
|
|
|
|
sub start_output
|
|
{
|
|
return if ($Options{tableonly});
|
|
|
|
print("<html>\n<head>\n");
|
|
if ($Options{utf8}) {
|
|
print '<meta charset="UTF-8">' . "\n";
|
|
}
|
|
|
|
print("<title>" . $Options{title} . "</title>\n");
|
|
if (!$Options{nostyle}) {
|
|
if ($Options{stylesheet}) {
|
|
print('<link rel="stylesheet" type="text/css" href="' .
|
|
$Options{stylesheet} . '">' . "\n");
|
|
} else {
|
|
print("<style>\n");
|
|
print default_stylesheet();
|
|
print("</style>\n");
|
|
}
|
|
}
|
|
print("</head>\n<body>\n");
|
|
if ($Options{prologue}) {
|
|
print $Options{prologue} . "\n";
|
|
}
|
|
}
|
|
|
|
sub end_output
|
|
{
|
|
return if ($Options{tableonly});
|
|
if ($Options{epilogue}) {
|
|
print $Options{epilogue} . "\n";
|
|
}
|
|
print("</body>\n</html>\n");
|
|
}
|
|
|
|
sub slurp_translations
|
|
{
|
|
my $line;
|
|
|
|
$line = <STDIN>;
|
|
chomp $line;
|
|
eval {
|
|
if ($Options{utf8}) {
|
|
$Translations = decode_json(encode('UTF-8', $line, Encode::FB_DEFAULT));
|
|
} else {
|
|
$Translations = decode_json($line);
|
|
}
|
|
};
|
|
if ($@) {
|
|
$Translations = {};
|
|
}
|
|
}
|
|
|
|
sub t
|
|
{
|
|
my ($str) = @_;
|
|
return $Translations->{$str} if exists($Translations->{$str});
|
|
return $str;
|
|
}
|
|
|
|
sub parse_input
|
|
{
|
|
undef $days;
|
|
undef $shades;
|
|
undef $moons;
|
|
undef $classes;
|
|
undef $weeks;
|
|
|
|
my $found_data = 0;
|
|
while(<STDIN>) {
|
|
chomp;
|
|
if ($_ eq '[') {
|
|
return parse_input_ppp();
|
|
}
|
|
if (/# translations/) {
|
|
slurp_translations();
|
|
next;
|
|
}
|
|
last if /^\# rem2ps2? begin$/;
|
|
}
|
|
|
|
my $line;
|
|
# Month Year numdays firstday monday_first_flag
|
|
$line = <STDIN>;
|
|
return 0 unless $line;
|
|
chomp($line);
|
|
($Month, $Year, $Numdays, $Firstwkday, $Mondayfirst) = split(' ', $line);
|
|
|
|
$Month =~ s/_/ /g;
|
|
# Day names
|
|
$line = <STDIN>;
|
|
return 0 unless $line;
|
|
chomp($line);
|
|
@Daynames = split(' ', $line);
|
|
|
|
for (my $i=0; $i<7; $i++) {
|
|
$Daynames[$i] =~ s/_/ /g;
|
|
}
|
|
|
|
# Prevmon prevlen
|
|
$line = <STDIN>;
|
|
return 0 unless $line;
|
|
chomp($line);
|
|
($Prevmon, $Prevlen) = split(' ', $line);
|
|
$Prevmon =~ s/_/ /g;
|
|
|
|
# Nextmon nextlen
|
|
$line = <STDIN>;
|
|
return 0 unless $line;
|
|
chomp($line);
|
|
($Nextmon, $Nextlen) = split(' ', $line);
|
|
$Nextmon =~ s/_/ /g;
|
|
|
|
$found_data = 1;
|
|
my $class;
|
|
if ($Options{nostyle}) {
|
|
$class = '';
|
|
} else {
|
|
$class = ' class="rem-entry"';
|
|
}
|
|
while(<STDIN>) {
|
|
chomp;
|
|
last if /^\# rem2ps2? end$/;
|
|
next if /^\#/;
|
|
my ($y, $m, $d, $special, $tag, $duration, $time, $body, $title);
|
|
my $url_pre = '';
|
|
my $url_post = '';
|
|
my $url;
|
|
if (m/^(\d*).(\d*).(\d*)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s*(.*)$/) {
|
|
($y, $m, $d, $special, $tag, $duration, $time, $body) =
|
|
($1, $2, $3, $4, $5, $6, $7, $8);
|
|
$title = '';
|
|
} elsif (/\{/) {
|
|
my $obj;
|
|
if ($Options{utf8}) {
|
|
$obj = decode_json(encode('UTF-8', $_, Encode::FB_DEFAULT));
|
|
} else {
|
|
$obj = decode_json($_);
|
|
}
|
|
next unless ($obj->{date} =~ /^(\d+)-(\d+)-(\d+)$/);
|
|
if ($obj->{info} && $obj->{info}->{url}) {
|
|
$url = $obj->{info}->{url};
|
|
$url_pre = "<a href=\"$url\">";
|
|
$url_post = "</a>";
|
|
}
|
|
$y = $1;
|
|
$m = $2;
|
|
$d = $3;
|
|
$special = $obj->{passthru} || '*';
|
|
$tag = $obj->{tags} || '*';
|
|
$duration = $obj->{duration} || '*';
|
|
$time = $obj->{time} || '*';
|
|
$body = $obj->{body};
|
|
$title = info_to_title($obj->{info});
|
|
} else {
|
|
next;
|
|
}
|
|
my $d1 = $d;
|
|
$d1 =~ s/^0+//;
|
|
$special = uc($special);
|
|
if ($special eq 'HTML') {
|
|
push(@{$days->[$d]}, $body);
|
|
} elsif ($special eq 'HTMLCLASS') {
|
|
$classes->[$d] = $body;
|
|
} elsif ($special eq 'WEEK') {
|
|
$body =~ s/^\s+//;
|
|
$body =~ s/\s+$//;
|
|
$weeks->{$d1}->{body} = $body;
|
|
$weeks->{$d1}->{url} = $url;
|
|
} elsif ($special eq 'MOON') {
|
|
if ($body =~ /(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
|
|
my ($phase, $moonsize, $fontsize, $msg) = ($1, $2, $3, $4);
|
|
$moons->[$d]->{'phase'} = $phase;
|
|
$moons->[$d]->{'msg'} = $msg;
|
|
} elsif ($body =~ /(\S+)/) {
|
|
$moons->[$d]->{'phase'} = $1;
|
|
$moons->[$d]->{'msg'} = '';
|
|
}
|
|
$moons->[$d]->{url} = $url;
|
|
} elsif ($special eq 'SHADE') {
|
|
if ($body =~ /(\d+)\s+(\d+)\s+(\d+)/) {
|
|
$shades->[$d] = sprintf("#%02X%02X%02X",
|
|
($1 % 256), ($2 % 256), ($3 % 256));
|
|
} elsif ($body =~ /(\d+)/) {
|
|
$shades->[$d] = sprintf("#%02X%02X%02X",
|
|
($1 % 256), ($1 % 256), ($1 % 256));
|
|
}
|
|
} elsif ($special eq 'COLOR' || $special eq 'COLOUR') {
|
|
if ($body =~ /(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/s) {
|
|
my($r, $g, $b, $text) = ($1, $2, $3, $4);
|
|
my $color = sprintf("style=\"color: #%02X%02X%02X;\"",
|
|
$r % 256, $g % 256, $b % 256);
|
|
push(@{$days->[$d]}, "<p$class$title $color>" . $url_pre . fix_whitespace(escape_html($text)) . $url_post . '</p>');
|
|
}
|
|
} elsif ($special eq '*') {
|
|
push(@{$days->[$d]}, "<p$class$title>" . $url_pre . fix_whitespace(escape_html($body)) . $url_post . '</p>');
|
|
}
|
|
}
|
|
return $found_data;
|
|
}
|
|
|
|
sub parse_input_ppp
|
|
{
|
|
my $json = "[\n";
|
|
my $curlies = 0;
|
|
my $did_a_calendar = 0;
|
|
while(<STDIN>) {
|
|
$json .= $_;
|
|
$curlies++ if ($_ eq "{\n");
|
|
$curlies-- if ($_ eq "}\n");
|
|
$curlies-- if ($_ eq "},\n");
|
|
if ($_ eq "]\n" && !$curlies) {
|
|
my $array;
|
|
eval {
|
|
if ($Options{utf8}) {
|
|
$array = decode_json(encode('UTF-8', $json, Encode::FB_DEFAULT));
|
|
} else {
|
|
$array = decode_json($json);
|
|
}
|
|
};
|
|
if (!$array) {
|
|
print STDERR "Could not decode JSON.\n";
|
|
exit(1);
|
|
}
|
|
if (!$did_a_calendar) {
|
|
start_output();
|
|
$did_a_calendar = 1;
|
|
}
|
|
if (exists($array->[0]{caltype}) &&
|
|
$array->[0]{caltype} eq 'weekly') {
|
|
emit_ppp_calendars($array, 'weekly');
|
|
} else {
|
|
emit_ppp_calendars($array, 'monthly');
|
|
}
|
|
$json = '';
|
|
}
|
|
}
|
|
if (!$did_a_calendar) {
|
|
print STDERR "$TIDY_PROGNAME: Could not find any calendar data on STDIN.\n";
|
|
exit(1);
|
|
} else {
|
|
end_output();
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
sub emit_ppp_calendars
|
|
{
|
|
my ($array, $type) = @_;
|
|
foreach my $cal (@$array) {
|
|
emit_one_ppp_calendar($cal, $type);
|
|
}
|
|
}
|
|
|
|
sub info_to_title
|
|
{
|
|
my ($info) = @_;
|
|
if (!$info) {
|
|
return '';
|
|
}
|
|
my $done_one = 0;
|
|
my $title = '';
|
|
foreach my $key ('location', 'description') {
|
|
next unless $info->{$key};
|
|
if ($done_one) {
|
|
$title .= "
";
|
|
}
|
|
$done_one = 1;
|
|
my $val = escape_html($info->{$key});
|
|
$val =~ s/\n/
/g;
|
|
$val =~ s/"/"/g;
|
|
$val =~ s/</</g;
|
|
$title .= ucfirst($key) . ': ' . $val;
|
|
}
|
|
return " title=\"$title\"";
|
|
}
|
|
|
|
sub emit_one_ppp_calendar
|
|
{
|
|
my ($c, $type) = @_;
|
|
|
|
undef $days;
|
|
undef $shades;
|
|
undef $moons;
|
|
undef $classes;
|
|
undef $weeks;
|
|
|
|
my $dates_to_day_index;
|
|
my $cols_to_date_info;
|
|
if (exists($c->{translations})) {
|
|
$Translations = $c->{translations};
|
|
}
|
|
if ($type eq 'monthly') {
|
|
$Month = $c->{monthname};
|
|
$Year = $c->{year};
|
|
$Numdays = $c->{daysinmonth};
|
|
$Firstwkday = $c->{firstwkday};
|
|
$Mondayfirst = $c->{mondayfirst};
|
|
@Daynames = @{$c->{daynames}};
|
|
$Prevmon = $c->{prevmonthname};
|
|
$Prevlen = $c->{daysinprevmonth};
|
|
$Nextmon = $c->{nextmonthname};
|
|
$Nextlen = $c->{daysinnextmonth};
|
|
} else {
|
|
my $idx = 0;
|
|
$Numdays = 7;
|
|
foreach my $date (@{$c->{dates}}) {
|
|
$Daynames[$idx] = $date->{dayname};
|
|
$dates_to_day_index->{$date->{date}} = $idx;
|
|
$cols_to_date_info->[$idx] = $date;
|
|
$idx++;
|
|
}
|
|
}
|
|
|
|
my $class;
|
|
if ($Options{nostyle}) {
|
|
$class = '';
|
|
} else {
|
|
$class = ' class="rem-entry"';
|
|
}
|
|
foreach my $obj (@{$c->{entries}}) {
|
|
next unless ($obj->{date} =~ /^(\d+)-(\d+)-(\d+)$/);
|
|
my $y = $1;
|
|
my $m = $2;
|
|
my $d = $3;
|
|
my $col;
|
|
if ($type eq 'weekly') {
|
|
$col = $dates_to_day_index->{$obj->{date}};
|
|
} else {
|
|
$col = $d;
|
|
$col =~ s/^0+//;
|
|
}
|
|
my $special = $obj->{passthru} || '*';
|
|
my $tag = $obj->{tags} || '*';
|
|
my $duration = $obj->{duration} || '*';
|
|
my $time = $obj->{time} || '*';
|
|
my $body = $obj->{body};
|
|
my $title = info_to_title($obj->{info});
|
|
my $url_pre = '';
|
|
my $url_post = '';
|
|
my $url = '';
|
|
if ($obj->{info} && $obj->{info}->{url}) {
|
|
$url = $obj->{info}->{url};
|
|
$url_pre = "<a href=\"$url\">";
|
|
$url_post = "</a>";
|
|
}
|
|
$special = uc($special);
|
|
if ($special eq 'HTML') {
|
|
push(@{$days->[$col]}, $body);
|
|
} elsif ($special eq 'HTMLCLASS') {
|
|
$classes->[$col] = $body;
|
|
} elsif ($special eq 'WEEK') {
|
|
$body =~ s/^\s+//;
|
|
$body =~ s/\s+$//;
|
|
$weeks->{$col}->{body} = $body;
|
|
$weeks->{$col}->{url} = $url;
|
|
} elsif ($special eq 'MOON') {
|
|
if ($body =~ /(\S+)\s+(\S+)\s+(\S+)\s+(.*)$/) {
|
|
my ($phase, $moonsize, $fontsize, $msg) = ($1, $2, $3, $4);
|
|
$moons->[$col]->{'phase'} = $phase;
|
|
$moons->[$col]->{'msg'} = $msg;
|
|
} elsif ($body =~ /(\S+)/) {
|
|
$moons->[$col]->{'phase'} = $1;
|
|
$moons->[$col]->{'msg'} = '';
|
|
}
|
|
$moons->[$col]->{url} = $url;
|
|
} elsif ($special eq 'SHADE') {
|
|
if ($body =~ /(\d+)\s+(\d+)\s+(\d+)/) {
|
|
$shades->[$col] = sprintf("#%02X%02X%02X",
|
|
($1 % 256), ($2 % 256), ($3 % 256));
|
|
} elsif ($body =~ /(\d+)/) {
|
|
$shades->[$col] = sprintf("#%02X%02X%02X",
|
|
($1 % 256), ($1 % 256), ($1 % 256));
|
|
}
|
|
} elsif ($special eq 'COLOR' || $special eq 'COLOUR') {
|
|
if ($body =~ /(\d+)\s+(\d+)\s+(\d+)\s+(.*)$/s) {
|
|
my($r, $g, $b, $text) = ($1, $2, $3, $4);
|
|
my $color = sprintf("style=\"color: #%02X%02X%02X;\"",
|
|
$r % 256, $g % 256, $b % 256);
|
|
push(@{$days->[$col]}, "<p$class$title $color>" . $url_pre . fix_whitespace(escape_html($text)) . $url_post . '</p>');
|
|
}
|
|
} elsif ($special eq '*') {
|
|
push(@{$days->[$col]}, "<p$class$title>" . $url_pre . fix_whitespace(escape_html($body)) . $url_post . '</p>');
|
|
}
|
|
}
|
|
output_calendar($type, $cols_to_date_info);
|
|
}
|
|
|
|
sub fix_whitespace
|
|
{
|
|
my ($text) = @_;
|
|
|
|
# Collapse multiple spaces/tabs to a single space
|
|
$text =~ s/[ \t]+/ /gs;
|
|
|
|
# Remove whitespace before/after newlines
|
|
$text =~ s/\s+\n/\n/gs;
|
|
$text =~ s/\n\s+/\n/gs;
|
|
# Collapse multiple newlines to a single newline
|
|
$text =~ s/\n+/\n/gs;
|
|
|
|
# Convert newlines to <br />
|
|
$text =~ s|\n|<br />|g;
|
|
return $text;
|
|
}
|
|
|
|
sub small_calendar
|
|
{
|
|
my($month, $monlen, $url, $first_col) = @_;
|
|
if ($Mondayfirst) {
|
|
$first_col--;
|
|
if ($first_col < 0) {
|
|
$first_col = 6;
|
|
}
|
|
}
|
|
|
|
if ($Options{nostyle}) {
|
|
print "<td width=\"14%\">\n";
|
|
print "<table border=\"0\">\n";
|
|
print "<caption>";
|
|
} else {
|
|
print "<td class=\"rem-small-calendar\">\n";
|
|
print "<table class=\"rem-sc-table\">\n";
|
|
print "<caption class=\"rem-sc-caption\">";
|
|
}
|
|
print "<a href=\"$url\">" if ($url);
|
|
print $month;
|
|
print "</a>" if ($url);
|
|
print "</caption>\n";
|
|
|
|
my $class;
|
|
if ($Options{nostyle}) {
|
|
print '<tr>';
|
|
$class = ' align="right"';
|
|
} else {
|
|
print '<tr class="rem-sc-hdr-row">';
|
|
$class = ' class="rem-sc-hdr"';
|
|
}
|
|
if (!$Mondayfirst) {
|
|
print "<th$class>" . substr($Daynames[0], 0, 1) . '</th>';
|
|
}
|
|
for (my $i=1; $i<7; $i++) {
|
|
print "<th$class>" . substr($Daynames[$i], 0, 1) . '</th>';
|
|
}
|
|
if ($Mondayfirst) {
|
|
print "<th$class>" . substr($Daynames[0], 0, 1) . '</th>';
|
|
}
|
|
print("</tr>\n");
|
|
my $col = 0;
|
|
for (; $col<$first_col; $col++) {
|
|
if ($col == 0) {
|
|
print("<tr>\n");
|
|
}
|
|
if ($Options{nostyle}) {
|
|
print("<td align=\"right\" width=\"14%\"> </td>");
|
|
} else {
|
|
print("<td class=\"rem-sc-empty-cell\"> </td>");
|
|
}
|
|
}
|
|
|
|
for (my $day=1; $day <= $monlen; $day++) {
|
|
if ($col == 0) {
|
|
print("<tr>\n");
|
|
}
|
|
$col++;
|
|
if ($Options{nostyle}) {
|
|
print("<td align=\"right\" width=\"14%\">$day</td>");
|
|
} else {
|
|
print("<td class=\"rem-sc-cell\">$day</td>");
|
|
}
|
|
if ($col == 7) {
|
|
print("</tr>\n");
|
|
$col = 0;
|
|
}
|
|
}
|
|
if ($col) {
|
|
while ($col < 7) {
|
|
if ($Options{nostyle}) {
|
|
print("<td align=\"right\" width=\"14%\"> </td>");
|
|
} else {
|
|
print("<td class=\"rem-sc-empty-cell\"> </td>");
|
|
}
|
|
$col++;
|
|
}
|
|
print("</tr>\n");
|
|
}
|
|
print("</table>\n");
|
|
print "</td>\n";
|
|
}
|
|
|
|
sub output_calendar
|
|
{
|
|
# Which column is 1st of month in?
|
|
my ($type, $date_info) = @_;
|
|
|
|
my ($first_col, $last_col, $number_of_rows);
|
|
|
|
if ($type eq 'monthly') {
|
|
$first_col = $Firstwkday;
|
|
if ($Mondayfirst) {
|
|
$first_col--;
|
|
if ($first_col < 0) {
|
|
$first_col = 6;
|
|
}
|
|
}
|
|
|
|
# Last column
|
|
$last_col = ($first_col + $Numdays - 1) % 7;
|
|
|
|
# Figure out how many rows
|
|
$number_of_rows = int(($first_col + $Numdays ) / 7 + 0.999);
|
|
|
|
# Add a row for small calendars if necessary
|
|
if ($first_col == 0 && $last_col == 6) {
|
|
$number_of_rows++;
|
|
}
|
|
} else {
|
|
$first_col = 0;
|
|
$last_col = 6;
|
|
$number_of_rows = 1;
|
|
}
|
|
|
|
# Start the table
|
|
my $class;
|
|
if ($Options{nostyle}) {
|
|
print '<table width="100%" border="1" cellspacing="0">';
|
|
if ($type eq 'monthly') {
|
|
print "<caption>$Month $Year </caption>";
|
|
}
|
|
print "\n<tr>";
|
|
$class = ' width="14%"';
|
|
} else {
|
|
if ($type eq 'monthly') {
|
|
print '<table class="rem-cal"><caption class="rem-cal-caption">' .
|
|
$Month . ' ' . $Year . '</caption>' . "\n";
|
|
} else {
|
|
print '<table class="rem-cal">' . "\n";
|
|
}
|
|
print '<tr class="rem-cal-hdr-row">';
|
|
$class = ' class="rem-cal-hdr"';
|
|
}
|
|
if ($type eq 'monthly') {
|
|
if (!$Mondayfirst) {
|
|
print "<th$class>" . $Daynames[0] . '</th>';
|
|
}
|
|
for (my $i=1; $i<7; $i++) {
|
|
print "<th$class>" . $Daynames[$i] . '</th>';
|
|
}
|
|
if ($Mondayfirst) {
|
|
print "<th$class>" . $Daynames[0] . '</th>';
|
|
}
|
|
} else {
|
|
for (my $i=0; $i<7; $i++) {
|
|
my $inf = $date_info->[$i];
|
|
print "<th$class>" . $inf->{dayname} . "<br>" .
|
|
$inf->{day} . ' ' .
|
|
$inf->{month} . ' ' .
|
|
$inf->{year} . '</th>';
|
|
}
|
|
}
|
|
|
|
print "</tr>\n";
|
|
# Start the calendar rows
|
|
my $col = 0;
|
|
if ($Options{nostyle}) {
|
|
print "<tr>\n";
|
|
} else {
|
|
print "<tr class=\"rem-cal-row rem-cal-row-$number_of_rows-rows\">\n";
|
|
}
|
|
if ($type eq 'monthly') {
|
|
if ($first_col > 0) {
|
|
small_calendar($Prevmon, $Prevlen, $Options{backurl},
|
|
($Firstwkday - $Prevlen + 35) % 7);
|
|
$col++;
|
|
}
|
|
|
|
if ($last_col == 6 && $first_col > 0) {
|
|
small_calendar($Nextmon, $Nextlen, $Options{forwurl},
|
|
($Firstwkday + $Numdays) % 7);
|
|
$col++;
|
|
}
|
|
}
|
|
if ($Options{nostyle}) {
|
|
$class = ' width="14%"';
|
|
} else {
|
|
$class = ' class="rem-empty rem-empty-$number_of_rows-rows"';
|
|
}
|
|
while ($col < $first_col) {
|
|
print("<td$class> </td>\n");
|
|
$col++;
|
|
}
|
|
|
|
for (my $day=1; $day<=$Numdays; $day++) {
|
|
draw_day_cell($day, $number_of_rows, $type);
|
|
$col++;
|
|
if ($col == 7) {
|
|
$col = 0;
|
|
print "</tr>\n";
|
|
if ($day < $Numdays) {
|
|
if ($Options{nostyle}) {
|
|
print "<tr>\n";
|
|
} else {
|
|
print "<tr class=\"rem-cal-row rem-cal-row-$number_of_rows-rows\">\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($type eq 'monthly') {
|
|
if ($col) {
|
|
while ($col < 7) {
|
|
if ($col == 5) {
|
|
if ($first_col == 0) {
|
|
small_calendar($Prevmon, $Prevlen, $Options{backurl},
|
|
($Firstwkday - $Prevlen + 35) % 7);
|
|
} else {
|
|
print("<td$class> </td>\n");
|
|
}
|
|
} elsif ($col == 6) {
|
|
small_calendar($Nextmon, $Nextlen, $Options{forwurl},
|
|
($Firstwkday + $Numdays) % 7);
|
|
} else {
|
|
print("<td$class> </td>\n");
|
|
}
|
|
$col++;
|
|
}
|
|
print "</tr>\n";
|
|
}
|
|
|
|
# Add a row for small calendars if they were not yet done!
|
|
if ($first_col == 0 && $last_col == 6) {
|
|
if ($Options{nostyle}) {
|
|
print "<tr>\n";
|
|
} else {
|
|
print "<tr class=\"rem-cal-row rem-cal-row-$number_of_rows-rows\">\n";
|
|
}
|
|
small_calendar($Prevmon, $Prevlen, $Options{backurl},
|
|
($Firstwkday - $Prevlen + 35) % 7);
|
|
for (my $i=0; $i<5; $i++) {
|
|
print("<td$class> </td>\n");
|
|
}
|
|
small_calendar($Nextmon, $Nextlen, $Options{forwurl},
|
|
($Firstwkday + $Numdays) % 7);
|
|
print("</tr>\n");
|
|
}
|
|
}
|
|
# End the table
|
|
print "</table>\n";
|
|
if ($type eq 'weekly') {
|
|
print " <br />\n";
|
|
}
|
|
}
|
|
|
|
sub draw_day_cell
|
|
{
|
|
my($day, $number_of_rows, $type) = @_;
|
|
my $shade = $shades->[$day];
|
|
my $week = '';
|
|
if (exists($weeks->{$day})) {
|
|
if ($weeks->{$day}->{url}) {
|
|
$week = ' <a href="' . $weeks->{$day}->{url} . '">' . escape_html($weeks->{$day}->{body}) . '</a>';
|
|
} else {
|
|
$week = ' ' . escape_html($weeks->{$day}->{body});
|
|
}
|
|
}
|
|
my $class;
|
|
if ($Options{nostyle}) {
|
|
$class = $classes->[$day] || '';
|
|
} else {
|
|
$class = $classes->[$day] || "rem-cell rem-cell-$number_of_rows-rows";
|
|
}
|
|
if ($shade) {
|
|
$shade = " style=\"background: $shade;\"";
|
|
} else {
|
|
$shade = "";
|
|
}
|
|
if ($class ne '') {
|
|
print "<td class=\"$class\"$shade>\n";
|
|
} else {
|
|
print "<td valign=\"top\" $shade>\n";
|
|
}
|
|
if ($moons->[$day]) {
|
|
my $phase = $moons->[$day]->{'phase'};
|
|
my $msg = $moons->[$day]->{'msg'};
|
|
$msg ||= '';
|
|
if ($msg ne '') {
|
|
$msg = ' ' . escape_html($msg);
|
|
}
|
|
my $img;
|
|
my $alt;
|
|
my $title;
|
|
if ($phase == 0) {
|
|
if ($Options{pngs}) {
|
|
$img = smoosh($Options{imgbase}, 'newmoon.png');
|
|
} else {
|
|
$img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAC6SURBVDiNpdNNbsIwFATgL0HKolchHKBX6yFaBOEyoPYUabvOIVKJRaCL2JX5TRNGGvnJ8ozGz89cYoElPvET+BX2yivn/1Bggw5HHMKa1h2qcPZC/JEIhvh+brIZIY6sorhMYo9hh3KGFzzfa84NZNjDt9OG/ZcH1BlaPE1IAG0+URhxzNGESKPFaHJs9Q0Ziww7HnvGeXSrJhis0jiFfjwnj3I0WRv+TKtr4hQl3lDrZ6QN9Wt654hfWfGDmBpUwDkAAAAASUVORK5CYII=';
|
|
}
|
|
$title = escape_html(t('New Moon'));
|
|
$alt = 'new';
|
|
} elsif ($phase == 1) {
|
|
if ($Options{pngs}) {
|
|
$img = smoosh($Options{imgbase}, 'firstquarter.png');
|
|
} else {
|
|
$img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADfSURBVDiNndM9TsNAFATgzy5yjZSAE85JBygETgENUPF3iBCitHAFQkcIhZ/Ryn9gRlrZmp2Z3ef3TBOHOMULPrDBMrhpi/4HI5xjix2+4nmJRbx/Yh7ahvkpRPVV4QDXwT3UQy46zGkAZDgK/iytefvHgCrkJsqZUH6cLnNbABSxd5Jhhf1IbkMXv8Qux7hH1Ic1xvk/jBWy6gavumvtwx7ectwZXkKh7MA95XgObeOtpI2U4zl0kGbpxgiPvwQUcXLrKFchc82f6Ur0PK49azOnmOI4TBu84zm4SV38DeIVYkrYJyNbAAAAAElFTkSuQmCC';
|
|
}
|
|
$title = escape_html(t('First Quarter'));
|
|
$alt = '1st';
|
|
} elsif ($phase == 2) {
|
|
if ($Options{pngs}) {
|
|
$img = smoosh($Options{imgbase}, 'fullmoon.png');
|
|
} else {
|
|
$img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADlSURBVDiNrdNBUsJAEAXQlyw4hq4hwWPqTixET6ELkZ16CcAq7oFLqXExjaYgQVNlV/Viev7/6XT/4TjGuME7PiLXUatb8N8xwB12SFjiIXIZtU/MAntEfgvQE4YtHxhiHpjXQ5H7uLhEcaLLAleBvd0Xx9Ha/BdyU+Q5OBV5OKmj7a4YBWdSyNPe4aKHAHkzqcQZNj3JgnNexqE8heyIAulffuFF3kTfIVbBVeu/xoXGGsn2TLJJ/mqkafNiINszySYZdbS90GHlvcgsWktY4TFy7ecxTdvIzahxHQLbyFXUqkPwF2ASRNYgB/PXAAAAAElFTkSuQmCC';
|
|
}
|
|
$alt = 'full';
|
|
$title = escape_html(t('Full Moon'));
|
|
} else {
|
|
if ($Options{pngs}) {
|
|
$img = smoosh($Options{imgbase}, 'lastquarter.png');
|
|
} else {
|
|
$img = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAGQAAABkABchkaRQAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAADmSURBVDiNndMxTsNAEIXhzy5yCyQ6FAgcE7oQheQWUAAl5BIkREoZrgB0GFNkHBl7bURGsryaee/3jHeXdpxjghU+8InXyI0S+n0MMEeBEi+4jfV3vAvMQtsyL0J0j2GtViaeRRMyj8IlsgY8BSijE2Kur/hy09wHKMJrEolhwtwHKDHOsI4OLnoAXfl1jiNsOkR9keE4P8D4q4scbzg5xIxtjie709f1E7siC+9+Gx/8fxvPKtEsklcJSBdgWhcN8ByFR5z+AWgd5QpyE+OUWOJO+zJNU+Z6jHAdgHe7K73CuD5zFT9nCmRDIssCaAAAAABJRU5ErkJggg==';
|
|
}
|
|
$alt = 'last';
|
|
$title = escape_html(t('Last Quarter'));
|
|
}
|
|
my $url_pre = '';
|
|
my $url_post = '';
|
|
if ($moons->[$day]->{url}) {
|
|
$url_pre = '<a href="' . $moons->[$day]->{url} . '">';
|
|
$url_post = '</a>';
|
|
}
|
|
if ($Options{nostyle}) {
|
|
print("<div style=\"float: left\">$url_pre<img border=\"0\" width=\"16\" height=\"16\" alt=\"$alt\" title=\"$title\" src=\"$img\">msg$url_post</div>");
|
|
} else {
|
|
print("<div class=\"rem-moon\">$url_pre<img width=\"16\" height=\"16\" alt=\"$alt\" title=\"$title\" src=\"$img\">$msg$url_post</div>");
|
|
}
|
|
}
|
|
|
|
if ($type eq 'monthly') {
|
|
if ($Options{nostyle}) {
|
|
print "<div style=\"float: right\">$day$week</div>\n";
|
|
print "<p> </p>\n";
|
|
} else {
|
|
print "<div class=\"rem-daynumber\">$day$week</div>\n";
|
|
}
|
|
} else {
|
|
if ($Options{nostyle}) {
|
|
print "<div style=\"float: right\">$week</div>\n";
|
|
print "<p> </p>\n";
|
|
} else {
|
|
print "<div class=\"rem-daynumber\">$week</div>\n";
|
|
}
|
|
}
|
|
if ($days->[$day]) {
|
|
print(join("\n", @{$days->[$day]}));
|
|
}
|
|
|
|
print "</td>\n";
|
|
}
|
|
|
|
sub escape_html
|
|
{
|
|
my($in) = @_;
|
|
$in =~ s/\&/\&/g;
|
|
$in =~ s/\</\</g;
|
|
$in =~ s/\>/\>/g;
|
|
return $in;
|
|
}
|
|
|
|
parse_options();
|
|
if ($Options{help}) {
|
|
usage(0);
|
|
exit(0);
|
|
} elsif ($Options{man}) {
|
|
system("perldoc $0");
|
|
exit(0);
|
|
} elsif ($Options{version}) {
|
|
print "rem2html version $rem2html_version.\n";
|
|
exit(0);
|
|
}
|
|
|
|
if (-t STDIN) { ## no critic
|
|
print STDERR "$TIDY_PROGNAME: Input should not come from a terminal.\n\n";
|
|
usage(1);
|
|
}
|
|
|
|
my $found_something = 0;
|
|
while(1) {
|
|
last if (!parse_input());
|
|
start_output() unless $found_something;
|
|
$found_something = 1;
|
|
output_calendar('monthly', undef);
|
|
}
|
|
if ($found_something) {
|
|
end_output();
|
|
exit(0);
|
|
} else {
|
|
print STDERR "$TIDY_PROGNAME: Could not find any calendar data on STDIN.\n";
|
|
exit(1);
|
|
}
|
|
|
|
sub default_stylesheet
|
|
{
|
|
return <<'EOF';
|
|
table.rem-cal {
|
|
font-family: helvetica, arial, sans-serif;
|
|
font-size: 12pt;
|
|
}
|
|
|
|
table.rem-sc-table {
|
|
font-family: helvetica, arial, sans-serif;
|
|
font-size: 10pt;
|
|
width: 95%;
|
|
float: left;
|
|
}
|
|
|
|
caption.rem-cal-caption {
|
|
font-size: 14pt;
|
|
font-weight: bold;
|
|
}
|
|
|
|
th.rem-cal-hdr {
|
|
width: 14%;
|
|
border-style: solid;
|
|
border-width: 1px;
|
|
vertical-align: top;
|
|
}
|
|
td.rem-empty, td.rem-cell, td.rem-small-calendar {
|
|
width: 14%;
|
|
height: 7em;
|
|
border-style: solid;
|
|
border-width: 1px;
|
|
vertical-align: top;
|
|
}
|
|
td.rem-today {
|
|
width: 14%;
|
|
height: 7em;
|
|
border-style: solid;
|
|
border-width: 2px;
|
|
border-color: #EE3333;
|
|
vertical-align: top;
|
|
}
|
|
|
|
table.rem-cal {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
div.rem-daynumber {
|
|
float: right;
|
|
text-align: right;
|
|
vertical-align: top;
|
|
font-size: 14pt;
|
|
}
|
|
|
|
p.rem-entry {
|
|
clear: both;
|
|
}
|
|
|
|
div.rem-moon {
|
|
float: left;
|
|
text-align: left;
|
|
vertical-align: top;
|
|
}
|
|
|
|
th.rem-sc-hdr {
|
|
text-align: right;
|
|
}
|
|
|
|
td.rem-sc-empty-cell, td.rem-sc-cell {
|
|
text-align: right;
|
|
width: 14%;
|
|
}
|
|
|
|
caption.rem-sc-caption {
|
|
font-size: 12pt;
|
|
}
|
|
EOF
|
|
}
|