#!/usr/bin/perl # # ========================================================================= # File: gen-bash_completion-svn.pl # # Copyright (c) 2004 Josh Glover # # 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 (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{}