Chinaunix首页 | 论坛 | 博客
  • 博客访问: 19726133
  • 博文数量: 679
  • 博客积分: 10495
  • 博客等级: 上将
  • 技术积分: 9308
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-18 10:51
文章分类

全部博文(679)

文章存档

2012年(5)

2011年(38)

2010年(86)

2009年(145)

2008年(170)

2007年(165)

2006年(89)

分类: Python/Ruby

2010-02-10 14:46:08

import getopt     # optparse not supported by Jython 2.2
import os
import re
import sys
import glob
import string

from robot.errors import DataError, Information, FrameworkError

from misc import plural_or_not
from robottypes import is_list, is_boolean
from text import wrap


ESCAPES = { 'space'   : ' ', 'apos'    : "'", 'quot'    : '"', 'lt'      : '<',
            'gt'      : '>', 'pipe'    : '|', 'star'    : '*', 'comma'   : ',',
            'slash'   : '/', 'semic'   : ';', 'colon'   : ':', 'quest'   : '?',
            'hash'    : '#', 'amp'     : '&', 'dollar'  : '$', 'percent' : '%',
            'at'      : '@', 'exclam'  : '!', 'paren1'  : '(', 'paren2'  : ')',
            'square1' : '[', 'square2' : ']', 'curly1'  : '{', 'curly2'  : '}',
            'bslash'  : '\\' }


class ArgumentParser:

    _opt_line_re = re.compile('''
    ^\s{,4}       # max 4 spaces in the beginning of the line
    ((-\S\s)*)    # all possible short options incl. spaces (group 1)
    --(\S{2,})    # required long option (group 3)
    (\s\S+)?      # optional value (group 4)
    (\s\*)?       # optional '*' telling option allowed multiple times (group 5)
    ''', re.VERBOSE)
    
    _usage_line_re = re.compile('''
    ^usage:.*
    \[options\]\s*
    (.*?)         # arguments (group 1)
    \s*$
    ''', re.VERBOSE | re.IGNORECASE)
   
    def __init__(self, usage, version=None, arg_limits=None):
        """Available options and tool name are read from the usage.

        Tool name is got from the first row of the usage. It is either the
        whole row or anything before first ' -- '.

        See for example 'runner.py' and 'rebot.py' for examples.
        """
        if not usage:
            raise FrameworkError('Usage cannot be empty')
        self._usage = usage
        self._name = usage.splitlines()[0].split(' -- ')[0].strip()
        self._version = version
        self._arg_limits = arg_limits
        self._short_opts = ''
        self._long_opts = []
        self._multi_opts = []
        self._toggle_opts = []
        self._names = []
        self._short_to_long = {}
        self._expected_args = ()
        self._parse_usage(usage)

    def parse_args(self, args_list, unescape=None, argfile=None, pythonpath=None,
                   help=None, version=None, check_args=False):
        """Parse given arguments and return options and positional arguments.
       
        Arguments must be given as a list and are typically sys.argv[1:].
       
        Options are retuned as a dictionary where long options are keys. Value
        is a string for those options that can be given only one time (if they
        are given multiple times the last value is used) or None if the option
        is not used at all. Value for options that can be given multiple times
        (denoted with '*' in the usage) is a list which contains all the given
        values and is empty if options are not used. Options not taken
        arguments have value False when they are not set and True otherwise.
       
        Positional arguments are returned as a list in the order they are given.
       
        'unescape' option can be used to automatically unescape problematic
        characters given in an escaped format. Given value must be the name of
        the long option used for escaping. Typically usage is having
        '--escape name:value *' in usage doc and specifying 'enescape="escape"'
        when calling this method.
       
        'argfile' can be used to automatically read arguments from specified
        file. Given value must be the name of the long option used for giving
        the argument file. Typical usage is '--argumentfile path *' in usage doc
        and calling this method with 'argfile="argumentfile"'. If 'argfile' is
        used, it can always be given multiple times and thus it is recommended
        to use '*' to denote that.
       
        'pythonpath' can be used to specify option(s) containing extra paths to
        be added into 'sys.path'. Value can be either a string containing the
        name of the long option used for this purpose or a list containing
        all such long options (i.e. the latter format allows aliases).

        'help' and 'version' make it possible to automatically generate help
        and version messages. Version is generated based on the tool name
        and version -- see __init__ for information how to set them. Help
        contains the whole usage given to __init__. Possible text
        in the usage is replaced with the given version. Possible <--ESCAPES-->
        is replaced with available escapes so that they are wrapped to multiple
        lines but take the same amount of horizontal space as <---ESCAPES--->.
        The numer of hyphens can be used to contrl the horizontal space. Both
        help and version are wrapped to Information exception.
       
        If 'check_args' is True, this method will automatically check that
        correct number of arguments, as parsed from the usage line, are given.
        If the last argument in the usage line ends with the character 's',
        the maximum number of arguments is infinite.

        Possible errors in processing arguments are reported using DataError.
        """
        if argfile:
            args_list = self._add_args_from_file(args_list, argfile)
        opts, args = self._parse_args(args_list)
        if unescape:
            opts, args = self._unescape_opts_and_args(opts, args, unescape)
        if help and opts[help]:
            self._raise_help()
        if version and opts[version]:
            self._raise_version()
        if pythonpath:
            sys.path = self._get_pythonpath(opts[pythonpath]) + sys.path
        if check_args:
            self._check_args(args)
        return opts, args

    def _parse_args(self, args):
        args = [ self._lowercase_long_option(a) for a in args ]
        try:
            opts, args = getopt.getopt(args, self._short_opts, self._long_opts)
        except getopt.GetoptError, err:
            raise DataError(err)
        return self._process_opts(opts), self._glob_args(args)

    def _lowercase_long_option(self, opt):
        if not opt.startswith('--'):
            return opt
        if '=' not in opt:
            return opt.lower()
        opt, value = opt.split('=', 1)
        return '%s=%s' % (opt.lower(), value)

    def _check_args(self, args):
        if not self._arg_limits:
            raise FrameworkError('No argument information specified.')
        minargs, maxargs = self._arg_limits
        if minargs <= len(args) <= maxargs:
            return
        minend = plural_or_not(minargs)
        if minargs == maxargs:
            exptxt = "%d argument%s" % (minargs, minend)
        elif maxargs != sys.maxint:
            exptxt = "%d to %d arguments" % (minargs, maxargs)
        else:
            exptxt = "at least %d argument%s" % (minargs, minend)
        raise DataError("Expected %s, got %d." % (exptxt, len(args)))
   
    def _unescape_opts_and_args(self, opts, args, escape_opt):
        try:
            escape_strings = opts[escape_opt]
        except KeyError:
            raise FrameworkError("No escape option '%s' in given options")
        escapes = self._get_escapes(escape_strings)
        for name, value in opts.items():
            if name != escape_opt:
                opts[name] = self._unescape(value, escapes)
        args = [ self._unescape(arg, escapes) for arg in args ]
        return opts, args
   
    def _add_args_from_file(self, args, argfile_opt):
        argfile_opts = ['--'+argfile_opt]
        for sopt, lopt in self._short_to_long.items():
            if lopt == argfile_opt:
                argfile_opts.append('-'+sopt)       
        while True:
            try:
                index = self._get_argfile_index(args, argfile_opts)
                path = args[index+1]
            except IndexError:
                break
            args[index:index+2] = self._get_args_from_file(path)
        return args

    def _get_argfile_index(self, args, argfile_opts):
        for opt in argfile_opts:
            if opt in args:
                return args.index(opt)
        raise IndexError

    def _get_args_from_file(self, path):
        try:
            argfile = open(path)
        except IOError, err:
            raise DataError("Opening argument file '%s' failed: %s" % (path, err))
        args = []
        for line in argfile.readlines():
            line = line.strip()
            if line.startswith('-'):
                args.extend(line.split(' ', 1))
            elif line and not line.startswith('#'):
                args.append(line)
        argfile.close()
        return args
   
    def _get_escapes(self, escape_strings):
        escapes = {}
        for estr in escape_strings:
            try:
                name, value = estr.split(':', 1)
            except ValueError:
                raise DataError("Invalid escape string syntax '%s'. Expected: %s"
                                % (estr, 'what:with'))
            try:
                escapes[value] = ESCAPES[name.lower()]
            except KeyError:
                raise DataError("Invalid escape '%s'. Available: %s"
                                % (name, self._get_available_escapes()))
        return escapes

    def _unescape(self, value, escapes):
        if value is None or is_boolean(value):
            return value
        if is_list(value):
            return [ self._unescape(item, escapes) for item in value ]
        for esc_name, esc_value in escapes.items():
            value = value.replace(esc_name, esc_value)
        return value
   
    def _process_opts(self, opt_tuple):
        opts = self._init_opts()
        for name, value in opt_tuple:
            name = self._get_name(name)
            if name in self._multi_opts:
                opts[name].append(value)
            elif name in self._toggle_opts:
                opts[name] = not opts[name]
            else:
                opts[name] = value
        return opts

    def _glob_args(self, args):
        temp = []
        for path in args:
            paths = glob.glob(path)
            if paths:
                temp.extend(paths)
            else:
                temp.append(path)
        return temp
               
    def _init_opts(self):
        opts = {}
        for name in self._names:
            if name in self._multi_opts:
                opts[name] = []
            elif name in self._toggle_opts:
                opts[name] = False
            else:
                opts[name] = None
        return opts
       
    def _get_name(self, name):
        name = name.lstrip('-')
        try:
            return self._short_to_long[name]
        except KeyError:
            return name
       
    def _parse_usage(self, usage):
        for line in usage.splitlines():
            if not self._parse_opt_line(line) and not self._arg_limits:
                self._parse_usage_line(line)

    def _parse_usage_line(self, line):
        res = self._usage_line_re.match(line)
        if res:
            args = res.group(1).split()
            if not args:
                self._arg_limits = (0, 0)
            else:
                maxargs = args[-1].endswith('s') and sys.maxint or len(args)
                self._arg_limits = (len(args), maxargs)
       
    def _parse_opt_line(self, line):
        res = self._opt_line_re.match(line)
        if not res:
            return False
        long_opt = res.group(3).lower()
        if long_opt in self._names:
            self._raise_option_multiple_times_in_usage('--' + long_opt)
        self._names.append(long_opt)
        short_opts = [ opt[1] for opt in res.group(1).split() ]
        for sopt in short_opts:
            if self._short_to_long.has_key(sopt):
                self._raise_option_multiple_times_in_usage('-' + sopt)
            self._short_to_long[sopt] = long_opt
        # options allowed multiple times
        if res.group(5):
            self._multi_opts.append(long_opt)
        # options with arguments
        if res.group(4):
            long_opt += '='
            short_opts = [ sopt + ':' for sopt in short_opts ]
        else:
            self._toggle_opts.append(long_opt)
        self._long_opts.append(long_opt)
        self._short_opts += (''.join(short_opts))
        return True

    def _get_pythonpath(self, paths):
        if not is_list(paths):
            paths = [paths]
        temp = []
        for path in self._split_pythonpath(paths):
            temp.extend(glob.glob(path))
        paths = [ os.path.normpath(path) for path in temp if path != '' ]
        return paths

    def _split_pythonpath(self, paths):
        # paths may already contein ':' as separator
        tokens = ':'.join(paths).split(':')
        if os.sep == '/':
            return tokens
        # Fix paths split like 'c:\temp' -> 'c', '\temp'
        ret = []
        drive = ''
        for item in tokens:
            item = item.replace('/', '\\')
            if drive and item.startswith('\\'):
                ret.append('%s:%s' % (drive, item))
                drive = ''
                continue
            if drive:
                ret.append(drive)
                drive = ''
            if len(item) == 1 and item in string.letters:
                drive = item
            else:
                ret.append(item)
        if drive:
            ret.append(drive)
        return ret

    def _get_available_escapes(self):
        names = ESCAPES.keys()
        names.sort()
        return ', '.join([ '%s (%s)' % (n, ESCAPES[n]) for n in names ])

    def _raise_help(self):
        msg = self._usage
        if self._version:
            msg = msg.replace('', self._version)
        def replace_escapes(res):
            escapes = 'Available escapes:\n' + self._get_available_escapes()
            return wrap(escapes, len(res.group(2)), len(res.group(1)))
        msg = re.sub('( *)(<-+ESCAPES-+>)', replace_escapes, msg)
        raise Information(msg)

    def _raise_version(self):
        if not self._version:
            raise FrameworkError('Version not set')
        raise Information('%s %s' % (self._name, self._version))

    def _raise_option_multiple_times_in_usage(self, opt):
        raise FrameworkError("Option '%s' multiple times in usage" % opt)

 文件路径:robotframework-2.1.2\src\robot\utils\argumentparser.py
功能:参数解释,在了解主要流程之后,需要继续深入至此
 
阅读(42446) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~