#!/usr/bin/env perl
#
# Convert darcs changes into RSS 1.0
#
# Version 0.6, 2005/02/07 Pedro Melo <melo@simplicidade.org>
#

use strict;
$^W = 1; # "use warnings" for dysfunctional older Perls
use XML::RSS;
use XML::Parser;
use Getopt::Long;
use Config::Simple;

#
# Configuration hash
#

my %config;

# Defaults for the main options
# Your project name and how many changes to use
$config{project} = "Netrek Vanilla Server";
$config{n_items} = 25;

# Where is markdown?
$config{markdown_path} = 'Markdown.pl';

################################################ Nothing to edit below

my $result = 0;
my %cmd_line_opts;
eval {
  # If you change any options here, also update them in the usage docs below. 
  $result = GetOptions(
    'project|title|p=s' => \$cmd_line_opts{project},
    'homepage|link|h=s' => \$cmd_line_opts{homepage},
    'description|d=s'   => \$cmd_line_opts{description},
    'url_cgi|u=s'       => \$cmd_line_opts{patch_cgi},
    'n_items|n=i'       => \$cmd_line_opts{n_items},
    'darcs|c=s'         => \$cmd_line_opts{darcs_cmd},
    'filter|f=s'        => \$cmd_line_opts{filter},
    'markdown|m=s'      => \$cmd_line_opts{markdown_path},
  )
};

my $repo = shift || '.';

my @files = (
    '/etc/darcs2rss/config',
    "$ENV{HOME}/.darcs2rss",
    "$repo/.darcs2rss",
);

foreach my $cfg (@files) {
  Config::Simple->import_from($cfg, \%config) if -f $cfg && -r _;
}

while (my ($key, $value) = each %cmd_line_opts) {
  $config{$key} = $value if defined $value;
}

$config{darcs_cmd} ||= 'darcs';
$config{filter}    ||= 'basic';

my $filter;
if ($config{filter}) {
  if ($config{filter} eq 'markdown') {
    if (!$config{markdown_path}) {
      print STDERR "Error: filter markdown selected, but I need to know where Markdown.pl is\n";
      print STDERR "       use --markdown /path/to/Markdown.pl to fix\n";
      exit(1);
    }
    eval {
      $blosxom::version = 1;
      require $config{markdown_path};
    };
    if ($@) {
      print STDERR "Error: failed loading Markdown module from '$config{markdown_path}'\n";
      exit(1);
    }
    $filter = \&markdown_filter;
  }
  elsif ($config{filter} eq 'pre') {
    $filter = \&pre_filter;
  }
  elsif ($config{filter} eq 'basic') {
    $filter = \&basic_filter;
  }
  else {
    print STDERR "Error: filter type '$config{filter}' is unknown to me. See $0 --help for options \n";
    exit(1);
  }
}

my $cmdline = "$config{darcs_cmd} changes --xml-output";
$cmdline .= " --last $config{n_items}" if $config{n_items};

my $err_msg;
if (!$result) {
  $err_msg = "\n"; # Getopt::Long already printed something to STDERR
}
elsif (!chdir($repo)) {
  $err_msg = "could not chdir into repository '$repo': $!";
}
elsif (!-r '_darcs') {
  $err_msg = "$repo is not a darcs repo. _darcs does not exist or is not readable";
}
elsif (`$config{darcs_cmd} --commands 2>&1` !~ m/rollback/) {
  $err_msg = "'$config{darcs_cmd}' does not appear to be a working darcs binary";
}
elsif (!open(CHANGES, "$cmdline |")) {
  $err_msg = "could not execute '$config{darcs_cmd}': $!\n"
}

if ($err_msg) { 
    warn "$err_msg\n\n";
    die <<USAGE
Usage: 

  # From the root of a darcs repo
  darcs2rss > /path/to/rss_file

  # From outside a repo
  darcs2rss /path/to/your/repo > /path/to/rss_file

Options:

  --project (also --title and -p): project name
  --homepage (also --link and -h): project homepage
  --description (also -d): project description
  --url_cgi (also -u): URL of darcs.cgi script
  --n_items (also -n): number of changes to include, default 25, 0 for unlimited
  --darcs (also -c): darcs command to use, default search PATH for 'darcs'
  --filter (also -f): filter comments - see available filters below
  --markdown (also -m): Markdown.pl location (mandatory if filter is markdown)

All the long options are case-insensitive and can be abbreviated to
uniqueness. So you can use --description or --desc but not --d because
it could mean --darcs

The following filters are available:
  - basic: convert line breaks and encode HTML entities if possible
  - markdown: filters your comment with Markdown
  - pre: puts your comment between <pre></pre>

If you want to use Markdown, you must specify the Markdown.pl location
with --markdown.

USAGE
}

my $rss = XML::RSS->new( version => '1.0' );

$rss->channel(
  title => $config{project},
  link  => $config{homepage} || '#',
  description => $config{description} || ' ',
);


my $ctx = {};
my $parser = XML::Parser->new(
    Handlers => {
      Start => sub { _handle_start( $ctx, @_ ) },
      End   => sub { _handle_end( $ctx, @_ )   },
      Char  => sub { _handle_char( $ctx, @_ )  },
    },
);
$parser = $parser->parse_start;

while(<CHANGES>) {
  $parser->parse_more($_);
}
$parser->parse_done;


print $rss->as_string;


#################################################
# XML::Parser handlers

sub _handle_start {
  my $ctx = shift;
  my $expat = shift;
  my $elem = shift;
 
  if ($elem eq 'patch') {
    $ctx->{attrs} = { @_ };
  }
  elsif ($elem eq 'name' || $elem eq 'comment') {
    $ctx->{inside} = $elem;
  }
}


sub _handle_end {
  my $ctx = shift;
  my $expat = shift;
  my $elem = shift;

  if ($elem eq 'patch') {
    my $link = "";
    $link = "$config{patch_cgi}?c=annotate&p=$ctx->{attrs}{hash}" if $config{patch_cgi};

    my $item_date;
    if ($ctx->{attrs}{date} =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) {
      $item_date = "$1-$2-$3T$4:$5:$6+00:00"; # TODO: fix timezone info
    }

    my $comment = $ctx->{comment};
    if ($filter && $comment) {
      $comment = $filter->($comment);
    }
    
    $rss->add_item(
      title => $ctx->{name} || "no title",
      # default to '#' because a link is required in the RSS spec
      link  => $link || '#',
      description => $comment,

      dc => {
        subject => "Patches",
        creator => $ctx->{attrs}{author} || "no author",
        date    => $item_date,
      },
    );

    delete $ctx->{attrs};
    delete $ctx->{name};
    delete $ctx->{comment};
  }
  elsif ($elem eq 'name' || $elem eq 'comment') {
    delete $ctx->{inside};
  }
}


sub _handle_char {
  my $ctx = shift;
  my $expat = shift;
  
  if ($ctx->{inside}) {
    $ctx->{$ctx->{inside}} .= shift;
  }
}


#################################################
# Filters available for comments

sub markdown_filter {
  return Markdown::Markdown(shift);
}


sub pre_filter {
  my $comment = shift;
  return "<pre>$comment</pre>";
}

sub basic_filter {
  my $comment = shift;

  eval { require HTML::Entities; };
  if ($@) {
      #  we could encode more HTML characters if HTML::Entities was installed;
  }
  else {
      import HTML::Entities;
      $comment = encode_entities($comment)
  }

    # To handle all kinds of line endings. See Programming Perl, 3rd Ed, page 623
  $comment =~ s/\015?\012/\n/g;

  # translate two or more line breaks to a paragraph break
  $comment =~ s#\n\n+#</p><p>#g;

  # translate a single line break to <BR>
  $comment =~ s#\n#<br/>#g;

  return "<p>$comment</p>";
}