--- /dev/null
+#!/usr/bin/python3
+# AFS Toolset commandline tab completion expander
+# -*- coding: utf-8 -*-
+#
+
+__copyright__ = """
+Copyright (C) 2014 Red Hat, Inc. All Rights Reserved.
+Written by David Howells (dhowells@redhat.com)
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public Licence version 2 as
+published by the Free Software Foundation.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public Licence for more details.
+
+You should have received a copy of the GNU General Public Licence
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+"""
+
+import sys, os
+
+import afs.commands
+from afs.argparse import *
+from afs.lib.output import *
+
+logfile = None
+
+def log(*args):
+ global logfile
+
+ if True:
+ return # Don't actually log
+
+ if not logfile:
+ logfile = open("/tmp/afs_completer.log", "a")
+ for i in args:
+ logfile.write(str(i))
+
+###############################################################################
+#
+# Generate a list of matching cell names
+#
+###############################################################################
+def expand_cell_list(prefix):
+ cells = []
+ for line in open("/etc/openafs/CellServDB", "r", encoding="iso8859_1"):
+ line = str(line.rstrip())
+ if line == "":
+ continue
+ comment_ix = line.find("#")
+ if comment_ix == 0:
+ continue
+ comment = ""
+ if comment_ix > 0:
+ comment = line[comment_ix + 1:].lstrip()
+ line = line[:comment_ix].rstrip()
+ if line[0] == ">":
+ # New cell name
+ if line[1:].startswith(prefix):
+ if line[1:] not in cells:
+ cells.append(line[1:])
+ return cells
+
+###############################################################################
+#
+# Expand the name from a string list
+#
+###############################################################################
+def expand_from_stringlist(comp_words, comp_cword, names):
+ reply = ""
+ for i in names:
+ if i.startswith(comp_words[comp_cword]):
+ reply += " " + i
+ output(reply.lstrip(), "\n")
+ sys.exit(0)
+
+def main():
+ if len(sys.argv) < 3:
+ log("Insufficient arguments\n")
+ sys.exit(2)
+
+ # The word we're trying to expand
+ comp_cword = int(sys.argv[1])
+
+ # The words so far on the command line
+ comp_words = sys.argv[2:]
+
+ log(comp_cword, " ", comp_words, "\n")
+
+ # Determine the command actually being run
+ cmdset = comp_words[0]
+ s = cmdset.rfind("/")
+ if s != -1:
+ cmdset = cmdset[s + 1:]
+ del s
+
+ # If we're starting from the "afs" program, we need to select the command set
+ cmdsets = afs.commands.get_command_sets()
+ #log("Command sets ", cmdsets, "\n")
+ if cmdset == "afs":
+ set_program_name("afs")
+ if comp_cword == 1:
+ expand_from_stringlist(comp_words, comp_cword, cmdsets)
+
+ if len(sys.argv) < 5:
+ log("Insufficient arguments\n")
+ sys.exit(2)
+
+ # Drop the "afs" element of the list
+ comp_words = sys.argv[3:]
+ comp_cword -= 1
+ cmdset = comp_words[0]
+ else:
+ set_program_name(cmdset)
+
+ # The command set must be one we know
+ if cmdset not in cmdsets:
+ error("Unknown command set '", cmdset, "'\n")
+ sys.exit(1)
+
+ cmdsetmod = afs.commands.import_command_set(cmdset)
+ commands = cmdsetmod.get_command_list()
+ commands.append("help")
+ commands.append("apropos")
+ #log("Commands ", commands, "\n")
+ if comp_cword == 1:
+ expand_from_stringlist(comp_words, comp_cword, commands)
+ cmd = comp_words[1]
+
+ # See if the command is in the set
+ found = False
+ for i in commands:
+ if i == cmd:
+ found = cmd
+ break
+ if i.startswith(cmd):
+ if found:
+ error("Command '" + cmd + "' is ambiguous\n")
+ sys.exit(1)
+ found = i
+ if not found:
+ error("Unknown Command '" + cmd + "'\n")
+ sys.exit(1)
+ cmd = found
+
+ # Load the command
+ if cmd == "help":
+ command = __import__("afs.help", globals(), locals(), ['*'])
+ elif cmd == "apropos":
+ command = __import__("afs.apropos", globals(), locals(), ['*'])
+ else:
+ command = cmdsetmod.get_command(cmd)
+ # If it's an alias, then switch to the real module
+ if hasattr(command, "alias"):
+ cmd = command.alias
+ command = cmdsetmod.get_command(cmd)
+
+ # Find the command's argument description
+ arglist = command.command_arguments
+
+ if hasattr(command, "cant_combine_arguments"):
+ cant_combine_arguments = command.cant_combine_arguments
+ else:
+ cant_combine_arguments = {}
+
+ # Discard the first two words of the nascent command line
+ comp_words = comp_words[2:]
+ comp_cword -= 2
+
+ # Ignore any arguments after the one being expanded
+ if comp_cword < len(comp_words) - 1:
+ comp_words = comp_words[0:comp_cword + 1]
+
+ # Determine a list of canonicalised flag names and initialise an array to
+ # keep track of the non-flag argument types.
+ #
+ # An argument beginning with a dash that directly follows on from a flag
+ # that takes an argument is assumed to be an argument, not a flag.
+ canon_flags = []
+ word_types = []
+ additional_args = []
+ skip_flag = False
+ for i in range(0, len(comp_words)):
+ word = comp_words[i]
+ if word.startswith("-") and not skip_flag:
+ switch = word[1:]
+ match = None
+ for j in arglist:
+ if j[0] == switch:
+ match = j
+ break
+ if j[0].startswith(switch):
+ if match:
+ # Ambiguous
+ match = None
+ break
+ match = j
+ if match:
+ log("CANON ", switch, "\n")
+ canon_flags.append(match)
+ if match[2] != "fn":
+ skip_flag = True
+ else:
+ canon_flags.append(None)
+ word_types.append(None)
+ else:
+ canon_flags.append(None)
+ word_types.append(None)
+ skip_flag = False
+ additional_args.append(False)
+ del i, j, word, switch, match, skip_flag
+
+ # We need to eliminate any arguments that have already been used
+ argnames = []
+ for i in arglist:
+ argnames.append(i[0])
+ del i
+
+ log("ARGNAMES 0 ", argnames, "\n")
+
+ # Firstly, we try to eliminate required arguments by position where the
+ # flag is implicit
+ pos = 0
+ for arg in arglist:
+ if pos >= len(comp_words):
+ break
+ word = comp_words[pos]
+ if word.startswith("-"):
+ break
+ spec = arg[2]
+ if spec[0] != "r":
+ break
+
+ # We have a required argument. If the first word of this argument is
+ # currently being expanded and is blank, then we stick the flag in
+ # first.
+ if word == "" and pos == comp_cword:
+ output("-", arg[0], "\n")
+ sys.exit(0)
+
+ word_types[pos] = arg
+ argnames.remove(arg[0])
+ pos += 1
+ if spec[1] == "s":
+ continue
+ while pos < len(comp_words):
+ word = comp_words[pos]
+ if word.startswith("-"):
+ break
+ word_types[pos] = arg
+ additional_args[pos] = True
+ pos += 1
+ break
+ del arg, pos
+
+ log("ARGNAMES 1 ", argnames, "\n")
+
+ # Secondly, eliminate flags by name where unambiguous
+ for i in canon_flags:
+ if i and i[0] in argnames:
+ argnames.remove(i[0])
+ del i
+
+ log("ARGNAMES 2 ", argnames, "\n")
+
+ # Work out the types of any optional arguments
+ last_flag = None
+ additional_arg = False
+ for i in range(0, len(comp_words)):
+ if canon_flags[i]:
+ switch = canon_flags[i]
+ if switch[2] != "fn":
+ last_flag = switch
+ else:
+ last_flag = None
+ additional_arg = False
+ elif last_flag:
+ word_types[i] = last_flag
+ additional_args[i] = additional_arg
+ if last_flag[2][1] == "s":
+ last_flag = None
+ else:
+ additional_arg = True
+ del i, last_flag, additional_arg
+
+ log("TYPES [")
+ for i in range(0, len(comp_words)):
+ if canon_flags[i]:
+ log(" -", canon_flags[i][0])
+ elif word_types[i] == "-":
+ log(" -?")
+ elif word_types[i]:
+ log(" ", word_types[i][3])
+ else :
+ log(" ??")
+ del i
+ log(" ]\n")
+
+ # Try to determine how to deal with the word being expanded
+ word = comp_words[comp_cword]
+ word_type = word_types[comp_cword]
+ canon = canon_flags[comp_cword]
+ additional_arg = additional_args[comp_cword]
+ log("WORD \"", word, "\"\n")
+ log("WORD_TYPE ", word_type, "\n")
+ log("CANON ", canon, "\n")
+
+ # Expand unambiguous flags fully
+ if canon:
+ log("*** GEN CANON ", canon, "\n")
+ output("-", canon[0], "\n")
+ sys.exit(0)
+
+ next_required = None
+ for arg in arglist:
+ if arg[2][0] == "r":
+ if arg[0] in argnames:
+ next_required = arg
+ break
+
+ # Insert a required flag now if there is one and there is no mandatory
+ # argument to the previous flag
+ if (word_type == None or word == "-" and additional_arg == True) and next_required:
+ log("*** GEN REQFLAG ", next_required[0], "\n")
+ output("-", next_required[0], "\n")
+ sys.exit(0)
+
+ # Work out what type this argument will be if it's not a flag and instruct
+ # the parent to run compgen
+ special = ""
+ if word_type != None:
+ if word_type[1] == get_cell:
+ special = "@CELL@"
+ elif (word_type[1] == get_bosserver or
+ word_type[1] == get_fileserver or
+ word_type[1] == get_volserver or
+ word_type[1] == get_vlservers or
+ word_type[1] == get_machine_name or
+ word_type[1] == get_machine_names):
+ special = "@HOSTNAME@"
+ elif word_type[1] == get_volume_name or word_type[1] == get_volume_names:
+ special = "@VOLNAME@"
+ elif word_type[1] == get_partition_id:
+ special = "@PARTID@"
+ elif (word_type[1] == get_path_name or word_type[1] == get_path_names or
+ word_type[1] == get_file_name or word_type[1] == get_file_names):
+ special = "@FILE@"
+ else:
+ special = "@OTHER@"
+ log("SPECIAL ", special, "\n")
+
+ # Expand an argument that can't be a flag
+ if word_type != None and additional_arg == False:
+ log("*** GEN NONFLAG ", word_type[0], "\n")
+ if special == "@CELL@":
+ space = ""
+ for i in expand_cell_list(word):
+ output(space, i)
+ space = " "
+ output("\n")
+ else:
+ output(special, "\n")
+ sys.exit(0)
+
+ # Work out a list of what options could go next
+ next_opts = ""
+ if len(word) == 0 or word.startswith("-"):
+ next_opts = next_required
+ if next_opts == None:
+ next_opts = ""
+ for i in argnames:
+ if i.startswith(word[1:]):
+ if next_opts == "":
+ next_opts = "-"
+ else:
+ next_opts += " -"
+ next_opts += i
+ log("NEXT OPTS ", next_opts, "\n")
+
+ if next_opts != "":
+ # If the next word has to be a flag of some sort, display a list thereof
+ if word_type == None or additional_arg == True and word.startswith("-"):
+ log("*** GEN OPTIONS ", next_opts, "\n")
+ output(next_opts)
+ output("\n")
+ sys.exit(0)
+
+ # The next word can be a flag or an additional argument
+ if additional_arg == True:
+ log("*** GEN ADDARG ", special, "\n")
+ if special == "@CELL@":
+ space = ""
+ for i in expand_cell_list(word):
+ output(space, i)
+ space = " "
+ output("\n")
+ else:
+ output(special, "\n")
+ sys.exit(0)
+
+ # Nothing left
+ log("*** GEN NOTHING\n")
+ output("\n")
+ sys.exit(0)
+
+main()