#!/usr/bin/env perl
use strict;
use v5.10;

=head1 NAME

pandoc-filter - execute Pandoc filters listed on metadata field C<filters>

=head1 DESCRIPTION

This Pandoc filter executes other filters listed in document metadata field
C<filters>. Each filter must be an executable files or have known file
extension and either be given by absolute or relative path or given by name in
C<~/.pandoc/filters> or C<$PATH>.

=head1 SYNOPSIS

  pandoc --filter pandoc-filters -o output.html < input.md

In C<input.md>:

  ---
  filters:
    - filter1        # in $PATH or ~/.pandoc/filters
    - ./filter2.pl   # relative path
    - /path/filter3  # absolute path
  ...

=head1 OPTIONS

=over

=item --help|-h

Print this help.

=item --to|--write|-t|-w <format>

Print help in given Pandoc output format, e.g. C<markdown>.

=item --about

Print purpose of this script in one line.

=item --list|-l

List all filters in C<~/.pandoc/filters>, optionally with purpose (for those
supporting option C<--about>.

=back

=cut

use Pandoc::Elements qw(pandoc_json MetaList);
use IPC::Cmd qw(can_run);
use Getopt::Long;
use Pandoc::Filter::Usage;
use IPC::Run3;

my $filters_dir = $ENV{HOME} . '/.pandoc/filters';

my %opt;
Getopt::Long::GetOptions(\%opt, 
    'help|?', 
    'about',
    'to|write:s',
    'list',
) or $opt{help} = 1;
Pandoc::Filter::Usage::frompod(\%opt);

if ( $opt{list} ) {
    exit unless -d $filters_dir;
    opendir DIR, $filters_dir;
    my @files = grep { !-d "$filters_dir/$_" and -x "$filters_dir/$_" } 
                readdir DIR; 
    for my $file ( @files ) {
        my $out = [];
        run3 ["$filters_dir/$file",'--about'], sub { undef }, $out, \undef;
        chomp($out->[0]);
        say @$out ? "$file: ".$out->[0] : $file;
    }
    closedir DIR;
    exit;
}

our %SCRIPTS = (
    hs => 'runhaskell',
    js => 'node',
    php => 'php',
    pl => 'perl',
    py => 'python',
    rb => 'ruby',
);

sub find_filter {
    my $filter = shift;
    my $script = "$filters_dir/$filter";
    unless (-x $script) {
        if (-e $script and $script =~ /\.([a-z]+)$/i) {
            if ( my $cmd = $SCRIPTS{lc($1)} ) {
                die "cannot execute filter with $cmd\n" unless can_run($cmd);
                return ($cmd, $script);
            }
        }
        $script = can_run($filter);
    }
    return $script ? ($script) : ();
}

# now the actual filter...

my $ast     = pandoc_json(<STDIN>);
my $filters = $ast->meta->{filters} // MetaList [];
my $format  = $ARGV[0] // '';

foreach my $filter ( map { $_->metavalue } @{$filters->content} ) {
    my @script = find_filter($filter);
    die "filter not found: $filter\n" unless @script;

    my $stdin  = $ast->to_json;
    my $stdout = "";
    my $stderr = "";

    run3 [@script, $format], \$stdin, \$stdout, \$stderr;
    if ($?) {
        $stderr .= "\n" if $stderr ne '' and $stderr !~ /\n\z/s;
        die "filter failed: $filter\n$stderr";
    }

    eval { $ast = pandoc_json($stdout) };
    die "filter emited no valid JSON: $filter\n" if $@;
}

say $ast->to_json;
