| git.druid.rocks | index | druid520 | btf | btf.sh |
btf.sh
#!/usr/bin/env sh
set -e
full=0
outdir="html"
while getopts "fo:" opt; do
case $opt in f) full=1 ;; o) outdir="$OPTARG" ;; *) exit 1 ;; esac
done
shift $((OPTIND-1))
dir="$1"
[ -z "$dir" ] && { echo "err: bad usage." >&2; exit 2; }
[ ! -d "$dir" ] && { echo "err: $dir: no such dir." >&2; exit 1; }
style_file="$dir/style.btfs"
[ ! -f "$style_file" ] && { echo "err: $style_file: not found." >&2; exit 1; }
_p=$(mktemp)
trap 'rm -f "$_p"' EXIT
cat > "$_p" <<'PERL'
use strict;
use warnings;
my ($style_file, $content_file, $full) = @ARGV;
$full = ($full eq '1');
sub tok {
my ($line) = @_;
my ($pos, @toks) = (0);
while ($pos < length $line) {
my $ch = substr($line, $pos, 1);
if ($ch eq '\\') {
$pos++;
push @toks, substr($line, $pos, 1) if $pos < length $line;
$pos++;
} elsif ($ch eq '[') {
my $e = index($line, ']', $pos);
if ($e == -1) { push @toks, '['; $pos++; }
else {
my $t = substr($line, $pos+1, $e-$pos-1);
$pos = $e+1;
if ($t eq 'e') { push @toks, {t=>'c'}; }
elsif ($t eq '/nav') { push @toks, {t=>'cnav'}; }
elsif ($t =~ /^([a-z][a-z0-9-]*)/) {
my $name = $1;
my %a;
while ($t =~ /([a-z][a-z0-9-]*)="([^"]*)"/g) { $a{$1} = $2; }
push @toks, {t=>'o',n=>$name,a=>\%a};
}
}
} else {
my $s = $pos;
while ($pos < length $line && substr($line,$pos,1) ne '[' && substr($line,$pos,1) ne '\\') { $pos++; }
push @toks, substr($line, $s, $pos-$s);
}
}
return @toks;
}
my $css = '';
my @css_stk;
sub close_css {
my $top = pop @css_stk;
if ($top->{t} eq 'r') { $css .= "}\n"; }
elsif ($top->{t} eq 'm') { $css .= "}\n"; }
elsif ($top->{t} eq 'p') { $css .= " $top->{n}: $top->{c};\n"; }
}
{ open my $fh, '<', $style_file or die "cannot open $style_file: $!";
my ($in_comment, $skip_depth) = (0, 0);
while (<$fh>) {
chomp;
for my $tok (tok($_)) {
if (ref $tok eq 'HASH') {
if ($tok->{t} eq 'c') {
if ($in_comment) {
if ($skip_depth) { $skip_depth--; next; }
$in_comment = 0; next;
}
if (@css_stk) { close_css(); next; }
next;
}
if ($in_comment) { $skip_depth++; next; }
if ($tok->{t} eq 'o') {
if ($tok->{n} eq 'comment') { $in_comment = 1; $skip_depth = 0; }
elsif ($tok->{n} eq 'rule') { $css .= "$tok->{a}{sel} {\n"; push @css_stk, {t=>'r'}; }
elsif ($tok->{n} eq 'media') { $css .= "\@media $tok->{a}{q} {\n"; push @css_stk, {t=>'m'}; }
elsif ($tok->{n} eq 'prop') { push @css_stk, {t=>'p',n=>$tok->{a}{n},c=>''}; }
}
} elsif (!$in_comment && @css_stk && $css_stk[-1]{t} eq 'p') { $css_stk[-1]{c} .= $tok; }
}
}
close $fh;
close_css() while @css_stk;
}
my (%tagmap) = (b=>'strong',i=>'em',s=>'del',url=>'a',list=>'ul',numlist=>'ol',quote=>'blockquote','list-item'=>'li');
my %is_block = map { $_=>1 } qw(h1 h2 h3 h4 h5 h6 p code list numlist quote table thead tbody tr td mathblock);
sub h { my $s=shift; return '' unless defined $s; $s =~ s/&/&/g; $s =~ s/</</g; $s =~ s/>/>/g; $s =~ s/"/"/g; $s; }
my ($html, @stk, $po, $in_comment, $skip_depth, $in_nav) = ('');
sub cp { if ($po) { if ($html =~ /<p>\s*$/) { $html =~ s/<p>\s*$//; } else { $html .= "</p>"; } $po = 0; } }
sub in_code { grep { $_->{n} eq 'code' } @stk }
sub ot {
my ($n,$a,$no_p) = @_;
cp() if $is_block{$n};
if ($n eq 'hr') { $html .= "<hr>"; return; }
if ($n eq 'br') { $html .= "<br>"; return; }
my $blk = $is_block{$n} || 0;
if (!$blk && !$po && !$no_p && !grep { $_->{b} } @stk) { $html .= "<p>"; $po = 1; }
push @stk, {n=>$n,a=>$a,b=>$blk};
if ($n eq 'img') { }
elsif ($n eq 'code' && $a->{lang}) { $html .= "<pre><code class=\"lang-$a->{lang}\">"; }
elsif ($n eq 'code') { $html .= "<pre><code>"; }
elsif ($n eq 'url') { $html .= "<a href=\"".h($a->{url} // '')."\">"; }
else {
my $t = $tagmap{$n} // $n;
$html .= "<$t";
for my $k (sort keys %$a) { $html .= " $k=\"$a->{$k}\"" if $k ne 'lang' && $k ne 'n'; }
$html .= ">";
}
}
sub ct {
my $p = pop @stk;
my $t = $tagmap{$p->{n}} // $p->{n};
if ($p->{n} eq 'img') { $html .= "<img src=\"".($p->{a}{src} // '')."\" alt=\"".h($p->{c})."\">"; }
elsif ($p->{n} eq 'code') { $html .= "</code></pre>"; }
else { $html .= "</$t>"; }
}
open my $cfh, '<', $content_file or die "cannot open $content_file: $!";
while (<$cfh>) {
chomp;
if ($_ eq '') {
cp() unless grep { $_->{b} } @stk;
next;
}
for my $tok (tok($_)) {
if (ref $tok eq 'HASH') {
if ($tok->{t} eq 'c') {
if ($in_comment) {
if ($skip_depth) { $skip_depth--; next; }
$in_comment = 0; next;
}
if ($in_nav) { ct() if @stk; next; }
ct() if @stk;
} elsif ($tok->{t} eq 'cnav') {
if ($in_nav) { $html .= '</nav>'; $in_nav = 0; }
next;
} elsif ($tok->{t} eq 'o') {
if ($tok->{n} eq 'comment') { $in_comment = 1; $skip_depth = 0; next; }
if ($in_comment) { $skip_depth++; next; }
if ($tok->{n} eq 'nav') { $in_nav = 1; $po = 0; $html .= '<nav id="tabs">'; next; }
if ($in_nav) {
next if $is_block{$tok->{n}};
ot($tok->{n}, $tok->{a}, 1);
next;
}
ot($tok->{n}, $tok->{a});
}
} elsif ($in_nav) {
$html .= h($tok);
} elsif (!$in_comment) {
my $in_b = grep { $_->{b} } @stk;
if (!$in_b && !$po) { $html .= "<p>"; $po = 1; }
if (@stk && $stk[-1]{n} eq 'img') { $stk[-1]{c} .= $tok; }
elsif (in_code()) { $html .= h($tok); }
elsif (!@stk || $stk[-1]{n} ne 'hr') { $html .= h($tok); }
}
}
if (in_code()) { $html .= "\n"; }
}
close $cfh;
cp();
if ($full) {
my $a = chr(60);
my $z = chr(62);
my $tag = $a . 'a href="https://git.druid.rocks/druid520/btf.git"' . $z;
my $hdr = $a . 'div id="btf-hdr"' . $z . '<small>do not edit — generated by ' . $tag . 'btf' . $a . '/a' . $z . '.</small>' . $a . '/div' . $z . "\n";
my $ftr = $a . 'div id="btf-ftr"' . $z . '<small>' . $tag . 'powered by btf.' . $a . '/a' . $z . '</small>' . $a . '/div' . $z . "\n";
print "<!DOCTYPE html>\n<html>\n<head>\n<style>\n$css</style>\n</head>\n<body>\n$hdr$html$ftr</body>\n</html>\n";
} else { print $html; }
PERL
find "$dir" -type f -name '*.btft' | while read -r src; do
rel="${src#$dir/}"
mkdir -p "$outdir/$(dirname "$rel")"
perl "$_p" "$style_file" "$src" "$full" > "$outdir/${rel%.btft}.html"
done