#!/usr/bin/perl
#
# =========================================================================
# File: gen-bash_completion-svn.pl
#
# Copyright (c) 2004 Josh Glover <unix@jmglov.net>
#
# LICENCE:
#
#   This file is distributed under the terms of the BSD-2 License:
#
#   http://www.jmglov.net/opensource/licenses/bsd.txt
#
# DESCRIPTION:
#
#   Generates Bourne shell code to be plugged into bash_completion that
#   implements completion for svn(1) (Subversion).
#
# USAGE:
#
#   gen-bash_completion-svn.pl
#
# TODO:
#
#   - Parse subcommand prototypes and enable advanced completion where
#     applicable (remote repository paths and filenames, revision numbers,
#     etc)
#   - Implement completion for svnadmin(1) and svnlook(1)
#   - Nothing, this code is perfect
#
# DEPENDENCIES:
#
#   Perl 5
#   Subversion
#
# MODIFICATIONS:
#
#   Josh Glover <unix@jmglov.net> (2004/07/25): Initial revision
# =========================================================================

use strict;
use warnings;

use File::Basename;

# Variable: ME
#
# Name of this program, for error messages and the like

our $ME = File::Basename::basename $0;

# Constant: CODE_HDR
#
# Emit the following code at the beginning

use constant CODE_HDR => q/
# svn(1) completion
#
have svn && {

_svn()
{
	local cur count mode i cvsroot cvsroots pwd
	local -a flags miss files entries changed newremoved

	COMPREPLY=()
	cur=${COMP_WORDS[COMP_CWORD]}
/;

# Constant: CODE_FTR
#
# Emit the following code at the end

use constant CODE_FTR => q/
	
	return 0
}
complete -F _svn $default svn
}

/;

# Constant: SUB_HDR
#
# Emit the following code before subcommand case statement

use constant SUB_HDR => q/
	count=0
	for i in ${COMP_WORDS[@]}; do
		[ $count -eq $COMP_CWORD ] && break
		if [ -z "$mode" ]; then
			case $i in
/;

# Constant: SUB_FTR
#
# Emit the following code after subcommand case statement

use constant SUB_FTR => q/			*)
				;;
			esac
		elif [[ "$i" = -* ]]; then
			flags=( ${flags[@]:-} $i )
		fi
		count=$((++count))
	done
/;

# Constant: MODE_HDR
#
# Emit the following code before mode case statement

use constant MODE_HDR => q/
	case "$mode" in
/;

# Constant: MODE_FTR
#
# Emit the following code after mode case statement

use constant MODE_FTR => q/
	*)
		;;
	esac
/;

# Constant: SUB_DOC_HDR
#
# Subcommand header in documentaion

use constant SUB_DOC_HDR => "Available subcommands:";

# Constant: SUB_DOC_FTR
#
# Subcommand footer in documentaion

use constant SUB_DOC_FTR => "";

# Constant: OPT_DOC_HDR
#
# Subcommand header in documentaion

use constant OPT_DOC_HDR => "Valid options:";

# Constant: OPT_DOC_FTR
#
# Subcommand footer in documentaion

use constant OPT_DOC_FTR => "";


MAIN: {

  my $tmp_str;

  $tmp_str = `svn help`
    or die "$ME: 'svn help' generated no output. Is Subversion installed?\n";

  my $found_hdr  = 0;
  my $output_hdr = 0;
  my @modes      = ();
  my @aliases    = ();

  ### Not sure where to grab options, hard-coded for now ###
  my @options = ( "--help", "-h", "-?" );
  ### Not sure where to grab options, hard-coded for now ###

  foreach (split /\n/, $tmp_str) {

    # Look for our header
    unless ($found_hdr) {

      $found_hdr = 1 if $_ eq &SUB_DOC_HDR;
      next;

    } # unless (looking for the header)

    # Look for the end of the subcommands section
    last if $_ eq &SUB_DOC_FTR;

    # If control reaches here, we have a subcommand line

    # Output the code and subcommand case headers unless we have already
    # done so
    unless ($output_hdr) {

      print &CODE_HDR;
      print &SUB_HDR;
      $output_hdr = 1;

    } # unless (outputting code header)

    # Grab the subcommand (which may have aliases)
    /^\s+(\w+)(\s\(.+\))?$/;

    my $sub     =  $1;
    my $aliases =  $2;
    $aliases    =~ s/\s\((.+)\)/$1/ if $aliases;

    # Push this subcommand onto the modes list
    push @modes, $sub;

    # Start the case
    print "\t\t\t";

    # Deal with aliases if we must
    if ($aliases) {

      print "\@($sub|";
      if ($aliases =~ /,/) {

        my @a = split( /,\s/, $aliases );
        print join( "|", @a );
        push @aliases, @a;

      } # if (more than one alias)
      else {

        print $aliases;
        push @aliases, $aliases;

      } # else (just one alias)
      print ")";

    } # if (we have aliases)

    # Otherwise, we have no aliases
    else { print "$sub" }

    # Finish the case
    print ")\n\t\t\t\tmode=$sub\n\t\t\t\t;;\n";

  } # foreach (looking for subcommands)

  # If we have no modes (meaning we found no subcommands), die
  die "$ME: no modes found. Is Subversion installed?\n"
    unless @modes;

  # Print the subcommand case footer and the mode case header
  print &SUB_FTR;
  print &MODE_HDR;

  foreach my $mode (@modes) {

    $tmp_str = `svn help $mode`
      or die "$ME: 'svn $mode' generated no output. Is Subversion installed?\n";

    # Start the case
    print( qq/\t$mode)\n\t\tif [[ "\$cur" == -* ]]; then\n/.
           qq/\t\t\tCOMPREPLY=( \$( compgen -W '/ );

    my $found_hdr = 0;
    my @opts      = ();

    foreach (split /\n/, $tmp_str) {

      # Look for our header
      unless ($found_hdr) {

        $found_hdr = 1 if $_ eq &OPT_DOC_HDR;
        next;

      } # unless (looking for the header)

      # Look for the end of the subcommands section
      last if $_ eq &OPT_DOC_FTR;

      # If control reaches here, we have an option line

      # Grab the option (which may have an alias)
      /^\s+(-[\w\-]+)(\s\[([^\]]+)\])?/ or next;

      push @opts, $1;
      push @opts, $3 if $3;

    } # foreach (looking for options)

    # Close the case
    print join( " ", @opts ).qq/' -- \$cur ) )\n\t\tfi\n\t\t;;\n/;

  } # foreach (traversing modes)

  # Output the no-mode case (which will tab-complete subcommands, aliases, and
  # SVN options)
  print( qq/\t"")\n\t\tCOMPREPLY=( \$( compgen -W '/.
         join( " ", (@modes, @aliases, @options) ).
         qq/' -- \$cur ) )\n\t\t;;\n/ );

  # Output the mode and code footers
  print &MODE_FTR;
  print &CODE_FTR;

} # MAIN{}

