Exemple #1
0
def load_database(db_path):
    """Load the database from 'db_path'.

    'db_path' -- The path to the directory containing the database.
    
    returns -- The new 'Database'."""

    # Make sure it is a directory.
    if not is_database(db_path):
        raise QMException, \
              qm.error("not test database", path=db_path)

    # Load the file.
    config_path = get_configuration_file(db_path)
    document = qm.xmlutil.load_xml_file(config_path)

    # Parse it.
    database_class, arguments \
        = (qm.extension.parse_dom_element
           (document.documentElement,
            lambda n: qm.test.base.get_extension_class(n, "database",
                                                       None, db_path)))
    # For backwards compatibility with QM 1.1.x, we look for "attribute"
    # elements.
    for node in document.documentElement.getElementsByTagName("attribute"):
        name = node.getAttribute("name")
        # These elements were only allowed to contain strings as
        # values.
        value = qm.xmlutil.get_dom_text(node)
        # Python does not allow keyword arguments to have Unicode
        # values, so we convert the name to an ordinary string.
        arguments[str(name)] = value
        
    return database_class(db_path, arguments)
Exemple #2
0
def open_temporary_file_fd(suffix = ""):
    """Create and open a temporary file.

    'suffix' -- The last part of the temporary file name, as for
    Python's 'mktemp' function.
    
    The file is open for reading and writing.  The caller is responsible
    for deleting the file when finished with it.

    returns -- A pair '(file_name, file_descriptor)' for the temporary
    file."""

    file_name = tempfile.mktemp(suffix)

    try:
        # Attempt to open the file.
        fd = os.open(file_name,
                     os.O_CREAT | os.O_EXCL | os.O_RDWR,
                     0600)
    except:
        exc_info = sys.exc_info()
        raise QMException, \
              qm.error("temp file error",
                       file_name=file_name,
                       exc_class=str(exc_info[0]),
                       exc_arg=str(exc_info[1]))
    return (file_name, fd)
Exemple #3
0
def open_temporary_file_fd(suffix=""):
    """Create and open a temporary file.

    'suffix' -- The last part of the temporary file name, as for
    Python's 'mktemp' function.
    
    The file is open for reading and writing.  The caller is responsible
    for deleting the file when finished with it.

    returns -- A pair '(file_name, file_descriptor)' for the temporary
    file."""

    file_name = tempfile.mktemp(suffix)

    try:
        # Attempt to open the file.
        fd = os.open(file_name, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0600)
    except:
        exc_info = sys.exc_info()
        raise QMException, \
              qm.error("temp file error",
                       file_name=file_name,
                       exc_class=str(exc_info[0]),
                       exc_arg=str(exc_info[1]))
    return (file_name, fd)
Exemple #4
0
    def Read(self, file_name):
        """Read the context file 'file_name'.

        'file_name' -- The name of the context file.

        Reads the context file and adds the context properties in the
        file to 'self'."""

        if file_name == "-":
            # Read from standard input.
            file = sys.stdin
        else:
            # Read from a named file.
            try:
                file = open(file_name, "r")
            except:
                raise qm.cmdline.CommandError, \
                      qm.error("could not read file", path=file_name)
        # Read the assignments.
        assignments = qm.common.read_assignments(file)
        # Add them to the context.
        for (name, value) in assignments.items():
            try:
                # Insert it into the context.
                self[name] = value
            except ValueError, msg:
                # The format of the context key is invalid, but
                # raise a 'CommandError' instead.
                raise qm.cmdline.CommandError, msg
Exemple #5
0
    def Read(self, file_name):
        """Read the context file 'file_name'.

        'file_name' -- The name of the context file.

        Reads the context file and adds the context properties in the
        file to 'self'."""

        if file_name == "-":
            # Read from standard input.
            file = sys.stdin
        else:
            # Read from a named file.
            try:
                file = open(file_name, "r")
            except:
                raise qm.cmdline.CommandError, \
                      qm.error("could not read file", path=file_name)
        # Read the assignments.
        assignments = qm.common.read_assignments(file)
        # Add them to the context.
        for (name, value) in assignments.items():
            try:
                # Insert it into the context.
                self[name] = value
            except ValueError, msg:
                # The format of the context key is invalid, but
                # raise a 'CommandError' instead.
                raise qm.cmdline.CommandError, msg
Exemple #6
0
        def __init__(cls, name, bases, dict):
            """Generate an '_argument_dictionary' holding all the
            'Field' objects.  Then replace 'Field' objects by their
            values for convenient use inside the code."""

            # List all base classes that are themselves of type Extension.
            #
            # 'Extension' isn't known at this point so all we can do to find
            # Extension base classes is test whether __metaclass__ is defined
            # and if so, whether it is a base class of the metaclass of cls.
            type = cls.__metaclass__

            def is_extension(base):
                metaclass = getattr(base, '__metaclass__', None)
                return metaclass and issubclass(type, metaclass)

            hierarchy = [base for base in bases if is_extension(base)]

            parameters = {}
            for c in hierarchy:
                parameters.update(c._argument_dictionary)
            # Now set parameters from class variables of type 'Field'.
            for key, field in dict.iteritems():
                if isinstance(field, Field):
                    field.SetName(key)
                    parameters[key] = field

            # For backward compatibility, inject all members of the
            # 'arguments' list into the dict, if it is indeed a list of fields.
            arguments = dict.get('arguments', [])
            if (type(arguments) is list and len(arguments) > 0
                    and isinstance(arguments[0], Field)):
                for field in arguments:
                    # Only allow name collisions between arguments and
                    # class variables if _allow_arg_names_matching_class_vars
                    # evaluates to True.
                    if (hasattr(cls, field.GetName())
                            and not cls._argument_dictionary.has_key(
                                field.GetName())
                            and not cls._allow_arg_names_matching_class_vars):
                        raise qm.common.QMException, \
                              qm.error("ext arg name matches class var",
                                       class_name = name,
                                       argument_name = field.GetName())
                    parameters[field.GetName()] = field

            setattr(cls, '_argument_dictionary', parameters)
            setattr(cls, '_argument_list', parameters.values())

            # Finally set default values.
            for i in parameters:
                setattr(cls, i, parameters[i].GetDefaultValue())
        def __init__(cls, name, bases, dict):
            """Generate an '_argument_dictionary' holding all the
            'Field' objects.  Then replace 'Field' objects by their
            values for convenient use inside the code."""

            # List all base classes that are themselves of type Extension.
            #
            # 'Extension' isn't known at this point so all we can do to find
            # Extension base classes is test whether __metaclass__ is defined
            # and if so, whether it is a base class of the metaclass of cls.
            type = cls.__metaclass__
            def is_extension(base):
                metaclass = getattr(base, '__metaclass__', None)
                return metaclass and issubclass(type, metaclass)

            hierarchy = [base for base in bases if is_extension(base)]

            parameters = {}
            for c in hierarchy:
                parameters.update(c._argument_dictionary)
            # Now set parameters from class variables of type 'Field'.
            for key, field in dict.iteritems():
                if isinstance(field, Field):
                    field.SetName(key)
                    parameters[key] = field

            # For backward compatibility, inject all members of the
            # 'arguments' list into the dict, if it is indeed a list of fields.
            arguments = dict.get('arguments', [])
            if (type(arguments) is list and
                len(arguments) > 0 and
                isinstance(arguments[0], Field)):
                for field in arguments:
                    # Only allow name collisions between arguments and
                    # class variables if _allow_arg_names_matching_class_vars
                    # evaluates to True.
                    if (hasattr(cls, field.GetName())
                        and not cls._argument_dictionary.has_key(field.GetName())
                        and not cls._allow_arg_names_matching_class_vars):
                        raise qm.common.QMException, \
                              qm.error("ext arg name matches class var",
                                       class_name = name,
                                       argument_name = field.GetName())
                    parameters[field.GetName()] = field

            setattr(cls, '_argument_dictionary', parameters)
            setattr(cls, '_argument_list', parameters.values())

            # Finally set default values.
            for i in parameters:
                setattr(cls, i, parameters[i].GetDefaultValue())
Exemple #8
0
def parse_assignment(assignment):
    """Parse an 'assignment' of the form 'name=value'.

    'aassignment' -- A string.  The string should have the form
    'name=value'.

    returns -- A pair '(name, value)'."""

    # Parse the assignment.
    try:
        (name, value) = string.split(assignment, "=", 1)
        return (name, value)
    except:
        raise QMException, \
              qm.error("invalid keyword assignment",
                       argument=assignment)
Exemple #9
0
def parse_assignment(assignment):
    """Parse an 'assignment' of the form 'name=value'.

    'aassignment' -- A string.  The string should have the form
    'name=value'.

    returns -- A pair '(name, value)'."""

    # Parse the assignment.
    try:
        (name, value) = string.split(assignment, "=", 1)
        return (name, value)
    except:
        raise QMException, \
              qm.error("invalid keyword assignment",
                       argument=assignment)
    def __init__(self):
        """Construct a new 'TemporaryDirectory."""

        self.__directory = None

        dir_path = tempfile.mktemp()
        try:
            os.mkdir(dir_path, 0700)
        except:
            exc_info = sys.exc_info()
            raise qm.common.QMException, \
                  qm.error("temp dir error",
                           dir_path=dir_path,
                           exc_class=str(exc_info[0]),
                           exc_arg=str(exc_info[1]))

        self.__directory = dir_path
    def __init__(self):
        """Construct a new 'TemporaryDirectory."""

        self.__directory = None
        
        dir_path = tempfile.mktemp()
        try:
            os.mkdir(dir_path, 0700)
        except:
            exc_info = sys.exc_info()
            raise qm.common.QMException, \
                  qm.error("temp dir error",
                           dir_path=dir_path,
                           exc_class=str(exc_info[0]),
                           exc_arg=str(exc_info[1]))

        self.__directory = dir_path
Exemple #12
0
def get_extension_class(class_name, kind, database, database_path = None):
    """Return the extension class named 'class_name'.

    'class_name' -- The name of the class, in the form 'module.class'.

    'kind' -- The kind of class to load.  This value must be one
    of the 'extension_kinds'.

    'database' -- The 'Database' with which the extension class will be
    used, or 'None' if 'kind' is 'database'.

    'database_path' -- The path from which the database will be loaded.
    If 'None', 'database.GetPath()' is used.

    returns -- The class object with the indicated 'class_name'."""

    global __class_caches
    
    # If this class is already in the cache, we can just return it.
    cache = __class_caches[kind]
    if cache.has_key(class_name):
        return cache[class_name]

    # For backwards compatibility with QM 1.1.x, we accept
    # "xmldb.Database" and "qm.test.xmldb.Database", even though those
    # to do not name actual database classes any more.
    if kind == "database" and class_name in ("xmldb.Database",
                                             "qm.test.xmldb.Database"):
        class_name = "xml_database.XMLDatabase"
        
    # Look for the class in each of the extension directories.
    directories = get_extension_directories(kind, database, database_path)
    directory = None
    for d in directories:
        if class_name in get_extension_class_names_in_directory(d)[kind]:
            directory = d
            break

    # If the class could not be found, issue an error.
    if not directory:
        raise QMException, qm.error("extension class not found",
                                    klass=class_name)

    # Load the class.
    return get_extension_class_from_directory(class_name, kind,
                                              directory, directories)
Exemple #13
0
def get_extension_class(class_name, kind, database, database_path=None):
    """Return the extension class named 'class_name'.

    'class_name' -- The name of the class, in the form 'module.class'.

    'kind' -- The kind of class to load.  This value must be one
    of the 'extension_kinds'.

    'database' -- The 'Database' with which the extension class will be
    used, or 'None' if 'kind' is 'database'.

    'database_path' -- The path from which the database will be loaded.
    If 'None', 'database.GetPath()' is used.

    returns -- The class object with the indicated 'class_name'."""

    global __class_caches

    # If this class is already in the cache, we can just return it.
    cache = __class_caches[kind]
    if cache.has_key(class_name):
        return cache[class_name]

    # For backwards compatibility with QM 1.1.x, we accept
    # "xmldb.Database" and "qm.test.xmldb.Database", even though those
    # to do not name actual database classes any more.
    if kind == "database" and class_name in ("xmldb.Database",
                                             "qm.test.xmldb.Database"):
        class_name = "xml_database.XMLDatabase"

    # Look for the class in each of the extension directories.
    directories = get_extension_directories(kind, database, database_path)
    directory = None
    for d in directories:
        if class_name in get_extension_class_names_in_directory(d)[kind]:
            directory = d
            break

    # If the class could not be found, issue an error.
    if not directory:
        raise QMException, qm.error("extension class not found",
                                    klass=class_name)

    # Load the class.
    return get_extension_class_from_directory(class_name, kind, directory,
                                              directories)
Exemple #14
0
def get_extension_class_from_directory(class_name, kind, directory, path):
    """Load an extension class from 'directory'.

    'class_name' -- The name of the extension class, in the form
    'module.class'.

    'kind' -- The kind of class to load.  This value must be one
    of the 'extension_kinds'.

    'directory' -- The directory from which to load the class.

    'path' -- The directories to search for modules imported by the new
    module.

    returns -- The class loaded."""
    
    global __class_caches
    global __extension_bases
    
    # If this class is already in the cache, we can just return it.
    cache = __class_caches[kind]
    if cache.has_key(class_name):
        return cache[class_name]

    # Load the class.
    try:
        klass = qm.common.load_class(class_name, [directory],
                                     path + sys.path)
    except:
        raise CouldNotLoadExtensionError(class_name, sys.exc_info())

    # Make sure the class is derived from the appropriate base class.
    if not issubclass(klass, __extension_bases[kind]):
        raise QMException, \
              qm.error("extension class not subclass",
                       kind = kind,
                       class_name = class_name,
                       base_name = __extension_bases[kind].__name__)
                      
    # Cache it.
    cache[class_name] = klass

    return klass
Exemple #15
0
    def __init__(self, key, msg="missing context variable"):
        """Construct a new 'ContextException'.

        'key' -- A string giving the context key for which no valid
        value was available.

        'msg' -- A diagnostic identifier explaining the problem.  The
        message string may contain a fill-in for the key."""

        msg = qm.error(msg, key=key)
        qm.common.QMException.__init__(self, msg)
        self.key = key
Exemple #16
0
    def __init__(self, class_name, exc_info):
        """Construct a new 'CouldNotLoadExtensionError'.

        'class_name' -- The name of the class.

        'exc_info' -- An exception tuple, as returned by 'sys.exc_info'."""
        
        self.exc_info = exc_info
        message = qm.common.format_exception(exc_info)
        message += "\n" + qm.error("could not load extension class",
                                   class_name = class_name)
        QMException.__init__(self, message)
Exemple #17
0
    def __init__(self, key, msg = "missing context variable"):
        """Construct a new 'ContextException'.

        'key' -- A string giving the context key for which no valid
        value was available.

        'msg' -- A diagnostic identifier explaining the problem.  The
        message string may contain a fill-in for the key."""

        msg = qm.error(msg, key = key)
        qm.common.QMException.__init__(self, msg)
        self.key = key
Exemple #18
0
    def __init__(self, class_name, exc_info):
        """Construct a new 'CouldNotLoadExtensionError'.

        'class_name' -- The name of the class.

        'exc_info' -- An exception tuple, as returned by 'sys.exc_info'."""

        self.exc_info = exc_info
        message = qm.common.format_exception(exc_info)
        message += "\n" + qm.error("could not load extension class",
                                   class_name=class_name)
        QMException.__init__(self, message)
Exemple #19
0
def get_class_arguments(extension_class):
    """Return the arguments associated with 'extension_class'.

    'extension_class' -- A class derived from 'Extension'.
    
    returns -- A list of 'Field' objects containing all of the
    arguments in the class hierarchy."""

    assert issubclass(extension_class, Extension)

    arguments = extension_class.__dict__.get("_argument_list")
    if arguments is None:
        # There are no arguments yet.
        arguments = []
        dictionary = {}
        # Start with the most derived class.
        for c in extension_class.__mro__:
            # Add the arguments from this class.
            new_arguments = c.__dict__.get("arguments", [])
            for a in new_arguments:
                name = a.GetName()
                # An extension class may not have an argument with the
                # same name as a class variable.  That leads to
                # serious confusion.
                if (not extension_class._allow_arg_names_matching_class_vars
                    and hasattr(extension_class, name)):
                    raise qm.common.QMException, \
                          qm.error("ext arg name matches class var",
                                   class_name = extension_class.__name__,
                                   argument_name = name)
                # If we already have an entry for this name, then a
                # derived class overrides this argument.
                if not dictionary.has_key(name):
                    arguments.append(a)
                    dictionary[name] = a
                    
        extension_class._argument_list = arguments
        extension_class._argument_dictionary = dictionary
        
    return arguments
Exemple #20
0
def validate_arguments(extension_class, arguments):
    """Validate the 'arguments' to the 'extension_class'.

    'extension_class' -- A class derived from 'Extension'.

    'arguments' -- A dictionary mapping argument names (strings) to
    values (strings).

    returns -- A dictionary mapping 'Field's to values.
    
    Check that each of the 'arguments' is a valid argument to
    'extension_class'.  If so, the argumets are converted as required
    by the 'Field', and the dictionary returned contains the converted
    values.  Otherwise, an exception is raised."""

    assert issubclass(extension_class, Extension)

    # We have not converted any arguments yet.
    converted_arguments = {}

    # Check that there are no arguments that do not apply to this
    # class.
    class_arguments = get_class_arguments_as_dictionary(extension_class)
    for name, value in arguments.items():
        field = class_arguments.get(name)
        if not field:
            raise qm.QMException, \
                  qm.error("unexpected extension argument",
                           name = name,
                           class_name \
                               = get_extension_class_name(extension_class))
        if field.IsComputed():
            raise qm.QMException, \
                  qm.error("value provided for computed field",
                           name = name,
                           class_name \
                               = get_extension_class_name(extension_class))
        converted_arguments[name] = field.ParseTextValue(value)

    return converted_arguments
Exemple #21
0
def validate_arguments(extension_class, arguments):
    """Validate the 'arguments' to the 'extension_class'.

    'extension_class' -- A class derived from 'Extension'.

    'arguments' -- A dictionary mapping argument names (strings) to
    values (strings).

    returns -- A dictionary mapping 'Field's to values.
    
    Check that each of the 'arguments' is a valid argument to
    'extension_class'.  If so, the argumets are converted as required
    by the 'Field', and the dictionary returned contains the converted
    values.  Otherwise, an exception is raised."""

    assert issubclass(extension_class, Extension)

    # We have not converted any arguments yet.
    converted_arguments = {}
    
    # Check that there are no arguments that do not apply to this
    # class.
    class_arguments = get_class_arguments_as_dictionary(extension_class)
    for name, value in arguments.items():
        field = class_arguments.get(name)
        if not field:
            raise qm.QMException, \
                  qm.error("unexpected extension argument",
                           name = name,
                           class_name \
                               = get_extension_class_name(extension_class))
        if field.IsComputed():
            raise qm.QMException, \
                  qm.error("value provided for computed field",
                           name = name,
                           class_name \
                               = get_extension_class_name(extension_class))
        converted_arguments[name] = field.ParseTextValue(value)

    return converted_arguments
Exemple #22
0
def parse_descriptor(descriptor, class_loader, extension_loader = None):
    """Parse a descriptor representing an instance of 'Extension'.

    'descriptor' -- A string representing an instance of 'Extension'.
    The 'descriptor' has the form 'class(arg1 = "val1", arg2 = "val2",
    ...)'.  The arguments and the parentheses are optional.

    'class_loader' -- A callable that, when passed the name of the
    extension class, will return the actual Python class object.

    'extension_loader' -- A callable that loads an existing extension
    given the name of that extension and returns a tuple '(class,
    arguments)' where 'class' is a class derived from 'Extension'.  If
    'extension_loader' is 'None', or if the 'class' returned is
    'None', then if a file exists named 'class', the extension is read
    from 'class' as XML.  Any arguments returned by the extension
    loader or read from the file system are overridden by the
    arguments explicitly provided in the descriptor.

    returns -- A pair ('extension_class', 'arguments') containing the
    extension class (a class derived from 'Extension') and the
    arguments (a dictionary mapping names to values) stored in the
    'element'.  The 'arguments' will have already been processed by
    'validate_arguments' by the time they are returned."""

    # Look for the opening parenthesis.
    open_paren = descriptor.find('(')
    if open_paren == -1:
        # If there is no opening parenthesis, the descriptor is simply
        # the name of an extension class.
        class_name = descriptor
    else:
        # The class name is the part of the descriptor up to the
        # parenthesis.
        class_name = descriptor[:open_paren]

    # Load the extension, if it already exists.
    extension_class = None
    if extension_loader:
        extension = extension_loader(class_name)
        if extension:
            extension_class = extension.__class__
            orig_arguments = extension.GetExplicitArguments()
    if not extension_class:
        if os.path.exists(class_name):
            extension_class, orig_arguments \
                = read_extension_file(open(filename), class_loader)
        else:
            extension_class = class_loader(class_name)
            orig_arguments = {}

    arguments = {}
    
    # Parse the arguments.
    if open_paren != -1:
        # Create a file-like object for the remainder of the string.
        arguments_string = descriptor[open_paren:]
        s = StringIO.StringIO(arguments_string)
        # Use the Python tokenizer to process the remainder of the
        # string.
        g = tokenize.generate_tokens(s.readline)
        # Read the opening parenthesis.
        tok = g.next()
        assert tok[0] == tokenize.OP and tok[1] == "("
        need_comma = 0
        # Keep going until we find the closing parenthesis.
        while 1:
            tok = g.next()
            if tok[0] == tokenize.OP and tok[1] == ")":
                break
            # All arguments but the first must be separated by commas.
            if need_comma:
                if tok[0] != tokenize.OP or tok[1] != ",":
                    raise qm.QMException, \
                          qm.error("invalid descriptor syntax",
                                   start = arguments_string[tok[2][1]:])
                tok = g.next()
            # Read the argument name.
            if tok[0] != tokenize.NAME:
                raise qm.QMException, \
                      qm.error("invalid descriptor syntax",
                               start = arguments_string[tok[2][1]:])
            name = tok[1]
            # Read the '='.
            tok = g.next()
            if tok[0] != tokenize.OP or tok[1] != "=":
                raise qm.QMException, \
                      qm.error("invalid descriptor syntax",
                               start = arguments_string[tok[2][1]:])
            # Read the value.
            tok = g.next()
            if tok[0] != tokenize.STRING:
                raise qm.QMException, \
                      qm.error("invalid descriptor syntax",
                               start = arguments_string[tok[2][1]:])
            # The token string will have surrounding quotes.  By
            # running it through "eval", we get at the underlying
            # value.
            value = eval(tok[1])
            arguments[name] = value
            # The next argument must be preceded by a comma.
            need_comma = 1
        # There shouldn't be anything left at this point.
        tok = g.next()
        if not tokenize.ISEOF(tok[0]):
            raise qm.QMException, \
                  qm.error("invalid descriptor syntax",
                           start = arguments_string[tok[2][1]:])
    
    # Process the arguments.
    arguments = validate_arguments(extension_class, arguments)
    # Use the explict arguments to override any specified in the file.
    orig_arguments.update(arguments)
    
    return (extension_class, orig_arguments)
Exemple #23
0
def parse_descriptor(descriptor, class_loader, extension_loader=None):
    """Parse a descriptor representing an instance of 'Extension'.

    'descriptor' -- A string representing an instance of 'Extension'.
    The 'descriptor' has the form 'class(arg1 = "val1", arg2 = "val2",
    ...)'.  The arguments and the parentheses are optional.

    'class_loader' -- A callable that, when passed the name of the
    extension class, will return the actual Python class object.

    'extension_loader' -- A callable that loads an existing extension
    given the name of that extension and returns a tuple '(class,
    arguments)' where 'class' is a class derived from 'Extension'.  If
    'extension_loader' is 'None', or if the 'class' returned is
    'None', then if a file exists named 'class', the extension is read
    from 'class' as XML.  Any arguments returned by the extension
    loader or read from the file system are overridden by the
    arguments explicitly provided in the descriptor.

    returns -- A pair ('extension_class', 'arguments') containing the
    extension class (a class derived from 'Extension') and the
    arguments (a dictionary mapping names to values) stored in the
    'element'.  The 'arguments' will have already been processed by
    'validate_arguments' by the time they are returned."""

    # Look for the opening parenthesis.
    open_paren = descriptor.find('(')
    if open_paren == -1:
        # If there is no opening parenthesis, the descriptor is simply
        # the name of an extension class.
        class_name = descriptor
    else:
        # The class name is the part of the descriptor up to the
        # parenthesis.
        class_name = descriptor[:open_paren]

    # Load the extension, if it already exists.
    extension_class = None
    if extension_loader:
        extension = extension_loader(class_name)
        if extension:
            extension_class = extension.__class__
            orig_arguments = extension.GetExplicitArguments()
    if not extension_class:
        if os.path.exists(class_name):
            extension_class, orig_arguments \
                = read_extension_file(open(filename), class_loader)
        else:
            extension_class = class_loader(class_name)
            orig_arguments = {}

    arguments = {}

    # Parse the arguments.
    if open_paren != -1:
        # Create a file-like object for the remainder of the string.
        arguments_string = descriptor[open_paren:]
        s = StringIO.StringIO(arguments_string)
        # Use the Python tokenizer to process the remainder of the
        # string.
        g = tokenize.generate_tokens(s.readline)
        # Read the opening parenthesis.
        tok = g.next()
        assert tok[0] == tokenize.OP and tok[1] == "("
        need_comma = 0
        # Keep going until we find the closing parenthesis.
        while 1:
            tok = g.next()
            if tok[0] == tokenize.OP and tok[1] == ")":
                break
            # All arguments but the first must be separated by commas.
            if need_comma:
                if tok[0] != tokenize.OP or tok[1] != ",":
                    raise qm.QMException, \
                          qm.error("invalid descriptor syntax",
                                   start = arguments_string[tok[2][1]:])
                tok = g.next()
            # Read the argument name.
            if tok[0] != tokenize.NAME:
                raise qm.QMException, \
                      qm.error("invalid descriptor syntax",
                               start = arguments_string[tok[2][1]:])
            name = tok[1]
            # Read the '='.
            tok = g.next()
            if tok[0] != tokenize.OP or tok[1] != "=":
                raise qm.QMException, \
                      qm.error("invalid descriptor syntax",
                               start = arguments_string[tok[2][1]:])
            # Read the value.
            tok = g.next()
            if tok[0] != tokenize.STRING:
                raise qm.QMException, \
                      qm.error("invalid descriptor syntax",
                               start = arguments_string[tok[2][1]:])
            # The token string will have surrounding quotes.  By
            # running it through "eval", we get at the underlying
            # value.
            value = eval(tok[1])
            arguments[name] = value
            # The next argument must be preceded by a comma.
            need_comma = 1
        # There shouldn't be anything left at this point.
        tok = g.next()
        if not tokenize.ISEOF(tok[0]):
            raise qm.QMException, \
                  qm.error("invalid descriptor syntax",
                           start = arguments_string[tok[2][1]:])

    # Process the arguments.
    arguments = validate_arguments(extension_class, arguments)
    # Use the explict arguments to override any specified in the file.
    orig_arguments.update(arguments)

    return (extension_class, orig_arguments)
Exemple #24
0
class CommandParser:
    """Class for the functionality that parses the command line.

    The command parser is used to easily specify a list of command line
    options and commands to be parsed from an argument list."""
    def __init__(self, name, options, commands, conflicting_options=()):
        """Create a new command parser.

        'name' -- The name of the executable that we are currently
        using.  This will normally be argv[0].
        
        'options' -- A list of 4-tuples specifying options that you wish
        this parser to accept.  The 4-tuple has the following form:
        (short_form, long_form, options, description).  'short_form'
        must be exactly one character.  'long_form' must be specified
        for every option in the list.  'arg_name' is a string
        representing the name of the argument that is passed to this
        option.  If it is 'None,' then this option doesn't take an
        argument.  'description' is a string describing the option.

        'commands' -- A list of 5-tuples specifying commands to be
        accepted after the command line options.  The 5-tuple has the
        form '(name, short_description, args_string, long_description,
        options)'.

          'name' -- The string for the command.

          'short_description' -- A short description of the command to
          be printed out in general help.

          'args_string' -- The string that will be printed after the
          command in the command specific help.

          'long_description' -- The long description to be printed out
          in the command specfic help.

          'options' -- A list of 4-tuples of the same form as the
          'options' described above.

        'conflicting_options' -- A sequence of sets of conflicting
        options.  Each element is a sequence of option specifiers in the
        same form as 'options', above."""

        self.__name = name

        # Check that the options are ok.
        self.CheckOptions(options)
        self.__options = copy.deepcopy(options)

        self.__option_to_long = {}
        for option in self.__options:
            if option[0]:
                # Check for duplicate short options.
                assert not self.__option_to_long.has_key('-' + option[0])
                self.__option_to_long['-' + option[0]] = option[1]
            # Check for duplicate long options.
            assert not self.__option_to_long.has_key('--' + option[1])
            self.__option_to_long['--' + option[1]] = option[1]

        # Check that the options for each command are ok.
        for command in commands:
            self.CheckOptions(command[4])

        self.__commands = copy.deepcopy(commands)
        for i in range(0, len(self.__commands)):
            command = self.__commands[i]
            map = {}
            for option in command[4]:
                if option[0] is not None:
                    # Check for duplicate short options.
                    if map.has_key('-' + option[0]):
                        raise ValueError, \
                              "duplicate short command option -%s" \
                              % option[0]
                    map['-' + option[0]] = option[1]
                # Check for duplicate long options.
                if map.has_key('--' + option[1]):
                    raise ValueError, \
                          "duplicate long command option --%s" % option[1]
                map['--' + option[1]] = option[1]
            command = command + (map, )
            self.__commands[i] = command

        # Build the options string for getopt.
        self.__getopt_options = self.BuildGetoptString(self.__options)

        # Check that all options in the conflicting options set are
        # included somewhere.
        for conflict_set in conflicting_options:
            # Check each option in each set.
            for option_spec in conflict_set:
                found = 0
                # Check in the global options.
                if option_spec in options:
                    found = 1
                    break
                if not found:
                    # Check in the command options for each command.
                    for command in commands:
                        if option in command[4]:
                            found = 1
                            break
                if not found:
                    # This option spec wasn't found anywhere.
                    raise ValueError, \
                          "unknown option --%s in conflict set", option[1]
        # Store for later.
        self.__conflicting_options = conflicting_options

    def CheckOptions(self, options):
        """Check that a list of options 4-tuples is correct.

        'options' -- A list of 4-tuples as described above.

        returns -- 1 if the options are all valid, 0 otherwise."""

        for short_option, long_option, options, descripton in options:
            # The short form of the option must have exactly 1 character.
            if short_option != None and len(short_option) != 1:
                raise ValueError, "short option must have exactly 1 character"
            # The long form of the option must be specified.
            if long_option == None or len(long_option) == 0:
                raise ValueError, \
                      "long option must be specified for -%s" % short_option

        return 1

    def BuildGetoptList(self, options):
        """Build a getopt list for the long options.

        'options' -- A list of 4-tuples as described above.

        returns -- A list to be passed to getopt to parse long options."""

        # Build the options string for getopt.
        getopt_list = []

        for option in options:
            # Tell getopt that this option takes an argument.
            if option[2] != None:
                getopt_list.append(option[1] + '=')
            else:
                getopt_list.append(option[1])

        return getopt_list

    def BuildGetoptString(self, options):
        """Build a getopt string for the options passed in.

        'options' -- A list of 4-tuples as described above.

        returns -- A string to be passed to getopt to parse the
        options."""

        # Build the options string for getopt.
        getopt_string = ''

        for option in options:
            if option[0] is not None:
                getopt_string = getopt_string + option[0]
                # Tell getopt that this option takes an argument.
                if option[2] != None:
                    getopt_string = getopt_string + ':'

        return getopt_string

    def GetOptionsHelp(self, options):
        """Return a string that is the basic help for options.

        options -- A list of options to get the help string for.

        returns -- A string to be printed for the options."""

        help_string = ""

        # Print out the short form, long form, and then the description.
        for option in options:
            # Format the short form, if there is one.
            if option[0] is None:
                short_form = "   "
            else:
                short_form = "-%s," % option[0]
            # Format the long form.  Include the option arugment, if
            # there is one.
            if option[2] is None:
                long_form = "--%-24s" % option[1]
            else:
                long_form = "--%-24s" % (option[1] + " " + option[2])
            # Generate a line for this option.
            help_string = help_string \
                          + "  %s %s: %s\n" \
                          % (short_form, long_form, option[3])

        return help_string

    def GetBasicHelp(self):
        """Return a string that is the basic help for the commands.

        returns -- A string to be printed with basic functionality of
        arguments and commands."""

        help_string = "Usage: %s " % self.__name
        help_string = help_string + "[ OPTION... ] COMMAND " \
                      "[ COMMAND-OPTION... ] [ ARGUMENT... ]\n\n"
        help_string = help_string + "Options:\n"
        help_string = help_string + self.GetOptionsHelp(self.__options)
        help_string = help_string + "\nCommands:\n"
        # Print out the commands and their short descriptions.
        for command in self.__commands:
            help_add = "%-30s: %s" % (command[0], command[1])
            help_string = help_string + "  %s\n" % (help_add)
        help_string = help_string \
                      + "\nInvoke\n  %s COMMAND --help\n" \
                      "for information about " \
                      "COMMAND-OPTIONS and ARGUMENTS.\n\n" % self.__name

        return help_string

    def GetCommandHelp(self, command):
        """Return a string that is the help for a specific command.

        command -- A string of the command that you want help for.

        returns -- A string of help for a given command."""

        help_string = "Usage: %s %s [ OPTIONS ] " % (self.__name, command)
        for command_item in self.__commands:
            if command_item[0] == command:
                help_string = help_string + command_item[2] + "\n\n"
                help_string = help_string + "Options:\n"
                help_string = help_string \
                              + self.GetOptionsHelp(command_item[4])
                help_string = help_string + "\n"
                help_string = help_string \
                              + structured_text.to_text(command_item[3])
                return help_string

        return "Command not found"

    def ParseCommandLine(self, argv):
        """Parse a command line.

        'argv' -- A string containing the command line starting with
        argv[1].  It should not contain the name of the executed program.

        returns -- A 4-tuple of the options given, the command given,
        the command options, and the command arguments.  Its form is
        this: (options, command, command_options, command_args).
        'options' is a list of 2-tuples indicating each option specified
        and the argument given to that option (if applicable).
        'command' is the command given.  'command_options' is a list of
        2-tuples indicating each option given to the command and its
        possible argument.  'command_args' is a list of arguments as
        given to the command.  If no command is given, then the function
        will return '' for the command, [] for the arguments, and [] for
        the command options.

        raises -- 'CommandError' if the command is invalid."""

        # Get the options off of the front of the command line.
        getopt_list = self.BuildGetoptList(self.__options)

        try:
            options, args = getopt.getopt(argv, self.__getopt_options,
                                          getopt_list)
        except getopt.error, msg:
            raise CommandError, msg

        for i in range(0, len(options)):
            option = options[i]
            new_option = (self.__option_to_long[option[0]], option[1])
            options[i] = new_option

        # Did not specify anything on the command line except options.
        if args == []:
            return (options, '', [], [])

        # Get the command.
        command = args[0]

        # This checks to make sure the command they specified is actually
        # a command that we know.  Checking this now saves trouble
        # in having to do it later.
        found = 0
        for command_item in self.__commands:
            if command == command_item[0]:
                found = 1

        if found == 0:
            # The command they specified does not exist; print out the
            # help and raise an exception.
            raise CommandError, \
                  qm.error("unrecognized command", command=command)

        # Get the arguments to the command.
        command_options = []

        for command_item in self.__commands:
            if command_item[0] == command:
                command_options = command_item[4]
                break
        getopt_string = self.BuildGetoptString(command_options)
        getopt_list = self.BuildGetoptList(command_options)
        try:
            command_options, command_args = getopt.getopt(
                args[1:], getopt_string, getopt_list)
        except getopt.error, msg:
            raise CommandError, "%s: %s" % (command, msg)
Exemple #25
0
        except getopt.error, msg:
            raise CommandError, "%s: %s" % (command, msg)

        for i in range(0, len(command_options)):
            option = command_options[i]
            new_option = (command_item[5][option[0]], option[1])
            command_options[i] = new_option

        # Check for mutually exclusive options.  First generate a set of
        # all the options that were specified, both global options and
        # command options.
        all_options = map(lambda option: option[0], options + command_options)
        # Loop over sets of conflicting options.
        for conflict_set in self.__conflicting_options:
            # Generate sequence of names of the conflicting options.
            conflict_names = map(lambda opt_spec: opt_spec[1], conflict_set)
            # Filter out options that were specified that aren't in the
            # set of conflicting options.
            conflict_filter = lambda option, conflict_names=conflict_names: \
                              option in conflict_names and option
            matches = filter(conflict_filter, all_options)
            # Was more than one option from the conflicting set specified?
            if len(matches) > 1:
                # Yes; that's a user error.
                raise qm.cmdline.CommandError, \
                      qm.error("conflicting options",
                               option1=matches[0],
                               option2=matches[1])

        return (options, command, command_options, command_args)
Exemple #26
0
        for i in range(0, len(command_options)):
            option = command_options[i]
            new_option = (command_item[5][option[0]], option[1])
            command_options[i] = new_option
            
        # Check for mutually exclusive options.  First generate a set of
        # all the options that were specified, both global options and
        # command options.
        all_options = map(lambda option: option[0],
                          options + command_options)
        # Loop over sets of conflicting options.
        for conflict_set in self.__conflicting_options:
            # Generate sequence of names of the conflicting options.
            conflict_names = map(lambda opt_spec: opt_spec[1], conflict_set)
            # Filter out options that were specified that aren't in the
            # set of conflicting options.
            conflict_filter = lambda option, conflict_names=conflict_names: \
                              option in conflict_names and option
            matches = filter(conflict_filter, all_options)
            # Was more than one option from the conflicting set specified?
            if len(matches) > 1:
                # Yes; that's a user error.
                raise qm.cmdline.CommandError, \
                      qm.error("conflicting options",
                               option1=matches[0],
                               option2=matches[1])

        return (options, command, command_options, command_args)