# Part of the A-A-P recipe executive: process the command line arguments.

# Copyright (C) 2002 Stichting NLnet Labs
# Permission to copy and use this file is specified in the file COPYING.
# If this file is missing you can find it here: http://www.a-a-p.org/COPYING

# The arguments come in three catogeries
# - options
#   start with a dash: in the form "-v", "--version", "-f foo".
# - variable assignments
#   contain an equal sign: "foo=asdf"
# - targets
#   the rest

# The code for handing options originally comes from SCons.



import string
import getopt

from Args import Args
from Error import *
from Version import *
import Global

#
# function called for -v and --version
#
def opt_version(opt, arg):
    print _("A-A-P recipe executive version %s; released %s %s GMT") \
		% (version_string, version_date, version_time)
    arg = arg		# avoid PyChecker warning
    opt = opt		# avoid PyChecker warning
    raise NormalExit

#
# function called for -h and --help
#
def opt_help(opt, arg):
    global option_list

    help_opts = filter(lambda x: x.helpline, option_list)
    print _("Usage: aap [OPTION] [VAR=VALUE] [TARGET] ...\n") \
	+ _("Options:\n") \
	+ string.join(map(lambda x: x.helpline, help_opts), "\n") + "\n"
    arg = arg		# avoid PyChecker warning
    opt = opt		# avoid PyChecker warning
    raise NormalExit


#
# The following variables are filled with all the possible options.
#
option_list = []	# list of all Option objects
short_opts = ""		# string of short (single-character) options
long_opts = []		# array of long (--) options
option_dict = {}	# mapping of each option string to its Option object

def options_init():
    """Initialize command-line arguments processing.
    
    This is in a function mainly so we can easily single-step over
    it in the debugger.
    """

    class Option:
	"""Class for command-line option information.

	This exists to provide a central location for everything
	describing a command-line option, so that we can change
	options without having to update the code to handle the
	option in one place, the -h help message in another place,
	etc.  There are no methods here, only attributes.

	You can initialize an Option with the following:

	func	The function that will be called when this
		option is processed on the command line.
		Calling sequence is:

			func(opt, arg)

		If there is no func, then this Option probably
		stores an optstring to be printed.

	name	The generic option name.  Usually either func or name
		is used.  When a name is specified, the option is stored for
		later use.

	helpline
		The string to be printed in -h output.  If no
		helpline is specified but a help string is
		specified (the usual case), a helpline will be
		constructed automatically from the short, long,
		arg, and help attributes.  (In practice, then,
		setting helpline without setting func allows you
		to print arbitrary lines of text in the -h
		output.)

	short	The string for short, single-hyphen
		command-line options.
		Do not include the hyphen:

			'a' for -a, 'xy' for -x and -y, etc.

	long	An array of strings for long, double-hyphen
		command-line options.  Do not include
		the hyphens:

			['my-option', 'verbose']

	arg	If this option takes an argument, this string
		specifies how you want it to appear in the
		-h output ('DIRECTORY', 'FILE', etc.).

	help	The help string that will be printed for
		this option in the -h output.  Must be
		49 characters or fewer.

	future	If non-zero, this indicates that this feature
		will be supported in a future release, not
		the currently planned one.  SCons will
		recognize the option, but it won't show up
		in the -h output.

	The following attribute is derived from the supplied attributes:

	optstring
		A string, with hyphens, describing the flags
		for this option, as constructed from the
		specified short, long and arg attributes.

	All Option objects are stored in the global option_list list,
	in the order in which they're created.  This is the list
	that's used to generate -h output, so the order in which the
	objects are created is the order in which they're printed.
	"""

	def __init__(self,
			func = None,
			name = None,
			helpline = None,
			short = None,
			long = None,
			arg = None,
			append = 0,
			help = None,
			future = None):
	    self.func = func
	    self.name = name
	    self.short = short
	    self.long = long
	    self.arg = arg
	    self.append = append
	    self.help = help

	    # process the short and long option names to create a help line.
	    opts = []
	    if self.short:
		for c in self.short:
		    if arg:
			c = c + " " + arg
		    opts = opts + ['-' + c]
	    if self.long:
		l = self.long
		if arg:
		    l = map(lambda x,a=arg: x + "=" + a, self.long)
		opts = opts + map(lambda x: '--' + x, l)

	    self.optstring = string.join(opts, ', ')
	    if helpline:
		self.helpline = helpline
	    elif help and not future:
		if len(self.optstring) <= 26:
		    sep = " " * (28 - len(self.optstring))
		else:
		    sep = self.helpstring = "\n" + " " * 30
		self.helpline = "  " + self.optstring + sep + self.help
	    else:
		self.helpline = None

	    # Add the newly created Option object to the list of objects.
	    global option_list
	    option_list.append(self)

    # In the following instantiations, the help string should be no
    # longer than 49 characters.  Use the following as a guide:
    #	help = _("1234567890123456789012345678901234567890123456789")

    Option(func = opt_version,
	short = 'V', long = ['version'],
	help = _("Print version information and exit"))

    Option(func = opt_help,
	short = 'h', long = ['help'],
	help = _("Print help message (this one) and exit"))

    Option(name = "verbose",
	short = 'v', long = ['verbose'],
	help = _("Print more information"))

    Option(name = "silent",
	short = 's', long = ['silent'],
	help = _("Print less information"))

    Option(name = "debug",
	short = 'd', long = ['debug'], arg = 'FLAGS',
	help = _("Debug the specified items"))

    Option(name = "profile",
	long = ['profile'], arg = 'FILE',
	help = _("Profile A-A-P execution and write results in FILE"))

    Option(name = "nobuild",
	short = 'n', long = ['nobuild'],
	help = _("Print the build commands but do not execute them"))

    Option(name = "touch",
	short = 't', long = ['touch'],
	help = _("Update target signatures, do not build them"))

    Option(name = "force",
	short = 'F', long = ['force'],
	help = _("Force rebuilding"))

    Option(name = "contents",
	short = 'C', long = ['contents'],
	help = _("Only build when file contents changed"))

    Option(name = "command",
	short = 'c', long = ['command'], arg = 'CMD', append = 1,
	help = _("Execute a command after reading the recipe"))

    Option(name = "continue",
	short = 'k', long = ['continue'],
	help = _("Continue building after an error"))

    Option(name = "stop",
	short = 'S', long = ['stop'],
	help = _("Stop building at first error (default)"))

    Option(name = "fetch-recipe",
	short = 'R', long = ['fetch-recipe'],
	help = _("Fetch recipe file and child recipes"))

    Option(name = "nofetch-recipe",
	short = 'N', long = ['nofetch-recipe'],
	help = _('Do not fetch recipes for "fetch" target'))

    Option(name = "nocache",
	short = 'a', long = ['nocache'],
	help = _("Always download files, don't use the cache"))

    Option(name = "local",
	short = 'l', long = ['local'],
	help = _("Do not recurse into subdirectories"))

    Option(name = "jobs",
	short = 'j', long = ['jobs'], arg = 'N',
	help = _("Maximum number of parallel jobs"))

    Option(name = "search-up",
	short = 'u', long = ['search-up', 'up'],
	help = _("Search directory tree upwards for main.aap recipe"))

    Option(name = "include",
	short = 'I', long = ['include'], arg = 'DIR', append = 1,
	help = _("Directory to search for included recipes"))

    Option(name = "recipe",
	short = 'f', long = ['recipe'], arg = 'FILE',
	help = _("Recipe file to be executed"))

    Option(name = "end",
	short = ' ',
	help = _("End of options, targets and assignments follow"))

    # Now that all Option objects are created, make the list of short and long
    # option names for use with getopt().
    # This also makes a dictonary to be able to find each Option object by the
    # option name.

    global short_opts
    global long_opts
    global option_dict

    for o in option_list:
	if o.short:
	    for c in o.short:
		option_dict['-' + c] = o
	    short_opts = short_opts + o.short
	    if o.arg:
		short_opts = short_opts + ":"
	if o.long:
	    for l in o.long:
		option_dict['--' + l] = o
	    if o.arg:
		long_opts = long_opts + map(lambda a: a + "=", o.long)
	    else:
		long_opts = long_opts + o.long


#
# The main function to process the command line arguments.
#
def doargs(argv):
    """
    Process the command line arguments "argv".
    Returns an Args object with the resuting args and targets.
    """

    global short_opts
    global long_opts
    global option_dict

    options_init()

    #
    # Start with an empty Args object.
    #
    args = Args()

    #
    # First extract the options.
    #

    # It looks like Python 2.0 changed the name of the exception class raised
    # by getopt.
    try:
	getopt_err = getopt.GetoptError
    except:
	getopt_err = getopt.error

    # Loop over the arguments.
    # getopt() stops when a non-option is encountered or a single '-'.
    # Parse targets and assignments until another option is found.
    while argv:
	try:
	    cmd_opts, argv = getopt.getopt(argv, short_opts, long_opts)
	except getopt_err, x:
	    raise UserError, _("Invalid argument: ") + str(x)
	else:
	    # For the recognized options call a function and/or add the
	    # argument to the argument dictionary for later use.
	    # Note that a function may cause us to exit!
	    for opt, arg in cmd_opts:
		o = option_dict[opt]
		if o.func:
		    o.func(opt, arg)
		if o.name:
		    if not arg:
			arg = 1
		    if o.append:
			if args.options.has_key(o.name):
			    args.options[o.name].append(arg)
			else:
			    args.options[o.name] = [ arg ]
		    else:
			args.options[o.name] = arg

	    #
	    # Get variable assignments and targets.
	    # An argument starting with '-' may be another option, unless "--"
	    # has been encountered.
	    #
	    while argv:
		if argv[0] == '-':
		    args.options["end"] = 1
		elif argv[0][0] == '-' and not args.options.has_key("end"):
		    break
		elif '=' in argv[0]:
		    name, value = string.split(argv[0], '=', 1)
		    args.values[name] = value
		else:
		    args.targets.append(argv[0])
		    # Using a "refresh", "fetch" or "update" target implies the
		    # --fetch-recipe option, unless --nofetch-recipe was used.
		    if (not args.options.has_key("nofetch-recipe")
			and (argv[0] == "refresh"
			    or argv[0] == "fetch"
			    or argv[0] == "update")):
			args.options["fetch-recipe"] = 1
		argv = argv[1:]

    return args


def local_arg():
    """Return non-zero  if the "-l" or "--local" argument was given.  zero
       string otherwise."""
    if Global.cmd_args.options.has_key("local"):
	return 1
    return 0

def error_continue():
    """Return TRUE if we are supposed to continue after an error."""
    return Global.cmd_args.options.get("continue")

# vim: set sw=4 sts=4 tw=79 fo+=l:
