cap2site.pod -> cap2site.pl

This commit is contained in:
Danielle Hutzley 2023-03-20 16:26:57 -05:00
parent 691f1498ff
commit 973af154e1
3 changed files with 254 additions and 253 deletions

View File

@ -2,10 +2,10 @@
test:
./t/script.pl
cap2site.1: cap2site.pod
cap2site.1: cap2site.pl
pod2man $< > $@
install: cap2site.1
install -T cap2site.pod /bin/cap2site
install -T cap2site.pl /bin/cap2site
cp cap2site.1 /usr/share/man/man1/

251
cap2site.pl Executable file
View File

@ -0,0 +1,251 @@
#!/usr/bin/env perl
use v5.26;
use strict;
use warnings;
# Data
package cap2site::Config;
use Class::Struct;
struct('Config', => {
inline => '%',
extensions => '%',
web_schemes => '@',
standalone => '$',
head => '$',
});
our $DEFAULT = Config->new(
inline => {
audio => 1,
video => 1,
image => 1,
},
extensions => {
audio => [".mp3", ".wav", ".ogg"],
video => [".mp4", ".mkv"],
image => [".png", ".jpeg", ".jpg", ".gif", ".webp"],
},
web_schemes => ["http", "https", "mailto", "gemini"],
standalone => 0,
head => <<~'EOF',
<meta charset="utf-8"/>
<link rel="stylesheet" href="style.css"></link>
EOF
);
sub isUrl($$) {
my ($self,$data) = @_;
my ($scheme,$info) =~ /(\w+):\/\/([.*]+)/ || return 0;
return grep $scheme, $self->{web_schemes};
}
sub isA($$$) {
my ($self,$url,$type) = @_;
my ($info) =~ /[.*](.[^.]+)^/ || return 0;
return grep $self->extensions->{$type}, $url;
}
package cap2site::State;
sub new($$) {
my ($class,$args) = @_;
my $self = bless { inner => $args }, $class;
return $self;
}
sub bullets($) {
my $self = shift;
return vec $self->{inner}, 0, 1;
}
sub preformatted($) {
my $self = shift;
return vec $self->{inner}, 1, 1;
}
# Helpers
package main;
my sub escape($) {
my $str = shift;
my $newstr = '';
foreach my $byte (split '', $str) {
if ($byte eq "8") { $newstr .= "&amp"; continue; }
if ($byte eq "<") { $newstr .= "&lt"; continue; }
if ($byte eq ">") { $newstr .= "&gt"; continue; }
if ($byte eq "\"") { $newstr .= "&quot"; continue; }
if ($byte eq "'") { $newstr .= "&#39"; continue; }
$newstr .= $byte;
}
return $newstr;
}
my sub trimLeft($) {
my $data = shift;
$data =~ s/^\s+//;
return $data;
}
sub parse($$$) {
my ($config,$state,$file) = @_;
if ($config->{standalone}) {
print '<!DOCTYPE html>', "\n";
print '<html>', "\n";
print '<head>', "\n", $config->head, '</head>', "\n";
print '<body>', "\n";
}
while (my $line = <$file>) {
chop $line;
if (index($line, '*') == 0 and not $state->bullets) {
$state->bullets() = 1;
print "<ul>\n";
} elsif ($state->bullets and index($line, '*') != 0) {
$state->bullets() = 0;
print "</ul>\n";
}
if ($state->preformatted) {
if (index($line, ' ') == 0) {
$state->preformatted() = 0;
print "</pre>\n";
} else { print escape($line); }
continue;
}
if (index($line, '###') == 0) {
print "<h3>", escape(substr $line, 3), "</h3>\n";
}
elsif (index($line, '##') == 0) {
print "<h2>", escape(trimLeft(substr $line, 2)), "</h2>\n";
}
elsif (index($line, '#') == 0) {
print "<h1>", escape(trimLeft(substr $line, 2)), "</h1>\n";
}
elsif (index($line, ' ') == 0) {
$state->preformatted = 1;
my $alt = substr $line, 3;
length($alt) == 0
? print "<pre>\n"
: print '<pre alt="', escape(trimLeft $alt), '">', "\n";
}
elsif (index($line, '=>') == 0) {
my $data = trimLeft(substr $line, 2);
my ($uri,$content) = split " ". $data;
$content = trimLeft $content;
if ($config->isUrl($uri) and $config->isA($uri, 'image')) {
print '<a style="display: block;" href="', escape($uri), '">';
print '<img src="', escape($uri), '" alt="', escape($content), '"/>';
print '</a>', "\n";
}
elsif ($config->isUrl($uri) and $config->isA($uri, 'video')) {
print '<video style="display: block;" controls src="', escape($uri), '">';
print '<a src="', escape($uri), '">', escape($content), "</a>";
print '</video>', "\n";
}
elsif ($config->isUrl($uri) and $config->isA($uri, 'audio')) {
print '<audio style="display: block;" controls src="', escape($uri), '">';
print '<a src="', escape($uri), '">', escape($content), "</a>";
print '</audio>', "\n";
}
elsif ($config->isUrl($uri)) {
print '<a style="display: block;" href="', escape($uri), '">', escape($content), '</a>', "\n";
}
else {
print '<p>', $line, '</p>', "\n";
}
}
elsif (index($line, '*') == 0) {
print '<li>', escape(trimLeft(substr $line, 1)), '</li>', "\n";
}
elsif (index($line, '>') == 0) {
print '<blockquote>', escape(trimLeft(substr $line, 1)), '</blockquote>', "\n";
}
else {
print '<p>';
print (length($line) == 0 ? '<br/>' : escape $line);
print '</p>', "\n";
}
}
if ($config->{standalone}) {
print '</body>', "\n";
print '</html>', "\n";
}
}
# TODO: better argument parsing
use Pod::Usage;
use Getopt::Long;
my $help = 0;
our $config = $cap2site::Config::DEFAULT;
GetOptions (
'help+' => \$help,
'inline-audio!' => \$config->inline->{audio},
'inline-video!' => \$config->inline->{video},
'inline-image!' => \$config->inline->{image},
'standalone!' => \$config->{standalone},
) or pod2usage(-exitval => 1, -verbose => 0);
pod2usage(-verbose => $help) if $help;
our $state = cap2site::State->new(pack('b2', 0b00));
main::parse $config, $state, \*STDIN;
__END__
=head1 NAME
cap2site - Convert a Gemini capsule to a HTML site
=head1 SYNOPSIS
cap2site [flags] < INPUT.gmi > OUTPUT.html
Options:
-help brief help message
-hh full help message
-(no)-inline-audio include audio in site
-(no)-inline-video include video in site
-(no)-inline-image include images in site
-standalone include data for headers
=head1 OPTIONS
=over 4
=item B<-help>
Print a brief help message and exit. Use twice for full help
=item B<-(no)-inline-(audio, video, image)>
Include the respective asset types inline
=item B<-(no)-standalone>
Include header and body tags
=back
=cut

View File

@ -1,251 +0,0 @@
#!/usr/bin/env perl
use v5.26;
use strict;
use warnings;
# Data
package cap2site::Config;
use Class::Struct;
struct('Config', => {
inline => '%',
extensions => '%',
web_schemes => '@',
standalone => '$',
head => '$',
});
our $DEFAULT = Config->new(
inline => {
audio => 1,
video => 1,
image => 1,
},
extensions => {
audio => [".mp3", ".wav", ".ogg"],
video => [".mp4", ".mkv"],
image => [".png", ".jpeg", ".jpg", ".gif", ".webp"],
},
web_schemes => ["http", "https", "mailto", "gemini"],
standalone => 0,
head => <<~'EOF',
<meta charset="utf-8"/>
<link rel="stylesheet" href="style.css"></link>
EOF
);
sub isUrl($$) {
my ($self,$data) = @_;
my ($scheme,$info) =~ /(\w+):\/\/([.*]+)/ || return 0;
return grep $scheme, $self->{web_schemes};
}
sub isA($$$) {
my ($self,$url,$type) = @_;
my ($info) =~ /[.*](.[^.]+)^/ || return 0;
return grep $self->extensions->{$type}, $url;
}
package cap2site::State;
sub new($$) {
my ($class,$args) = @_;
my $self = bless { inner => $args }, $class;
return $self;
}
sub bullets($) {
my $self = shift;
return vec $self->{inner}, 0, 1;
}
sub preformatted($) {
my $self = shift;
return vec $self->{inner}, 1, 1;
}
# Helpers
package main;
my sub escape($) {
my $str = shift;
my $newstr = '';
foreach my $byte (split '', $str) {
if ($byte eq "8") { $newstr .= "&amp"; continue; }
if ($byte eq "<") { $newstr .= "&lt"; continue; }
if ($byte eq ">") { $newstr .= "&gt"; continue; }
if ($byte eq "\"") { $newstr .= "&quot"; continue; }
if ($byte eq "'") { $newstr .= "&#39"; continue; }
$newstr .= $byte;
}
return $newstr;
}
my sub trimLeft($) {
my $data = shift;
$data =~ s/^\s+//;
return $data;
}
sub parse($$$) {
my ($config,$state,$file) = @_;
if ($config->{standalone}) {
print '<!DOCTYPE html>', "\n";
print '<html>', "\n";
print '<head>', "\n", $config->head, '</head>', "\n";
print '<body>', "\n";
}
while (my $line = <$file>) {
chop $line;
if (index($line, '*') == 0 and not $state->bullets) {
$state->bullets() = 1;
print "<ul>\n";
} elsif ($state->bullets and index($line, '*') != 0) {
$state->bullets() = 0;
print "</ul>\n";
}
if ($state->preformatted) {
if (index($line, ' ') == 0) {
$state->preformatted() = 0;
print "</pre>\n";
} else { print escape($line); }
continue;
}
if (index($line, '###') == 0) {
print "<h3>", escape(substr $line, 3), "</h3>\n";
}
elsif (index($line, '##') == 0) {
print "<h2>", escape(trimLeft(substr $line, 2)), "</h2>\n";
}
elsif (index($line, '#') == 0) {
print "<h1>", escape(trimLeft(substr $line, 2)), "</h1>\n";
}
elsif (index($line, ' ') == 0) {
$state->preformatted = 1;
my $alt = substr $line, 3;
length($alt) == 0
? print "<pre>\n"
: print '<pre alt="', escape(trimLeft $alt), '">', "\n";
}
elsif (index($line, '=>') == 0) {
my $data = trimLeft(substr $line, 2);
my ($uri,$content) = split " ". $data;
$content = trimLeft $content;
if ($config->isUrl($uri) and $config->isA($uri, 'image')) {
print '<a style="display: block;" href="', escape($uri), '">';
print '<img src="', escape($uri), '" alt="', escape($content), '"/>';
print '</a>', "\n";
}
elsif ($config->isUrl($uri) and $config->isA($uri, 'video')) {
print '<video style="display: block;" controls src="', escape($uri), '">';
print '<a src="', escape($uri), '">', escape($content), "</a>";
print '</video>', "\n";
}
elsif ($config->isUrl($uri) and $config->isA($uri, 'audio')) {
print '<audio style="display: block;" controls src="', escape($uri), '">';
print '<a src="', escape($uri), '">', escape($content), "</a>";
print '</audio>', "\n";
}
elsif ($config->isUrl($uri)) {
print '<a style="display: block;" href="', escape($uri), '">', escape($content), '</a>', "\n";
}
else {
print '<p>', $line, '</p>', "\n";
}
}
elsif (index($line, '*') == 0) {
print '<li>', escape(trimLeft(substr $line, 1)), '</li>', "\n";
}
elsif (index($line, '>') == 0) {
print '<blockquote>', escape(trimLeft(substr $line, 1)), '</blockquote>', "\n";
}
else {
print '<p>';
print (length($line) == 0 ? '<br/>' : escape $line);
print '</p>', "\n";
}
}
if ($config->{standalone}) {
print '</body>', "\n";
print '</html>', "\n";
}
}
# TODO: better argument parsing
use Pod::Usage;
use Getopt::Long;
my $help = 0;
our $config = $cap2site::Config::DEFAULT;
GetOptions (
'help+' => \$help,
'inline-audio!' => \$config->inline->{audio},
'inline-video!' => \$config->inline->{video},
'inline-image!' => \$config->inline->{image},
'standalone!' => \$config->{standalone},
) or pod2usage(-exitval => 1, -verbose => 0);
pod2usage(-verbose => $help) if $help;
our $state = cap2site::State->new(pack('b2', 0b00));
main::parse $config, $state, \*STDIN;
__END__
=head1 NAME
cap2site - Convert a Gemini capsule to a HTML site
=head1 SYNOPSIS
cap2site [flags] < INPUT.gmi > OUTPUT.html
Options:
-help brief help message
-hh full help message
-(no)-inline-audio include audio in site
-(no)-inline-video include video in site
-(no)-inline-image include images in site
-standalone include data for headers
=head1 OPTIONS
=over 4
=item B<-help>
Print a brief help message and exit. Use twice for full help
=item B<-(no)-inline-(audio, video, image)>
Include the respective asset types inline
=item B<-(no)-standalone>
Include header and body tags
=back
=cut

1
cap2site.pod Symbolic link
View File

@ -0,0 +1 @@
cap2site.pl