diff --git a/Makefile b/Makefile index ac26916..a6825e8 100644 --- a/Makefile +++ b/Makefile @@ -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/ diff --git a/cap2site.pl b/cap2site.pl new file mode 100755 index 0000000..6d3c7a4 --- /dev/null +++ b/cap2site.pl @@ -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', + + + 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 .= "&"; continue; } + if ($byte eq "<") { $newstr .= "<"; continue; } + if ($byte eq ">") { $newstr .= ">"; continue; } + if ($byte eq "\"") { $newstr .= """; continue; } + if ($byte eq "'") { $newstr .= "'"; 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 '', "\n"; + print '', "\n"; + print '', "\n", $config->head, '', "\n"; + print '', "\n"; + } + + while (my $line = <$file>) { + chop $line; + + if (index($line, '*') == 0 and not $state->bullets) { + $state->bullets() = 1; + print "\n"; + } + + if ($state->preformatted) { + if (index($line, ' ') == 0) { + $state->preformatted() = 0; + print "\n"; + } else { print escape($line); } + + continue; + } + + if (index($line, '###') == 0) { + print "

", escape(substr $line, 3), "

\n"; + } + + elsif (index($line, '##') == 0) { + print "

", escape(trimLeft(substr $line, 2)), "

\n"; + } + + elsif (index($line, '#') == 0) { + print "

", escape(trimLeft(substr $line, 2)), "

\n"; + } + + elsif (index($line, ' ') == 0) { + $state->preformatted = 1; + my $alt = substr $line, 3; + + length($alt) == 0 + ? print "
\n"
+        : print '
', "\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 '';
+        print '', escape($content), '';
+        print '', "\n";
+      }
+
+      elsif ($config->isUrl($uri) and $config->isA($uri, 'video')) {
+        print '', "\n";
+      }
+
+      elsif ($config->isUrl($uri) and $config->isA($uri, 'audio')) {
+        print '', "\n";
+      }
+
+      elsif ($config->isUrl($uri)) {
+        print '', escape($content), '', "\n";
+      }
+
+      else {
+        print '

', $line, '

', "\n"; + } + } + + elsif (index($line, '*') == 0) { + print '
  • ', escape(trimLeft(substr $line, 1)), '
  • ', "\n"; + } + + elsif (index($line, '>') == 0) { + print '
    ', escape(trimLeft(substr $line, 1)), '
    ', "\n"; + } + + else { + print '

    '; + print (length($line) == 0 ? '
    ' : escape $line); + print '

    ', "\n"; + } + } + + if ($config->{standalone}) { + print '', "\n"; + print '', "\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 diff --git a/cap2site.pod b/cap2site.pod deleted file mode 100755 index 6d3c7a4..0000000 --- a/cap2site.pod +++ /dev/null @@ -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', - - - 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 .= "&"; continue; } - if ($byte eq "<") { $newstr .= "<"; continue; } - if ($byte eq ">") { $newstr .= ">"; continue; } - if ($byte eq "\"") { $newstr .= """; continue; } - if ($byte eq "'") { $newstr .= "'"; 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 '', "\n"; - print '', "\n"; - print '', "\n", $config->head, '', "\n"; - print '', "\n"; - } - - while (my $line = <$file>) { - chop $line; - - if (index($line, '*') == 0 and not $state->bullets) { - $state->bullets() = 1; - print "\n"; - } - - if ($state->preformatted) { - if (index($line, ' ') == 0) { - $state->preformatted() = 0; - print "
    \n"; - } else { print escape($line); } - - continue; - } - - if (index($line, '###') == 0) { - print "

    ", escape(substr $line, 3), "

    \n"; - } - - elsif (index($line, '##') == 0) { - print "

    ", escape(trimLeft(substr $line, 2)), "

    \n"; - } - - elsif (index($line, '#') == 0) { - print "

    ", escape(trimLeft(substr $line, 2)), "

    \n"; - } - - elsif (index($line, ' ') == 0) { - $state->preformatted = 1; - my $alt = substr $line, 3; - - length($alt) == 0 - ? print "
    \n"
    -        : print '
    ', "\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 '';
    -        print '', escape($content), '';
    -        print '', "\n";
    -      }
    -
    -      elsif ($config->isUrl($uri) and $config->isA($uri, 'video')) {
    -        print '', "\n";
    -      }
    -
    -      elsif ($config->isUrl($uri) and $config->isA($uri, 'audio')) {
    -        print '', "\n";
    -      }
    -
    -      elsif ($config->isUrl($uri)) {
    -        print '', escape($content), '', "\n";
    -      }
    -
    -      else {
    -        print '

    ', $line, '

    ', "\n"; - } - } - - elsif (index($line, '*') == 0) { - print '
  • ', escape(trimLeft(substr $line, 1)), '
  • ', "\n"; - } - - elsif (index($line, '>') == 0) { - print '
    ', escape(trimLeft(substr $line, 1)), '
    ', "\n"; - } - - else { - print '

    '; - print (length($line) == 0 ? '
    ' : escape $line); - print '

    ', "\n"; - } - } - - if ($config->{standalone}) { - print '', "\n"; - print '', "\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 diff --git a/cap2site.pod b/cap2site.pod new file mode 120000 index 0000000..1df13f3 --- /dev/null +++ b/cap2site.pod @@ -0,0 +1 @@ +cap2site.pl \ No newline at end of file