#!perl

#
# Output as Format: read from stdin and output it as code in the given format
# https://github.com/sshaw/output-as-format
#
# Based off copy-as-format for Emacs: https://github.com/sshaw/copy-as-format
#
# Copyright (c) 2017-2019 Skye Shaw
#
# This library is free software; you can redistribute it and/or modify
# it under the same terms as Perl itself.
#

use strict;
use warnings;

package Util;

sub html_escape {
  my $text = shift;
  my %escapes = ('&' => '&amp;', '<' => '&lt;');

  $$text =~ s/[&<]/$escapes{$&}/eg;
}

sub indent {
  my ($text, $n) = @_;
  $$text =~ s/^/' ' x $n/gme;
}


# Should just do Markdown->quote, Markdown->list, etc...
package Quote;

sub html {
  my $lines = shift;

  print '<blockquote>',"\n";
  for (@$lines) {
    Util::html_escape(\$_);
    print $_,"\n";
  }
  print '</blockquote>',"\n";
}

sub jira {
  my ($lines, $multiline) = @_;
  if ($multiline) {
    print '{quote}',"\n";
    printf "%s\n", $_ for @$lines;
    print '{quote}',"\n";
  }
  else {
    printf "bq. %s\n", join ' ', @$lines
  }
}

sub orgmode {
  my $lines = shift;
  print "#+BEGIN_QUOTE\n";
  print $_,"\n" for @$lines;
  print "#+END_QUOTE\n";
}

sub markdown {
  my $lines = shift;
  printf "> %s\n", $_ for @$lines;
}

sub output {
  shift;

  my ($format, $multiline) = @_;
  my $lines = [];

  die "unsupported quote format: $format\n" unless $Quote::{$format};

  while (<STDIN>) {
    chomp;
    s/^\s*//g;
    push @$lines, $_;
  }

  $multiline = @$lines > 1 ? 1 : 0 unless defined($multiline);
  $Quote::{$format}->($lines, $multiline);
}

$Quote::{$_} = *markdown for qw(github slack);

package List;

sub html {
  my ($lines, $numbered) = @_;
  my $li = sub {
    for (@$lines) {
      Util::html_escape(\$_);
      printf '<li>%s</li>', $_;
    }
  };

  if ($numbered) {
    print '<ol>';
    $li->();
    print '</ol>';
  }
  else {
    print '<ul>';
    $li->();
    print '</ul>';
  }
}

# mediawiki too
sub markdown {
  my ($lines, $numbered) = @_;
  my $prefix = $numbered ? '1.' : '*';

  printf "%s %s\n", $prefix, $_ for @$lines;
}

sub jira {
  my ($lines, $numbered) = @_;
  my $prefix = $numbered ? '#' : '*';
  printf "%s %s\n", $prefix, $_ for @$lines;
}

sub orgmode {
  my ($lines, $numbered) = @_;
  my $prefix = $numbered ? '1.' : '-';

  printf "- %s\n", $_ for @$lines;
}

sub rst {
  # Bullet lists are the same as Markdown
  return markdown(shift) unless pop;
  my $lines = shift;
  printf "#. %s\n", $_ for @$lines;
}

sub pod {
  # Numbered
  my $lines = shift;
  print '=over 2',"\n";
  printf "=item %s\n" for @$lines;
  print '=back';
}

sub output {
  shift;

  my ($format, $numbered) = @_;
  my $lines = [];

  die "unsupported list format: $format\n" unless $List::{$format};

  while (<STDIN>) {
    chomp;
    s/^\s*//g;
    push @$lines, $_;
  }

  $List::{$format}->($lines, $numbered);
}

$List::{$_} = *markdown for qw(github slack);

package Code;

sub output {
  shift;

  my ($format, $lang, $multiline) = @_;

  my @indents;
  my $snippet = '';

  while (<STDIN>) {
    s/\t/' ' x 8/eg;
    $snippet .= $_;
    push @indents, /^(\s*)/ && length $1;
  }

  exit 1 unless @indents;

  # Remove the minimum amount of leading white space
  my $trim = (sort { $a <=> $b } @indents)[0];
  $snippet =~ s/^\s{$trim}//mg;

  # Remove newline for single line input
  chomp $snippet if @indents == 1;

  $multiline = @indents > 1 ? 1 : 0 unless defined($multiline);

  $Code::{$format}->($snippet, $multiline, $lang);
}

sub disqus {
  my ($snippet, undef, $lang) = @_;
  Util::html_escape(\$snippet);

  printf "<pre><code class='%s'>\n%s</code></pre>\n", $lang || '', $snippet;
}

sub github {
  my ($snippet, $multiline, $lang) = @_;

  if ($multiline) {
    printf "```%s\n%s```\n", $lang || '', $snippet;
  } else {
    printf '`%s`', $snippet;
  }
}

sub hipchat {
  my $snippet = shift;
  printf '/code %s', $snippet;
}

sub html {
  my ($snippet, $multiline, $lang) = @_;
  Util::html_escape(\$snippet);

  if ($multiline) {
    printf "<pre><code>\n%s</code></pre>", $snippet;
  }
  else {
    printf '<code>%s</code>', $snippet;
  }
}

sub jira {
  my ($snippet, $multiline, $lang) = @_;
  if ($multiline) {
    printf "{code%s}\n%s{code}\n", $lang ? ":$lang" : '', $snippet;
  }
  else {
    printf '{{%s}}', $snippet;
  }
}

sub markdown {
  my ($snippet, $multiline) = @_;

  if ($multiline) {
    Util::indent(\$snippet, 4);
    print $snippet;
  }
  else {
    printf '`%s`', $snippet;
  }
}

sub mediawiki {
  my ($snippet, $multiline, $lang) = @_;

  my $markup = sprintf '<syntaxhighlight lang="%s"', $lang || '';
  $markup .= ' inline' unless $multiline;
  $markup .= sprintf ">\n%s</syntaxhighlight>", $snippet;
  print $markup;
}

sub orgmode {
  my ($snippet, $multiline, $lang) = @_;

  if ($multiline) {
    printf "#+BEGIN_SRC %s\n%s\n#+END_SRC\n", $lang || '', $snippet;
  }
  else {
    # Cannot escape '~' within Org-mode's code formatting
    # entities and empty space don't work.
    printf '~%s~', $snippet;
  }
}

sub pod {
  my ($snippet, $multiline, $lang) = @_;

  if ($multiline) {
    Util::indent(\$snippet, 2);
    print $snippet;
  }
  else {
    $snippet =~ s/<</E<lt>E<lt>/g;
    printf 'C<< %s >>', $snippet;
  }
}

sub rst {
  my ($snippet, $multiline, $lang) = @_;

  if ($multiline) {
    Util::indent(\$snippet, 4);
    printf ".. code::\n\n%s\n", $snippet;
  }
  else {
    printf '``%s``', $snippet;
  }
}

sub slack {
  my ($snippet, $multiline) = @_;

  if ($multiline) {
    printf "```\n%s```\n", $snippet;
  } else {
    $snippet =~ s/^\s+|\s+$//g;
    printf '`%s`', $snippet;
  }
}

package main;

use Getopt::Long qw(:config no_ignore_case);

our $VERSION = '0.04';

sub HELP_MESSAGE {
    my $out = shift;

    print $out <<USAGE
usage: oaf [-1mpsLQ] [-f format] [-l lang] [--list[1]] [--quote]
Output stdin according to the given options
  -f FORMAT    Format to output, defaults to markdown
  -h, --help   Display this message
  -l LANG      Programming language of stdin, if supported by FORMAT
  -L, --list   Output a bullet point list using FORMAT, each line is a list item
  -1, --list1  Output a numbered list using FORMAT, each line is a list item
  -m           Force multiline output, if supported by FORMAT
  -p           Print the supported formats and exit
  -Q, --quote  Output as a quote in FORMAT
  -s           Force single line output, if supported by FORMAT
  --version    Print the version
USAGE
}

sub VERSION_MESSAGE {
  my $out = shift;
  print $out "Output as Format v$VERSION\n";
}

my %options;
my %formats = (bitbucket => 'github',
               disqus    => 'disqus',
               github    => 'github',
               gitlab    => 'github',
               hipchat   => 'hipchat',
               html      => 'html',
               jira      => 'jira',
               markdown  => 'markdown',
               mediawiki => 'mediawiki',
               orgmode   => 'orgmode',
               pod       => 'pod',
               rst       => 'rst',
               slack     => 'slack');

GetOptions('f=s'       => \$options{f},
           'h|help'    => sub { HELP_MESSAGE(*STDOUT) and exit },
           'l=s'       => \$options{l},
           'list|L'    => \$options{list},
           'list1|1'   => \$options{list1},
           'm'         => \$options{m},
           'p'         => \$options{p},
           'quote|Q'   => \$options{quote},
           's'         => \$options{s},
           'v|version' => sub { VERSION_MESSAGE(*STDOUT) and exit }
          ) or HELP_MESSAGE(*STDERR) and exit 1;

if ($options{p}) {
  local $\= "\n";

  print for sort keys %formats;

  exit;
}

my $lang = $options{l} || $ENV{OAF_LANG};

my $multiline = $options{m};
$multiline = 0 if $options{s};

# FIXME: use GetOptions for this..?
my $format = $options{f} || $ENV{OAF_FORMAT} || 'markdown';
die "Unknown format: $format\n" unless $formats{$format};
# ======

if ($options{list} || $options{list1}) {
  my $numbered = $options{list1} || 0;
  List->output($formats{$format}, $numbered);
}
elsif ($options{quote}) {
  Quote->output($formats{$format}, $multiline);
}
else {
  Code->output($formats{$format}, $lang, $multiline);
}
