2011年11月4日金曜日

サブコマンドをラッピングするためのクラス

#! /usr/bin/env python
#-*- coding: utf-8 -*-
import sys
import os
import optparse
import subprocess

class Messages:
    need_param_invalid = 'The arguments is insufficient.'
    param_not_supplied = '{0} option not supplied.'
    param_not_support  = 'not support parameter.'

class TransparentOptionParser(optparse.OptionParser):
    # over ride on arguments from 'rargs' 'values' consuming.
    def _process_args(self, largs, rargs, values):
        """_process_args(largs : [string],
                         rargs : [string],
                         values : Values)

        Process command-line arguments and populate 'values', consuming
        options and arguments from 'rargs'.  If 'allow_interspersed_args' is
        false, stop at the first non-option argument.  If true, accumulate any
        interspersed non-option arguments in 'largs'.
        """
        def _through_option(func, *args, **kwds):
            """new function"""
            try:
                func(*args, **kwds)
            except optparse.BadOptionError, err:
                largs.append(err.opt_str)

        while rargs:
            arg = rargs[0]
            # We handle bare "--" explicitly, and bare "-" is handled by the
            # standard arg handler since the short arg case ensures that the
            # len of the opt string is greater than 1.
            if arg == "--":
                del rargs[0]
                return
            elif arg[0:2] == "--":
                # process a single long option (possibly with value(s))
                self._process_long_opt(rargs, values)
            elif arg[:1] == "-" and len(arg) > 1:
                # process a cluster of short options (possibly with
                # value(s) for the last one only)
                _through_option(self._process_short_opts, rargs, values) # modified
            elif self.allow_interspersed_args:
                largs.append(arg)
                del rargs[0]
            else:
                return                  # stop now, leave this arg in rargs

        # Say this is the original argument list:
        # [arg0, arg1, ..., arg(i-1), arg(i), arg(i+1), ..., arg(N-1)]
        #                            ^
        # (we are about to process arg(i)).
        #
        # Then rargs is [arg(i), ..., arg(N-1)] and largs is a *subset* of
        # [arg0, ..., arg(i-1)] (any options and their arguments will have
        # been removed from largs).
        #
        # The while loop will usually consume 1 or more arguments per pass.
        # If it consumes 1 (eg. arg is an option that takes no arguments),
        # then after _process_arg() is done the situation is:
        #
        #   largs = subset of [arg0, ..., arg(i)]
        #   rargs = [arg(i+1), ..., arg(N-1)]
        #
        # If allow_interspersed_args is false, largs will always be
        # *empty* -- still a subset of [arg0, ..., arg(i-1)], but
        # not a very interesting subset!

    def parse_args(self, args=None, values=None):
        if args is not None:
            args = list(args)
        return optparse.OptionParser.parse_args(self, args, values)

class SubOptionParser(TransparentOptionParser):
    def parse_args(self, args, need=0, ignore=[]):
        args_org = args
        if ignore:
            args = filter(lambda arg: arg not in ignore, args)
        opts, args = TransparentOptionParser.parse_args(self, args_org)
        if ignore:
            args += filter(lambda arg: arg not in ignore, args_org)

        if need is None:
            return opts, args
        else:
            need = int(need) # raise TypeError
            try:
                if need == 1:
                    need_arg = args.pop(0) # raise IndexError
                    return opts, args, need_arg
                elif need > 1:
                    need_arg = [args.pop(0) for ii in range(need)] # raise IndexError
                    return opts, args, need_arg
                else:
                    raise IndexError, Messages.need_param_invalid
            except IndexError, err:
                self.error(msg(Messages.need_param_invalid, need))

    def requires(self, *opts):
        for opt in opts:
            self.check_required(opt)

    def check_required(self, opt):
        option = self.get_option(opt)
        
        # assumes the option's 'default' is set to None !
        if getattr(self.values, option.dest):
            self.error(param_not_supplied.format(option))
    
class DummyChild(object):
    def wait(self):
        return 0

def call(*args, **kwds):
    args = list(args)
    args = map(str, args)
    
    try:
        env = os.environ['SUBCOMMAND']
    except KeyError, err:
        env = ''
    
    env = env.lower().split(':')

    if 'noexec' in env:
        child = DummyChild()
    else: # execute
        child = subprocess.Popen(args)
    
    
    try:
        nowait = kwds['nowait']
    except KeyError:
        nowait = False

    if nowait:
        return child
    else:
        return child.wait()

class SubCommand(object):
    def _usage(self, *args, **kwds):
        funcs = dir(self)
        funcs = map(lambda x: x.lower(), funcs)
        funcs = filter(lambda x: x[0] != '_' or x == 'call', funcs)
        usage = '{0} subcommand [options]\n\nsubcommand list\n\n'.format(self.__class__.__name__.lower())
        for name in funcs:
            usage += ' * {0}\n'.format(name)
        return usage

    def _call(self, *args, **kwds):
        parser = SubOptionParser(usage=self._usage())
        opts, args, subcmd = parser.parse_args(args, need=1, ignore=['-h', '--help'])
        name = subcmd.lower()
        attrs = dir(self)
        subcmds = filter(lambda attr: attr.lower() == name, attrs)
        
        length = len(subcmds)
        if length < 1:
            parser.error(param_not_support, name)
        assert length == 1, str(length)

        name = subcmds[0]
        try:
            class_or_func = getattr(self, name)
        except AttributeError, err:
            parser.print_usage()
            assert False, str(err)
        
        if hasattr(class_or_func, '_call'): # class
            subcmd = class_or_func()
            return subcmd._call(*args, **kwds)
        else:
            return class_or_func(*argrs, **kwds)

def msg(*args):
    args = map(str, args)
    return ': '.join(args)

def dispatch(subcommands, *args, **kwds):
    subcommands = dict([(subcmd.__name__.lower(), subcmd) for subcmd in subcommands])
    args = list(args)

    name = args.pop(0) # raise IndexError
    
    
    name = name.lower()

    cmd_class = subcommands[name] # raise KeyError
    cmd = cmd_class()
    return cmd._call(*args, **kwds)

def test():
    class TESTA(SubCommand):
        class TESTAA(SubCommand):
            def _call(self, *args, **kwds):
                call('python', '-c', 'print "{0}"'.format(self.__class__.__name__))

        class TESTAB(SubCommand):
            def _call(self, *args, **kwds):
                call('python', '-c', 'print "{0}"'.format(self.__class__.__name__))

        class TESTAC(SubCommand):
            def _call(self, *args, **kwds):
                call('python', '-c', 'print "{0}"'.format(self.__class__.__name__))

    class TESTB(SubCommand):
        class TESTBA(SubCommand):
            def _call(self, *args, **kwds):
                call('python', '-c', 'print "{0}"'.format(self.__class__.__name__))

        class TESTBB(SubCommand):
            def _call(self, *args, **kwds):
                call('python', '-c', 'print "{0}"'.format(self.__class__.__name__))

        class TESTBC(SubCommand):
            def _call(self, *args, **kwds):
                call('python', '-c', 'print "{0}"'.format(self.__class__.__name__))


    dispatch([TESTA, TESTB], *sys.argv[1:])

if __name__ == '__main__':
    test()

0 件のコメント:

コメントを投稿