Initial Commit

This commit is contained in:
Danielle Hutzley 2023-03-17 19:35:37 -05:00
commit 2728fe0303
4 changed files with 217 additions and 0 deletions

7
Makefile Normal file
View File

@ -0,0 +1,7 @@
.PHONY: test install
test:
./t/script.pl
install:
install -T cap2site.pod /bin/cap2site

190
cap2site.pod Executable file
View File

@ -0,0 +1,190 @@
#!/usr/bin/env perl
use v5.10;
use strict;
use warnings;
# Data
package cap2site::Config;
use Class::Struct;
struct('Config', => {
inline => '%',
extensions => '%',
web_schemes => '@',
});
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"],
);
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) = @_;
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";
}
}
}
# TODO: better argument parsing
use Pod::Usage;
pod2usage(1) unless $#ARGV < 0;
our $config = $cap2site::Config::DEFAULT;
our $state = cap2site::State->new(pack('b2', 0b00));
main::parse $config, $state, \*STDIN;
__END__
=head1 SYNOPSIS
cap2site < INPUT.gmi > OUTPUT.html

2
t/hello.gmi Normal file
View File

@ -0,0 +1,2 @@
# Hello, world!
Lorem ipsum dolor sit amet

18
t/script.pl Executable file
View File

@ -0,0 +1,18 @@
#!/usr/bin/env perl
use v5.10;
use strict;
use warnings;
sub test($$$) {
my ($cmd,$file,$expected) = @_;
my $res = readpipe($cmd . " < t/" . $file);
$res eq $expected or die "FAIL: $cmd\nEXPECTED:\n$expected\nACTUAL:\n$res";
}
test("./cap2site.pod", "hello.gmi", <<'EOF'
<h1>Hello, world!</h1>
<p>Lorem ipsum dolor sit amet</p>
EOF
);
say "DONE!";