From 5bfc0945b06b4951e7cbae4f859c85afde9231b5 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Wed, 14 Aug 2024 10:39:13 +0300 Subject: bin: track rem2html --- .local/bin/rem2html | 790 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 790 insertions(+) create mode 100755 .local/bin/rem2html diff --git a/.local/bin/rem2html b/.local/bin/rem2html new file mode 100755 index 0000000..f298fb3 --- /dev/null +++ b/.local/bin/rem2html @@ -0,0 +1,790 @@ +#!/usr/bin/env perl +# SPDX-License-Identifier: GPL-2.0-only + +use strict; +use warnings; + +use Getopt::Long; +use JSON::MaybeXS; + +my %Options; + +my $rem2html_version = '2.1'; + +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" to HTML + +=head1 NAME + +rem2html - Convert the output of "remind -pp" to HTML + +=head1 SYNOPSIS + + remind -pp ... | rem2html [options] + +You can also use the old interchange format as below, but the -pp +version is preferred. + + remind -p ... | rem2html [options] + +=head1 OPTIONS + +=over 4 + +=item --help, -h + +Print usage information + +=item --version + +Print version + +=item --backurl I + +When producing the small calendar for the previous month, make the +month name a link to I. + +=item --forwurl I + +When producing the small calendar for the next month, make the +month name a link to I. + +=item --imgbase I + +When creating URLs for the stylesheet or external images, use I +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 + +Use I as the stylesheet. If this option is used, +I is interpreted relative to B I it starts +with a "/". + +=item --nostyle + +Produce basic HTML that does not use a CSS stylesheet. + +=item --tableonly + +Output results as a EtableE ... E/tableE sequence only +without any EhtmlE or EbodyE tags. + +=item --title I + +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, SHARE, COLOR + +The standard SPECIALs supported by all back-ends + +=back + +=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" + +Usage: remind -pp ... | rem2html [options] + +Options: + +--help, -h Print usage information +--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>... 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", + "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); + } +} + +sub start_output +{ + return if ($Options{tableonly}); + + print("\n"); + print("\n\n" . $Options{title} . "\n"); + print('' . "\n"); + print('' . "\n"); + if (!$Options{nostyle}) { + if ($Options{stylesheet}) { + print('' . "\n"); + } else { + print("\n"); + } + } + print("\n\n"); + if ($Options{prologue}) { + print $Options{prologue} . "\n"; + } +} + +sub end_output +{ + return if ($Options{tableonly}); + if ($Options{epilogue}) { + print $Options{epilogue} . "\n"; + } + print("\n\n"); +} + +sub parse_input +{ + undef $days; + undef $shades; + undef $moons; + undef $classes; + undef $weeks; + + my $found_data = 0; + while() { + chomp; + last if /^\# rem2ps2? begin$/; + } + + my $line; + # Month Year numdays firstday monday_first_flag + $line = ; + return 0 unless $line; + chomp($line); + ($Month, $Year, $Numdays, $Firstwkday, $Mondayfirst) = split(' ', $line); + + $Month =~ s/_/ /g; + # Day names + $line = ; + return 0 unless $line; + chomp($line); + @Daynames = split(' ', $line); + + for (my $i=0; $i<7; $i++) { + $Daynames[$i] =~ s/_/ /g; + } + + # Prevmon prevlen + $line = ; + return 0 unless $line; + chomp($line); + ($Prevmon, $Prevlen) = split(' ', $line); + $Prevmon =~ s/_/ /g; + + # Nextmon nextlen + $line = ; + 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() { + chomp; + last if /^\# rem2ps2? end$/; + next if /^\#/; + my ($y, $m, $d, $special, $tag, $duration, $time, $body); + 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); + } elsif (/\{/) { + my $obj = decode_json($_); + next unless ($obj->{date} =~ /^(\d+)-(\d+)-(\d+)$/); + $y = $1; + $m = $2; + $d = $3; + $special = $obj->{passthru} || '*'; + $tag = $obj->{tags} || '*'; + $duration = $obj->{duration} || '*'; + $time = $obj->{time} || '*'; + $body = $obj->{body}; + } 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; + } 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'} = ''; + } + } 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+(.*)$/) { + 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]}, "" . escape_html($text) . '

'); + } + } elsif ($special eq '*') { + push(@{$days->[$d]}, "" . escape_html($body) . '

'); + } + } + return $found_data; +} + +sub small_calendar +{ + my($month, $monlen, $url, $first_col) = @_; + if ($Mondayfirst) { + $first_col--; + if ($first_col < 0) { + $first_col = 6; + } + } + + if ($Options{nostyle}) { + print "\n"; + print "\n"; + print "\n"; +} + +sub output_calendar +{ + # Which column is 1st of month in? + my $first_col = $Firstwkday; + if ($Mondayfirst) { + $first_col--; + if ($first_col < 0) { + $first_col = 6; + } + } + + # Last column + my $last_col = ($first_col + $Numdays - 1) % 7; + + # Figure out how many rows + my $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++; + } + + # Start the table + my $class; + if ($Options{nostyle}) { + print '
"; + } else { + print "
\n"; + print "\n"; + print "\n"; + + my $class; + if ($Options{nostyle}) { + print ''; + $class = ' align="right"'; + } else { + print ''; + $class = ' class="rem-sc-hdr"'; + } + if (!$Mondayfirst) { + print "" . substr($Daynames[0], 0, 1) . ''; + } + for (my $i=1; $i<7; $i++) { + print "" . substr($Daynames[$i], 0, 1) . ''; + } + if ($Mondayfirst) { + print "" . substr($Daynames[0], 0, 1) . ''; + } + print("\n"); + my $col = 0; + for (; $col<$first_col; $col++) { + if ($col == 0) { + print("\n"); + } + if ($Options{nostyle}) { + print(""); + } else { + print(""); + } + } + + for (my $day=1; $day <= $monlen; $day++) { + if ($col == 0) { + print("\n"); + } + $col++; + if ($Options{nostyle}) { + print(""); + } else { + print(""); + } + if ($col == 7) { + print("\n"); + $col = 0; + } + } + if ($col) { + while ($col < 7) { + if ($Options{nostyle}) { + print(""); + } else { + print(""); + } + $col++; + } + print("\n"); + } + print("
"; + } + print "" if ($url); + print $month; + print "" if ($url); + print "
  
$day$day
  
\n"); + print "
' . "\n"; + print ''; + $class = ' width="14%"'; + } else { + print '
' . + $Month . ' ' . $Year . '
' . "\n"; + print ''; + $class = ' class="rem-cal-hdr"'; + } + if (!$Mondayfirst) { + print "" . $Daynames[0] . ''; + } + for (my $i=1; $i<7; $i++) { + print "" . $Daynames[$i] . ''; + } + if ($Mondayfirst) { + print "" . $Daynames[0] . ''; + } + print "\n"; + + # Start the calendar rows + my $col = 0; + if ($Options{nostyle}) { + print "\n"; + } else { + print "\n"; + } + 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(" \n"); + $col++; + } + + for (my $day=1; $day<=$Numdays; $day++) { + draw_day_cell($day, $number_of_rows); + $col++; + if ($col == 7) { + $col = 0; + print "\n"; + if ($day < $Numdays) { + if ($Options{nostyle}) { + print "\n"; + } else { + print "\n"; + } + } + } + } + + if ($col) { + while ($col < 7) { + if ($col == 5) { + if ($first_col == 0) { + small_calendar($Prevmon, $Prevlen, $Options{backurl}, + ($Firstwkday - $Prevlen + 35) % 7); + } else { + print(" \n"); + } + } elsif ($col == 6) { + small_calendar($Nextmon, $Nextlen, $Options{forwurl}, + ($Firstwkday + $Numdays) % 7); + } else { + print(" \n"); + } + $col++; + } + print "\n"; + } + + # Add a row for small calendars if they were not yet done! + if ($first_col == 0 && $last_col == 6) { + if ($Options{nostyle}) { + print "\n"; + } else { + print "\n"; + } + small_calendar($Prevmon, $Prevlen, $Options{backurl}, + ($Firstwkday - $Prevlen + 35) % 7); + for (my $i=0; $i<5; $i++) { + print(" \n"); + } + small_calendar($Nextmon, $Nextlen, $Options{forwurl}, + ($Firstwkday + $Numdays) % 7); + print("\n"); + } + # End the table + print "
' . + $Month . ' ' . $Year . '
\n"; +} + +sub draw_day_cell +{ + my($day, $number_of_rows) = @_; + my $shade = $shades->[$day]; + my $week = ''; + if (exists($weeks->{$day})) { + $week = ' ' . $weeks->{$day}; + } + 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 "\n"; + } else { + print "\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 = '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 = '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 = '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 = 'Last Quarter'; + } + if ($Options{nostyle}) { + print("
\"$alt\"$msg
"); + } else { + print("
\"$alt\"$msg
"); + } + } + + if ($Options{nostyle}) { + print "
$day$week
\n"; + print "

 

\n"; + } else { + print "
$day$week
\n"; + } + if ($days->[$day]) { + print(join("\n", @{$days->[$day]})); + } + + print "\n"; +} + +sub escape_html +{ + my($in) = @_; + $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(); +} +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 +} -- cgit v1.2.3-70-g09d2