From 98a608647f757c12aa94d1d6c196ae2b6b5dab10 Mon Sep 17 00:00:00 2001 From: Seblu <sebastien.luttringer@smartjog.com> Date: Mon, 23 May 2011 18:23:47 +0200 Subject: [PATCH] Improve commands loading Commands loading is now done by crawling commands directory under cccli module. All command which starts by Commmand_ and which is a son of Command class are candidate. --- cccli/cli.py | 2 +- cccli/{command => }/command.py | 120 ++++++++++++++++++++++ cccli/command/__init__.py | 90 ---------------- cccli/commands.py | 108 ------------------- cccli/commands/__init__.py | 8 ++ cccli/{command => commands}/account.py | 2 +- cccli/{command => commands}/alias.py | 2 +- cccli/{command => commands}/cancel.py | 2 +- cccli/{command => commands}/execute.py | 2 +- cccli/{command => commands}/expert.py | 2 +- cccli/{command => commands}/jobs.py | 2 +- cccli/{command => commands}/kill.py | 2 +- cccli/{command => commands}/list.py | 2 +- cccli/{command => commands}/migrate.py | 2 +- cccli/{command => commands}/right.py | 2 +- cccli/{command => commands}/server.py | 2 +- cccli/{command => commands}/shell.py | 2 +- cccli/{command => commands}/shutdown.py | 2 +- cccli/{command => commands}/tag.py | 2 +- cccli/{command => commands}/tagdisplay.py | 2 +- cccli/{command => commands}/vm.py | 2 +- setup.py | 4 +- 22 files changed, 147 insertions(+), 217 deletions(-) rename cccli/{command => }/command.py (70%) delete mode 100644 cccli/command/__init__.py delete mode 100644 cccli/commands.py create mode 100644 cccli/commands/__init__.py rename cccli/{command => commands}/account.py (98%) rename cccli/{command => commands}/alias.py (97%) rename cccli/{command => commands}/cancel.py (95%) rename cccli/{command => commands}/execute.py (95%) rename cccli/{command => commands}/expert.py (95%) rename cccli/{command => commands}/jobs.py (96%) rename cccli/{command => commands}/kill.py (93%) rename cccli/{command => commands}/list.py (99%) rename cccli/{command => commands}/migrate.py (98%) rename cccli/{command => commands}/right.py (98%) rename cccli/{command => commands}/server.py (97%) rename cccli/{command => commands}/shell.py (98%) rename cccli/{command => commands}/shutdown.py (96%) rename cccli/{command => commands}/tag.py (97%) rename cccli/{command => commands}/tagdisplay.py (98%) rename cccli/{command => commands}/vm.py (99%) diff --git a/cccli/cli.py b/cccli/cli.py index b9cb2e3..e00cbcb 100644 --- a/cccli/cli.py +++ b/cccli/cli.py @@ -9,7 +9,7 @@ CloudControl CLI main module import cccli from cccli.exception import * from cccli.printer import Printer, color -from cccli.commands import Commands, Aliases +from cccli.command import Commands, Aliases from cccli.handler import CliHandler from cccli.tagdisplay import TagDisplay from sjrpc.core.exceptions import * diff --git a/cccli/command/command.py b/cccli/command.py similarity index 70% rename from cccli/command/command.py rename to cccli/command.py index 4d098de..a8d0219 100644 --- a/cccli/command/command.py +++ b/cccli/command.py @@ -4,11 +4,126 @@ ''' CloudControl CLI command module ''' +import re +import ConfigParser +import os +import shlex +import imp from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color from optparse import OptionParser +import cccli.commands + +class Commands(object): + '''Command manager''' + + def __init__(self, cli): + # save cli context + self.cli = cli + # build command list + self.cmds = self.load_commands(cccli.commands.__path__[0], Command) + # build remote function list + try: + self.functions = set([ c["name"] for c in self.cli.rpc.call("functions") ]) + except RpcError as e: + raise cliError("RPCError: Unable to retrieve remote commands: %s"%str(e)) + # remove not available remote commands + for cname in tuple(self.cmds): + cobj = self.cmds[cname](self.cli, cname) + if isinstance(cobj, RemoteCommand): + try: + if len(cobj.remote_functions()) == 0: + raise NotImplementedError("No remote function") + if not cobj.remote_functions().issubset(self.functions): + del self.cmds[cname] + self.cli.printer.debug("Command %s not available"%cname) + except NotImplementedError as e: + self.cli.printer.debug("Command %s lack of remote_functions"%cname) + del self.cmds[cname] + + def __len__(self): + return len(self.cmds) + + def __contains__(self, item): + return item in self.cmds.keys() + + def __iter__(self): + return iter(self.cmds.keys()) + + def __repr__(self): + return repr(self.cmds.keys()) + + def __call__(self, argv): + # check argv + if len(argv) == 0: + raise cmdBadName() + # find right commands to call + if argv[0] not in self: + matchlist = [ x for x in self if re.match("%s.+"%re.escape(argv[0]), x) ] + if len(matchlist) == 1: + argv[0] = matchlist[0] + else: + raise cmdBadName() + # create class and call it + cmd = self.cmds[argv[0]](self.cli, argv[0]) + return cmd(argv) + + def usage(self, argv0): + '''Return usage of a command''' + u = self.cmds[argv0](self.cli, argv0).usage() + return u if u is not None else "" + + def help(self, argv0): + '''Return of a command''' + h = self.cmds[argv0](self.cli, argv0).help() + return h if h is not None else "" + + def load_commands(self, path, cls): + '''Load sublasss of cls from package name''' + cmds=dict() + for name in os.listdir(path): + if name.endswith(".py") and not name.startswith("__"): + fpath = os.path.join(path, name) + module = imp.load_source(os.path.splitext(name)[0], fpath) + for key, entry in module.__dict__.items(): + try: + if issubclass(entry, cls) and entry.__name__.startswith("Command_"): + cmds[entry.__name__[8:]] = entry + except TypeError: + continue + return cmds + + +class Aliases(dict): + ''' Aliases manager''' + def load(self, filename): + '''load alias from file''' + if filename is not None: + fparser = ConfigParser.RawConfigParser() + fparser.read(filename) + if fparser.has_section("alias"): + self.clear() + self.update(fparser.items("alias")) + + def save(self, filename): + '''save alias on file''' + if filename is not None: + fparser = ConfigParser.RawConfigParser() + fparser.read(filename) + fparser.remove_section("alias") + fparser.add_section("alias") + for n,v in self.items(): + fparser.set("alias", n, v) + fparser.write(open(filename, "w")) + + def substitute(self, argv): + if argv[0] in self: + oldargv = argv[1:] + argv = shlex.split(self[argv[0]]) + argv.extend(oldargv) + return argv class Command(object): '''Base of all command class''' @@ -19,6 +134,11 @@ class Command(object): self.name = argv0 def __call__(self, argv): + '''Code called when command is invoked''' + raise NotImplementedError + + def name(self): + '''Name of the command''' raise NotImplementedError def usage(self): diff --git a/cccli/command/__init__.py b/cccli/command/__init__.py deleted file mode 100644 index d4d3fc5..0000000 --- a/cccli/command/__init__.py +++ /dev/null @@ -1,90 +0,0 @@ -#!/usr/bin/env python -#coding=utf8 - -''' -CloudControl CLI Commands Package -''' - -__all__ = [ - "Command", - "OptionCommand", - "RemoteCommand", - "TqlCommand", - "Command_addaccount", - "Command_addright", - "Command_addtag", - "Command_alias", - "Command_cancel", - "Command_clear", - "Command_clone", - "Command_close", - "Command_declose", - "Command_delaccount", - "Command_delright", - "Command_deltag", - "Command_destroy", - "Command_execute", - "Command_expert", - "Command_help", - "Command_history", - "Command_jobs", - "Command_kill", - "Command_list", - "Command_migrate", - "Command_passwd", - "Command_pause", - "Command_quit", - "Command_resume", - "Command_rights", - "Command_server", - "Command_shutdown", - "Command_start", - "Command_stop", - "Command_tagdisplay", - "Command_tags", - "Command_unalias", - "Command_undefine", - "Command_usage", - "Command_version", - "Command_whoami", -] - -# importing -from cccli.command.command import Command, OptionCommand, RemoteCommand, TqlCommand -from cccli.command.account import Command_addaccount -from cccli.command.account import Command_close -from cccli.command.account import Command_declose -from cccli.command.account import Command_delaccount -from cccli.command.account import Command_passwd -from cccli.command.alias import Command_alias -from cccli.command.alias import Command_unalias -from cccli.command.cancel import Command_cancel -from cccli.command.execute import Command_execute -from cccli.command.expert import Command_expert -from cccli.command.jobs import Command_jobs -from cccli.command.kill import Command_kill -from cccli.command.list import Command_list -from cccli.command.migrate import Command_migrate -from cccli.command.right import Command_addright -from cccli.command.right import Command_delright -from cccli.command.right import Command_rights -from cccli.command.server import Command_server -from cccli.command.shell import Command_clear -from cccli.command.shell import Command_help -from cccli.command.shell import Command_history -from cccli.command.shell import Command_quit -from cccli.command.shell import Command_usage -from cccli.command.shell import Command_version -from cccli.command.shell import Command_whoami -from cccli.command.shutdown import Command_shutdown -from cccli.command.tag import Command_addtag -from cccli.command.tag import Command_deltag -from cccli.command.tag import Command_tags -from cccli.command.tagdisplay import Command_tagdisplay -from cccli.command.vm import Command_clone -from cccli.command.vm import Command_destroy -from cccli.command.vm import Command_pause -from cccli.command.vm import Command_resume -from cccli.command.vm import Command_start -from cccli.command.vm import Command_stop -from cccli.command.vm import Command_undefine diff --git a/cccli/commands.py b/cccli/commands.py deleted file mode 100644 index 21a463f..0000000 --- a/cccli/commands.py +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env python -#coding=utf8 - -''' -CloudControl CLI commands module -''' -import re -import ConfigParser -import os -import shlex - -from cccli.exception import * -from cccli.command import * - -class Commands(object): - '''Command manager''' - - def __init__(self, cli): - # save cli context - self.cli = cli - # build command list - self.cmds = dict() - for x in [ x for x in globals() if x.startswith("Command_") ]: - self.cmds[x[8:]] = globals()[x] - # build remote function list - try: - self.functions = set([ c["name"] for c in self.cli.rpc.call("functions") ]) - except RpcError as e: - raise cliError("RPCError: Unable to retrieve remote commands: %s"%str(e)) - # remove not available commands - for cname in tuple(self.cmds): - cobj = self.cmds[cname](self.cli, cname) - if isinstance(cobj, RemoteCommand): - try: - if len(cobj.remote_functions()) == 0: - raise NotImplementedError("No remote function") - if not cobj.remote_functions().issubset(self.functions): - del self.cmds[cname] - self.cli.printer.debug("Command %s not available"%cname) - except NotImplementedError as e: - self.cli.printer.debug("Command %s lack of remote_functions"%cname) - del self.cmds[cname] - - def __len__(self): - return len(self.cmds) - - def __contains__(self, item): - return item in self.cmds.keys() - - def __iter__(self): - return iter(self.cmds.keys()) - - def __repr__(self): - return repr(self.cmds.keys()) - - def __call__(self, argv): - # check argv - if len(argv) == 0: - raise cmdBadName() - # find right commands to call - if argv[0] not in self: - matchlist = [ x for x in self if re.match("%s.+"%re.escape(argv[0]), x) ] - if len(matchlist) == 1: - argv[0] = matchlist[0] - else: - raise cmdBadName() - # create class and call it - cmd = self.cmds[argv[0]](self.cli, argv[0]) - return cmd(argv) - - def usage(self, argv0): - '''Return usage of a command''' - u = self.cmds[argv0](self.cli, argv0).usage() - return u if u is not None else "" - - def help(self, argv0): - '''Return of a command''' - h = self.cmds[argv0](self.cli, argv0).help() - return h if h is not None else "" - -class Aliases(dict): - ''' Aliases manager''' - def load(self, filename): - '''load alias from file''' - if filename is not None: - fparser = ConfigParser.RawConfigParser() - fparser.read(filename) - if fparser.has_section("alias"): - self.clear() - self.update(fparser.items("alias")) - - def save(self, filename): - '''save alias on file''' - if filename is not None: - fparser = ConfigParser.RawConfigParser() - fparser.read(filename) - fparser.remove_section("alias") - fparser.add_section("alias") - for n,v in self.items(): - fparser.set("alias", n, v) - fparser.write(open(filename, "w")) - - def substitute(self, argv): - if argv[0] in self: - oldargv = argv[1:] - argv = shlex.split(self[argv[0]]) - argv.extend(oldargv) - return argv diff --git a/cccli/commands/__init__.py b/cccli/commands/__init__.py new file mode 100644 index 0000000..5796bf0 --- /dev/null +++ b/cccli/commands/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python +#coding=utf8 + +''' +CloudControl CLI Commands Package +''' + +__all__ = [] diff --git a/cccli/command/account.py b/cccli/commands/account.py similarity index 98% rename from cccli/command/account.py rename to cccli/commands/account.py index 350b846..280cf6a 100644 --- a/cccli/command/account.py +++ b/cccli/commands/account.py @@ -8,7 +8,7 @@ CloudControl accounts related commands from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_addaccount(TqlCommand): '''Create an account''' diff --git a/cccli/command/alias.py b/cccli/commands/alias.py similarity index 97% rename from cccli/command/alias.py rename to cccli/commands/alias.py index a41da61..e499511 100644 --- a/cccli/command/alias.py +++ b/cccli/commands/alias.py @@ -6,7 +6,7 @@ CloudControl alias related command ''' from cccli.exception import * -from cccli.command.command import OptionCommand +from cccli.command import OptionCommand import re class Command_alias(OptionCommand): diff --git a/cccli/command/cancel.py b/cccli/commands/cancel.py similarity index 95% rename from cccli/command/cancel.py rename to cccli/commands/cancel.py index 08e1d45..0eac3ea 100644 --- a/cccli/command/cancel.py +++ b/cccli/commands/cancel.py @@ -8,7 +8,7 @@ CloudControl cancel command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import RemoteCommand +from cccli.command import RemoteCommand class Command_cancel(RemoteCommand): '''Cancel a job''' diff --git a/cccli/command/execute.py b/cccli/commands/execute.py similarity index 95% rename from cccli/command/execute.py rename to cccli/commands/execute.py index 41dc467..49ea3aa 100644 --- a/cccli/command/execute.py +++ b/cccli/commands/execute.py @@ -6,7 +6,7 @@ CloudControl execute command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_execute(TqlCommand): '''Execute a command on the remote host''' diff --git a/cccli/command/expert.py b/cccli/commands/expert.py similarity index 95% rename from cccli/command/expert.py rename to cccli/commands/expert.py index 9ab46be..3b31fee 100644 --- a/cccli/command/expert.py +++ b/cccli/commands/expert.py @@ -6,7 +6,7 @@ CloudControl expert command ''' from cccli.exception import * from cccli.printer import Printer, color -from cccli.command.command import Command +from cccli.command import Command from sjrpc.utils import ConnectionProxy import code diff --git a/cccli/command/jobs.py b/cccli/commands/jobs.py similarity index 96% rename from cccli/command/jobs.py rename to cccli/commands/jobs.py index 570807e..a6eb370 100644 --- a/cccli/command/jobs.py +++ b/cccli/commands/jobs.py @@ -8,7 +8,7 @@ CloudControl jobs command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_jobs(TqlCommand): '''List jobs''' diff --git a/cccli/command/kill.py b/cccli/commands/kill.py similarity index 93% rename from cccli/command/kill.py rename to cccli/commands/kill.py index 5ce3018..d89e108 100644 --- a/cccli/command/kill.py +++ b/cccli/commands/kill.py @@ -8,7 +8,7 @@ CloudControl Connection related commands from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_kill(TqlCommand): '''Kill a server connection''' diff --git a/cccli/command/list.py b/cccli/commands/list.py similarity index 99% rename from cccli/command/list.py rename to cccli/commands/list.py index e4c1334..63c0ff4 100644 --- a/cccli/command/list.py +++ b/cccli/commands/list.py @@ -8,7 +8,7 @@ CloudControl list command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand import math import os diff --git a/cccli/command/migrate.py b/cccli/commands/migrate.py similarity index 98% rename from cccli/command/migrate.py rename to cccli/commands/migrate.py index 967ccf2..3fc6bf2 100644 --- a/cccli/command/migrate.py +++ b/cccli/commands/migrate.py @@ -8,7 +8,7 @@ CloudControl migrate command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_migrate(TqlCommand): '''Migrate vm''' diff --git a/cccli/command/right.py b/cccli/commands/right.py similarity index 98% rename from cccli/command/right.py rename to cccli/commands/right.py index c4fe42d..2e5250b 100644 --- a/cccli/command/right.py +++ b/cccli/commands/right.py @@ -8,7 +8,7 @@ CloudControl right releated commands from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import Command, TqlCommand +from cccli.command import Command, TqlCommand class Command_rights(TqlCommand): '''List account rights''' diff --git a/cccli/command/server.py b/cccli/commands/server.py similarity index 97% rename from cccli/command/server.py rename to cccli/commands/server.py index 2252b66..2d4cc3c 100644 --- a/cccli/command/server.py +++ b/cccli/commands/server.py @@ -7,7 +7,7 @@ CloudControl server command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import RemoteCommand +from cccli.command import RemoteCommand class Command_server(RemoteCommand): '''Server manipulation command''' diff --git a/cccli/command/shell.py b/cccli/commands/shell.py similarity index 98% rename from cccli/command/shell.py rename to cccli/commands/shell.py index 886ff1f..6e88a91 100644 --- a/cccli/command/shell.py +++ b/cccli/commands/shell.py @@ -7,7 +7,7 @@ CloudControl shells related commands from cccli.exception import * from cccli.printer import Printer, color -from cccli.command.command import Command +from cccli.command import Command class Command_quit(Command): '''Quit application with respect''' diff --git a/cccli/command/shutdown.py b/cccli/commands/shutdown.py similarity index 96% rename from cccli/command/shutdown.py rename to cccli/commands/shutdown.py index c80c6de..fd61661 100644 --- a/cccli/command/shutdown.py +++ b/cccli/commands/shutdown.py @@ -6,7 +6,7 @@ CloudControl physical host related commands from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_shutdown(TqlCommand): '''Shutdown a physical host''' diff --git a/cccli/command/tag.py b/cccli/commands/tag.py similarity index 97% rename from cccli/command/tag.py rename to cccli/commands/tag.py index ba10e1e..d9b34f8 100644 --- a/cccli/command/tag.py +++ b/cccli/commands/tag.py @@ -8,7 +8,7 @@ CloudControl tag releated commands from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand from optparse import OptionParser diff --git a/cccli/command/tagdisplay.py b/cccli/commands/tagdisplay.py similarity index 98% rename from cccli/command/tagdisplay.py rename to cccli/commands/tagdisplay.py index 884554d..ea8dcaa 100644 --- a/cccli/command/tagdisplay.py +++ b/cccli/commands/tagdisplay.py @@ -7,7 +7,7 @@ CloudControl tagdisplay command from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import OptionCommand +from cccli.command import OptionCommand class Command_tagdisplay(OptionCommand): '''Tagdisplay tool''' diff --git a/cccli/command/vm.py b/cccli/commands/vm.py similarity index 99% rename from cccli/command/vm.py rename to cccli/commands/vm.py index 6892051..814013e 100644 --- a/cccli/command/vm.py +++ b/cccli/commands/vm.py @@ -6,7 +6,7 @@ CloudControl VM related commands from cccli.exception import * from sjrpc.core.exceptions import * from cccli.printer import Printer, color -from cccli.command.command import TqlCommand +from cccli.command import TqlCommand class Command_start(TqlCommand): '''Start a stopped vm''' diff --git a/setup.py b/setup.py index 0e03c42..c9473e1 100644 --- a/setup.py +++ b/setup.py @@ -11,8 +11,8 @@ setup( long_description=ldesc, author='Sebastien Luttringer', author_email='sebastien.luttringer@smartjog.com', - license='GPL2', - packages=['cccli', 'cccli.command'], + license='GPL2', + packages=['cccli', 'cccli.commands'], scripts=['bin/cc-cli'], classifiers=[ 'Operating System :: Unix', -- GitLab