diff --git a/bin/cc-cli b/bin/cc-cli index df03cb5af735708496f3838cd15223798fa76dc5..048a36d70a831e6023cfd6f71279a68e191c8d23 100755 --- a/bin/cc-cli +++ b/bin/cc-cli @@ -19,13 +19,15 @@ import getpass settings = { "port": "1984", - "timeout": "5" + "timeout": "5", + "hsize": "100" } try: # parse rc file if "HOME" in os.environ: settings["alias"] = "%s/.cc-cli.conf"%os.environ["HOME"] + settings["history"] = "%s/.cc-cli.history"%os.environ["HOME"] if os.access("%s/.cc-cli.conf"%os.environ["HOME"], os.R_OK): fparser = ConfigParser.SafeConfigParser() fparser.read("%s/.cc-cli.conf"%os.environ["HOME"]) @@ -72,13 +74,20 @@ try: dest="timeout", default="10", help="connection timeout") - - - + oparser.add_option("--history-file", + action="store", + dest="history", + default="", + help="History file") + oparser.add_option("--history-size", + action="store", + dest="hsize", + default="", + help="History max entry count") (options, args) = oparser.parse_args() # options handling - for i in ("debug", "login", "server", "port", "timeout"): + for i in ("debug", "login", "server", "port", "timeout", "history", "hsize"): if hasattr(options, i): o = getattr(options, i) if o: @@ -96,18 +105,12 @@ try: if "server" not in settings: raise BadArgument("No server address") - # check port value - try: - settings["port"] = int(settings["port"]) - except: - raise BadArgument("Invalid port number") - - # check timeout value - try: - settings["timeout"] = int(settings["timeout"]) - except: - raise BadArgument("Invalid timeout") - + # check int values + for i in "port", "timeout", "hsize": + try: + settings[i] = int(settings[i]) + except: + raise BadArgument("Invalid %s number"%i) # check login if "login" not in settings: settings["login"] = raw_input("Login: ") @@ -117,8 +120,8 @@ try: settings["pass"] = getpass.getpass("Password: ") # start cli - cli = cli.Cli(settings, args) - cli.start() + cli = cli.Cli(settings) + cli.start(" ".join(args)) except BadArgument, e: printer.error("Bad Argument: %s"%str(e)) oparser.print_help() diff --git a/cccli/cli.py b/cccli/cli.py index 6f0b6edbb6bd365932007fbf3787044be350a16c..e0aab5ea8ede9a3ad9bd394b6207fd63b44b58e8 100644 --- a/cccli/cli.py +++ b/cccli/cli.py @@ -12,67 +12,74 @@ import ssl import threading import subprocess import ConfigParser -import cccli -from cccli import printer, command -from cccli.clierror import * +import re + +from cccli import printer, command, version, debug from cccli.command import Command +from cccli.clierror import * +from sjrpc.core.exceptions import * from sjrpc.client import SimpleRpcClient from sjrpc.utils import RpcHandler, ConnectionProxy, pure -import sjrpc.core.exceptions - -import re -import readline class Cli(object): - def __init__(self, settings, args): - self._settings = settings - self.alias = Alias(settings.get("alias", "")) - self.alias.load() - self.interactive = sys.stderr.isatty() and sys.stdin.isatty() - self._prompt = "> " - self._commands = args + def __init__(self, settings): + self.isinteractive = sys.stderr.isatty() and sys.stdin.isatty() + self.settings = settings + self.prompt = "> " self.rpc = None + self.alias = Alias() + self.history = History() - def start(self): + def start(self, line=""): '''Start a CLI''' + # not interactive is command line + if line: + self.isinteractive = False + # start readline and load history + if self.isinteractive: + import readline + self.history.readline = readline + self.history.load(self.settings.get("history", "")) + self.history.maxsize(self.settings.get("hsize", None)) + # load alias + self.alias.load(self.settings.get("alias", "")) + printer.debug("Alias: %s"%self.alias) # Connecting self._connect() # authentifications self._auth() # run parsing args - if len(self._commands) > 0: - self._parse_line(" ".join(self._commands)) - elif self.interactive: - self._interactive_parser() + if line: + self._parse_line(line) else: - self._parser() + self._shell() + self.history.save(self.settings.get("history", "")) + def _connect(self): printer.debug("Connecting...") - rpcc = SimpleRpcClient.from_addr(self._settings["server"], - self._settings["port"], + rpcc = SimpleRpcClient.from_addr(self.settings["server"], + self.settings["port"], enable_ssl=True, default_handler=CliHandler(), on_disconnect="quit", - timeout=self._settings["timeout"] + timeout=self.settings["timeout"] ) rpcc.start(daemonize=True) self.rpc = ConnectionProxy(rpcc) def _auth(self): printer.debug("Authenticating...") - if self.rpc.authentify(self._settings["login"], self._settings["pass"]): + if self.rpc.authentify(self.settings["login"], self.settings["pass"]): printer.debug("Authenticated.") else: printer.fatal("Autentification failed!") - def _interactive_parser(self): - '''Interactive shell parser''' - # init readline - + def _shell(self): + '''Shell parser''' while True: try: - line = raw_input(self._prompt) + line = raw_input(self.prompt) self._parse_line(line) except EOFError: printer.out("") @@ -81,19 +88,8 @@ class Cli(object): break except KeyboardInterrupt: printer.out("") - try: - pass - #readline.write_history_file(self._settings["histfile"]) - except IOError: - pass printer.out("Tcho!") - def _parser(self): - '''Non interactive parser''' - while True: - line = raw_input(self._prompt) - self._parse_line(line) - def _parse_line(self, line): '''Parse a line (more than one command)''' for cmd in line.split(";"): @@ -130,11 +126,11 @@ class Cli(object): printer.error("command: %s."%str(e)) else: printer.error("No command: %s."%argv[0]) - except sjrpc.core.exceptions.RpcError, e: - if cccli.debug: raise + except RpcError, e: + if debug: raise printer.error("sjRPC: %s"%str(e)) except Exception, e: - if cccli.debug: raise + if debug: raise printer.error("%s: %s."%(argv[0], str(e))) def _lex_argv(self, string): @@ -143,28 +139,70 @@ class Cli(object): class Alias(dict): ''' Alias wrapper''' - def __init__(self, filename): - self._filename = filename - - def load(self): + def load(self, filename): '''load alias from file''' - if os.access(self._filename, os.R_OK): + if os.access(filename, os.R_OK): fparser = ConfigParser.SafeConfigParser() - fparser.read(self._filename) + fparser.read(filename) if fparser.has_section("alias"): self.clear() self.update(fparser.items("alias")) - def save(self): + def save(self, filename): '''save alias on file''' - if os.access(self._filename, os.R_OK or os.W_OK): + if os.access(filename, os.R_OK or os.W_OK): fparser = ConfigParser.SafeConfigParser() - fparser.read(self._filename) + 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(self._filename, "w")) + fparser.write(open(filename, "w")) + + +class History(object): + '''History class''' + def __init__(self, readline=None): + self.readline = readline + + def __nonzero__(self): + return not self.readline is None + + def __getattribute__(self, name): + r = object.__getattribute__(self, "readline") + if name == "readline": + return r + if r is None: + return lambda *a,**k: None + return object.__getattribute__(self, name) + + def __iter__(self): + for i in range(1, len(self)): + yield self.readline.get_history_item(i) + + def __len__(self): + return self.readline.get_current_history_length() + + def load(self, path): + '''Load history from a file''' + try: + self.readline.read_history_file(path) + except IOError: + pass + + def save(self, path): + '''Save history into path''' + try: + self.readline.write_history_file(path) + except IOError: + pass + + def maxsize(self, size=None): + '''Set or return max history size''' + if size is not None: + self.readline.set_history_length(size) + return self.readline.get_history_length() + class CliHandler(RpcHandler): '''Handle RPC incoming request''' @@ -172,9 +210,8 @@ class CliHandler(RpcHandler): @pure def get_tags(self, tags=()): if "version" in tags: - return { "version": cccli.version } + return { "version": version } @pure def quit(self, rpc=None): printer.fatal("Disconnected from server!") - diff --git a/cccli/command.py b/cccli/command.py index eb36521509c5a982b099d950b4a5e70c733eeda3..104fe780fb57846c907068da569897dcae5cf5c3 100644 --- a/cccli/command.py +++ b/cccli/command.py @@ -126,7 +126,7 @@ class Command(object): printer.out("%s=%s"%(argv[1], self.cli.alias[argv[1]])) elif len(argv) == 3: self.cli.alias[argv[1]] = argv[2] - self.cli.alias.save() + self.cli.alias.save(self.cli.settings.get("alias", "")) else: raise BadArgument() cmd_alias.usage = "alias [name] [value]" @@ -139,7 +139,7 @@ class Command(object): if argv[1] not in self.cli.alias: raise BadArgument("%s: No such alias"%argv[1]) del self.cli.alias[argv[1]] - self.cli.alias.save() + self.cli.alias.save(self.cli.settings.get("alias", "")) cmd_unalias.usage = "unalias [name]" cmd_unalias.desc = "Remove an aliases" @@ -150,6 +150,15 @@ class Command(object): cmd_rcmd.usage = "rcmd" cmd_rcmd.desc = "Print remote command list" + def cmd_history(self, argv): + '''Show history of commands''' + if not self.cli.history: + raise Exception("History is disabled") + for l in self.cli.history: + printer.out(l) + cmd_history.usage = "history" + cmd_history.desc = "Show commands history" + def cmd_list(self, argv): '''List objects''' if len(argv) == 1: