diff --git a/README b/README index 55d87f2192208d38f826b92c362c8ae87b5b298e..9888d121d487a56590c20ed39f0f70dcbaa6edac 100644 --- a/README +++ b/README @@ -1,107 +1,6 @@ -================== -Tag Query Language -================== -== by examples == -# list accounts -> list a - -# list hypervisor -> list hv - -# list vm -> list vm - -# list vm of hypervisor toto -> list hv=toto&vm - -# list vm chiche of hypervisor toto -> list hv=toto&vm=chiche - -#list vm with 2 cpu -> list vm&cpu=2 - -#list vm with 2 cpu and mem > 10g -> list vm&cpu=2&mem>10g - -#list hypervistor with 2cpu and vm with 2cpu -> list hv&cpu=2&vm&cpu=2 - -#list hypervistor at least 2cpu and show tags pop and alloc -> list hv&cpu>=2$pop$alloc - -== Basics == -- TQL build a list of objects from left to right -- Every tag can add or remove objects -- Separators create link between tag -- Operators apply only on one tag - -== separators of tags == -& and between tags -$ show a tag - -== operators on tags == -= strict equality -!= not strict equlity -: globing matching -!:not globing matching -~ regex matching -!~ not regex matching -> superior strict ->= superior -< inferior -<= inferior strict - -== number facility == -10k = 1000 -10ki = 1024 -1m = 1000 ^ 2 -1mi = 1024 ^ 2 -1g = 1000 ^ 3 -1gi = 1024 ^ 3 - -== well known tags == -a: account name (eg: bobby) -role: account role (eg: hypersivor/host/cli/vm) -id: a[.vm] (eg: hkvm-itx1-3.slfw-b) -con: connection uptime or offline (eg: 3600/offline) -hv: hypervisor name (eg: kvm-chausette) -vm: virtual machine name (eg: access) -h: hostname (eg: access) -hvtype: hypervistor type (xen/kvm) -libvirtver: Libvirt version -status: Vm status (eg: running/paused/stopped) -pop: Point of Presence -cpu: cpu count -mem: memory size -memused: memory used -memfree: memory free -arch: (x86/x64) -uname: uname of host -uptime: uptime of hostname -load: load average -hvm: hardware virtualisation enabled -alloc: host is allowed to be selected to a migration -nvm: vm count on an hypervisor -version: account version -sto: storage pool names (eg: vg fg) -stovg_type: vg storage pool type (eg: lvm) -stovg_size: vg storage pool size (eg: 1042) -stovg_used: vg storage pool used space (eg: 1) -stovg_free: vg storage pool free space (eg: 1041) -stovg_path: vg storage pool path (eg: /dev/vg/) -stovg_vol: vg storage pool volume list (eg: sex titi toto) -disk: disk index list (eg: 1 2 3 4) -disk1_path: disk 1 path (eg: /dev/vg/sex) -disk1_size: disk 1 size (eg: 1024) -disk1_pool: storage pool back reference (eg: vg) [vmonly] -disk1_voln: storage pool volume back reference (eg: ) [vmonly] - =========== New release =========== -Update version in debian/control -Update version in debian/changelog -Update version in setup.py Update version in cccli/__init__.py - - +Update version in debian/control +Update version in debian/changelog \ No newline at end of file diff --git a/TQL b/TQL new file mode 100644 index 0000000000000000000000000000000000000000..20a4347e98fe468f3dadab443c41f6c355599ce0 --- /dev/null +++ b/TQL @@ -0,0 +1,98 @@ +================== +Tag Query Language +================== +== by examples == +# list accounts +> list a + +# list hypervisor +> list hv + +# list vm +> list vm + +# list vm of hypervisor toto +> list hv=toto&vm + +# list vm chiche of hypervisor toto +> list hv=toto&vm=chiche + +#list vm with 2 cpu +> list vm&cpu=2 + +#list vm with 2 cpu and mem > 10g +> list vm&cpu=2&mem>10g + +#list hypervistor with 2cpu and vm with 2cpu +> list hv&cpu=2&vm&cpu=2 + +#list hypervistor at least 2cpu and show tags pop and alloc +> list hv&cpu>=2$pop$alloc + +== Basics == +- TQL build a list of objects from left to right +- Every tag can add or remove objects +- Separators create link between tag +- Operators apply only on one tag + +== separators of tags == +& and between tags +$ show a tag +# limit output + +== operators on tags == += strict equality +!= not strict equlity +: globing matching +!:not globing matching +~ regex matching +!~ not regex matching +> superior strict +>= superior +< inferior +<= inferior strict + +== number facility == +10k = 1000 +10ki = 1024 +1m = 1000 ^ 2 +1mi = 1024 ^ 2 +1g = 1000 ^ 3 +1gi = 1024 ^ 3 + +== well known tags == +a: account name (eg: bobby) +role: account role (eg: hypersivor/host/cli/vm) +id: a[.vm] (eg: hkvm-itx1-3.slfw-b) +con: connection uptime or offline (eg: 3600/offline) +hv: hypervisor name (eg: kvm-chausette) +vm: virtual machine name (eg: access) +h: hostname (eg: access) +hvtype: hypervistor type (xen/kvm) +libvirtver: Libvirt version +status: Vm status (eg: running/paused/stopped) +pop: Point of Presence +cpu: cpu count +mem: memory size +memused: memory used +memfree: memory free +arch: (x86/x64) +uname: uname of host +uptime: uptime of hostname +load: load average +hvm: hardware virtualisation enabled +alloc: host is allowed to be selected to a migration +nvm: vm count on an hypervisor +version: account version +sto: storage pool names (eg: vg fg) +stovg_type: vg storage pool type (eg: lvm) +stovg_size: vg storage pool size (eg: 1042) +stovg_used: vg storage pool used space (eg: 1) +stovg_free: vg storage pool free space (eg: 1041) +stovg_path: vg storage pool path (eg: /dev/vg/) +stovg_vol: vg storage pool volume list (eg: sex titi toto) +disk: disk index list (eg: 1 2 3 4) +disk1_path: disk 1 path (eg: /dev/vg/sex) +disk1_size: disk 1 size (eg: 1024) +disk1_pool: storage pool back reference (eg: vg) [vmonly] +disk1_voln: storage pool volume back reference (eg: ) [vmonly] diff --git a/bin/cc-cli b/bin/cc-cli index 048a36d70a831e6023cfd6f71279a68e191c8d23..3fa01164e1bac04a7e9f9ddc6e0b3d74504216e1 100755 --- a/bin/cc-cli +++ b/bin/cc-cli @@ -2,20 +2,21 @@ #coding=utf8 ''' -CloudControl CLI +CloudControl CLI Binary ''' import os, os.path import sys -import cccli -from cccli import printer,cli -from cccli.clierror import * import optparse import ConfigParser import pprint import re import warnings -import getpass + +from cccli import version, debug +from cccli.cli import Cli +from cccli.printer import Printer +from cccli.exception import * settings = { "port": "1984", @@ -23,6 +24,8 @@ settings = { "hsize": "100" } +printer = Printer(False) + try: # parse rc file if "HOME" in os.environ: @@ -48,7 +51,7 @@ try: # Parse line argument oparser = optparse.OptionParser(usage="usage: %prog [options] [commands]", - version=cccli.version) + version=version) oparser.add_option("-d", "--debug", action="store_true", dest="debug", @@ -95,7 +98,7 @@ try: # debug stuff if "debug" in settings: - cccli.debug = bool(settings["debug"]) + debug = bool(settings["debug"]) else: warnings.filterwarnings("ignore") printer.debug("Debugging on") @@ -113,14 +116,14 @@ try: raise BadArgument("Invalid %s number"%i) # check login if "login" not in settings: - settings["login"] = raw_input("Login: ") + settings["login"] = printer.ask("Login: ") # check password if "pass" not in settings: - settings["pass"] = getpass.getpass("Password: ") + settings["pass"] = printer.getpass("Password: ") # start cli - cli = cli.Cli(settings) + cli = Cli(settings) cli.start(" ".join(args)) except BadArgument, e: printer.error("Bad Argument: %s"%str(e)) @@ -128,7 +131,7 @@ except BadArgument, e: except KeyboardInterrupt: exit(1) except Exception, e: - if cccli.debug: + if debug: printer.fatal(str(e), exitcode=-1) raise printer.warn("This is a not expected error, please report it!") diff --git a/cccli/cli.py b/cccli/cli.py index c79f8cf01a4e8780a86e302a25aa5285904627bb..aedb884ca05232dc2a1fdb5c9a4f898ceef88984 100644 --- a/cccli/cli.py +++ b/cccli/cli.py @@ -2,24 +2,22 @@ #coding=utf8 ''' -CloudControl CLI class +CloudControl CLI main module ''' import os, os.path import sys -import socket -import ssl -import threading -import subprocess -import ConfigParser import re +import subprocess + +from cccli import version, debug +from cccli.command import Command, Alias +from cccli.printer import Printer +from cccli.exception import * -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 +from sjrpc.core.exceptions import * class Cli(object): def __init__(self, settings): @@ -27,23 +25,21 @@ class Cli(object): self.settings = settings self.prompt = "> " self.rpc = None + self.printer = None self.alias = Alias() - self.history = History(self) def start(self, line=""): '''Start a CLI''' - # not interactive is command line + # not interactive if command line if line: self.isinteractive = False - # start readline and load history - if self.isinteractive: - import readline - self.readline = readline - self.history.load(self.settings.get("history", "")) - self.history.maxsize(self.settings.get("hsize", None)) + # start printer and load history + self.printer = Printer(self.isinteractive) + self.printer.history.load(self.settings.get("history", "")) + self.printer.history.maxsize(self.settings.get("hsize", None)) # load alias self.alias.load(self.settings.get("alias", "")) - printer.debug("Alias: %s"%self.alias) + self.printer.debug("Alias: %s"%self.alias) # Connecting self._connect() # authentifications @@ -53,11 +49,11 @@ class Cli(object): self._parse_line(line) else: self._shell() - self.history.save(self.settings.get("history", "")) + self.printer.history.save(self.settings.get("history", "")) def _connect(self): - printer.debug("Connecting...") + self.printer.debug("Connecting...") rpcc = SimpleRpcClient.from_addr(self.settings["server"], self.settings["port"], enable_ssl=True, @@ -69,26 +65,26 @@ class Cli(object): self.rpc = ConnectionProxy(rpcc) def _auth(self): - printer.debug("Authenticating...") + self.printer.debug("Authenticating...") if self.rpc.authentify(self.settings["login"], self.settings["pass"]): - printer.debug("Authenticated.") + self.printer.debug("Authenticated.") else: - printer.fatal("Autentification failed!") + self.printer.fatal("Autentification failed!") def _shell(self): '''Shell parser''' while True: try: - line = raw_input(self.prompt) + line = self.printer.getline(self.prompt) self._parse_line(line) except EOFError: - printer.out("") + self.printer.out("") break except SystemExit: break except KeyboardInterrupt: - printer.out("") - printer.out("Tcho!") + self.printer.out("") + self.printer.out("Tcho!") def _parse_line(self, line): '''Parse a line (more than one command)''' @@ -115,102 +111,28 @@ class Cli(object): Command(argv, self).call() except BadArgument, e: if str(e): - printer.error("Bad argument: %s."%str(e)) + self.printer.error("Bad argument: %s."%str(e)) else: - printer.error("Bad argument.") + self.printer.error("Bad argument.") usage = Command.usage(argv[0]) if usage != "": - printer.out("usage: %s."%usage) + self.printer.out("usage: %s."%usage) except BadCommand, e: if str(e): - printer.error("command: %s."%str(e)) + self.printer.error("command: %s."%str(e)) else: - printer.error("No command: %s."%argv[0]) + self.printer.error("No command: %s."%argv[0]) except RpcError, e: if debug: raise - printer.error("sjRPC: %s"%str(e)) + self.printer.error("sjRPC: %s"%str(e)) except Exception, e: if debug: raise - printer.error("%s: %s."%(argv[0], str(e))) + self.printer.error("%s: %s."%(argv[0], str(e))) def _lex_argv(self, string): '''Lex command argument''' return string.split(" ") -class Alias(dict): - ''' Alias wrapper''' - def load(self, filename): - '''load alias from file''' - if os.access(filename, os.R_OK): - fparser = ConfigParser.SafeConfigParser() - fparser.read(filename) - if fparser.has_section("alias"): - self.clear() - self.update(fparser.items("alias")) - - def save(self, filename): - '''save alias on file''' - if os.access(filename, os.R_OK or os.W_OK): - fparser = ConfigParser.SafeConfigParser() - 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")) - -class History(object): - '''History class''' - def __init__(self, cli): - self.cli = cli - - def __nonzero__(self): - return not self.cli.readline is None - - def __getattribute__(self, name): - r = object.__getattribute__(self, "cli") - if name == "cli": - return r - if r.readline is None: - return lambda *a,**k: None - return object.__getattribute__(self, name) - - def __iter__(self): - for i in range(1, len(self)): - yield self.cli.readline.get_history_item(i) - - def __len__(self): - return self.cli.readline.get_current_history_length() - - def load(self, path): - '''Load history from a file''' - try: - self.cli.readline.read_history_file(path) - except IOError: - pass - - def save(self, path): - '''Save history into path''' - try: - self.cli.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.cli.readline.set_history_length(size) - return self.cli.readline.get_history_length() - - def removelast(self): - '''Remove last history line''' - self.cli.readline.remove_history_item(self.cli.readline.get_current_history_length()) - - def clear(self): - '''Clear history''' - self.cli.readline.clear_history() - - class CliHandler(RpcHandler): '''Handle RPC incoming request''' @@ -221,4 +143,4 @@ class CliHandler(RpcHandler): @pure def quit(self, rpc=None): - printer.fatal("Disconnected from server!") + self.printer.fatal("Disconnected from server!") diff --git a/cccli/command.py b/cccli/command.py index a9ed961ca55b6b045e97d8f5d6c6efb4c5d1c942..7cee18323a86aec50b8291e025e86fa660a29b7c 100644 --- a/cccli/command.py +++ b/cccli/command.py @@ -1,11 +1,21 @@ +#!/usr/bin/env python +#coding=utf8 + +''' +CloudControl CLI command module +''' + import os, os.path import sys import re -from sjrpc.client import SimpleRpcClient -import cccli import pprint -from cccli import printer -from cccli.clierror import * +import ConfigParser + +from cccli.exception import * +from cccli.printer import color + +from sjrpc.client import SimpleRpcClient +from sjrpc.core.exceptions import * class Command(object): @@ -29,6 +39,7 @@ class Command(object): self._cmd = getattr(self, "cmd_%s"%argv[0]) self._argv = argv self.cli = cli + self.printer = cli.printer @classmethod def usage(cls, cmdname): @@ -63,7 +74,7 @@ class Command(object): def cmd_version(self, argv): '''Print cli version''' - printer.out(cccli.version) + self.printer.out(cccli.version) cmd_version.usage = "version" cmd_version.desc = "Print cli version" @@ -73,9 +84,9 @@ class Command(object): raise BadArgument() usage = Command.usage(argv[1]) if usage != "": - printer.out("usage: %s"%usage) + self.printer.out("usage: %s"%usage) else: - printer.out("No usage.") + self.printer.out("No usage.") cmd_usage.usage = "usage [command]" cmd_usage.desc = "Print usage of a command" @@ -93,21 +104,21 @@ class Command(object): doclist.append(getattr(getattr(self, x), "__doc__")) # printing commands list width = max(map(len, cmdlist)) + 3 - printer.out("%sCommands:%s"%(printer.color["lwhite"], printer.color["reset"])) + self.printer.out("%sCommands:%s"%(color["lwhite"], color["reset"])) for c, d in zip(cmdlist, doclist): line = "%s"%c line = line.ljust(width,) line += "- %s"%d - printer.out(line) + self.printer.out(line) elif len(argv) == 2: fname = "cmd_%s"%argv[1] if hasattr(self, fname): if hasattr(getattr(self, fname), "__doc__"): - printer.out("Description: %s"%getattr(getattr(self, fname), "__doc__")) + self.printer.out("Description: %s"%getattr(getattr(self, fname), "__doc__")) if hasattr(getattr(self, fname), "usage"): - printer.out("Usage: %s"%getattr(getattr(self, fname), "usage")) + self.printer.out("Usage: %s"%getattr(getattr(self, fname), "usage")) if hasattr(getattr(self, fname), "details"): - Printer.out("Details: %s"%getattr(getattr(self, fname), "details")) + Self.Printer.out("Details: %s"%getattr(getattr(self, fname), "details")) else: raise BadArgument(argv[1]) else: @@ -119,11 +130,11 @@ class Command(object): '''Show or create alias''' if len(argv) == 1: for n, v in self.cli.alias.items(): - printer.out("%s=%s"%(n, v)) + self.printer.out("%s=%s"%(n, v)) elif len(argv) == 2: if argv[1] not in self.cli.alias: raise BadArgument(argv[1]) - printer.out("%s=%s"%(argv[1], self.cli.alias[argv[1]])) + self.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.settings.get("alias", "")) @@ -146,7 +157,7 @@ class Command(object): def cmd_rcmd(self, argv): '''Show remote commands''' for cmds in self.cli.rpc.list_commands(): - printer.out("%s"%cmds["name"]) + self.printer.out("%s"%cmds["name"]) cmd_rcmd.usage = "rcmd" cmd_rcmd.desc = "Print remote command list" @@ -155,7 +166,7 @@ class Command(object): if not self.cli.history: raise Exception("History is disabled") for l in self.cli.history: - printer.out(l) + self.printer.out(l) cmd_history.usage = "history" cmd_history.desc = "Show commands history" @@ -167,7 +178,7 @@ class Command(object): for item in items: pprint.pprint(item) #for key, val in item.items(): - # printer.out("%s: %s "%(key, val)) + # self.printer.out("%s: %s "%(key, val)) cmd_list.usage = "list [tql]" def _startstopsdestroypauseresume(self, argv): @@ -178,16 +189,16 @@ class Command(object): # print tql list result items = self.cli.rpc.list(tql) if len(items) == 0: - printer.out("No selected object") + self.printer.out("No selected object") return - printer.out("Your request give the following result:") + self.printer.out("Your request give the following result:") for item in items: - printer.out("%s"%item["id"]) - printer.out("Count: %s"%len(items)) - if raw_input("Are you sure to %s? (yes/NO) "%argv[0]) != "yes": + self.printer.out("%s"%item["id"]) + self.printer.out("Count: %s"%len(items)) + if self.printer.ask("Are you sure to %s? (yes/NO) "%argv[0]) != "yes": raise Exception("Aborted") if len(items) > 5: - if raw_input("You request is on more than 5 objets. Are you really sure to %s its? (Yes, I am) "%argv[0]) != "Yes, I am": + if self.printer.ask("You request is on more than 5 objets. Are you really sure to %s its? (Yes, I am) "%argv[0]) != "Yes, I am": raise Exception("Aborted") self.cli.rpc[argv[0]](tql) @@ -217,3 +228,26 @@ class Command(object): self._startstopsdestroypauseresume(argv) cmd_destroy.usage = "destroy [tql]" + +class Alias(dict): + ''' Alias wrapper''' + def load(self, filename): + '''load alias from file''' + if os.access(filename, os.R_OK): + fparser = ConfigParser.SafeConfigParser() + fparser.read(filename) + if fparser.has_section("alias"): + self.clear() + self.update(fparser.items("alias")) + + def save(self, filename): + '''save alias on file''' + if os.access(filename, os.R_OK or os.W_OK): + fparser = ConfigParser.SafeConfigParser() + 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")) + diff --git a/cccli/clierror.py b/cccli/exception.py similarity index 76% rename from cccli/clierror.py rename to cccli/exception.py index 6fd1000012fe441eeab1a47a3460807f9a4d3366..4c5599712ceeb35fc94af2642df059f608926a93 100644 --- a/cccli/clierror.py +++ b/cccli/exception.py @@ -2,7 +2,7 @@ #coding=utf8 ''' -CloudControl CLI Errors +CloudControl CLI Exceptions Module ''' ################################################################################ @@ -14,3 +14,6 @@ class BadCommand(cliError): class BadArgument(cliError): pass + +class cliWarning(Exception): + pass diff --git a/cccli/printer.py b/cccli/printer.py index 67cc3a294e9a0190395d4b3fc0692fb69b243c3d..c25135bf14334f10e1cc0cdc2ef234c877d6b73b 100644 --- a/cccli/printer.py +++ b/cccli/printer.py @@ -2,14 +2,15 @@ #coding=utf8 ''' -CloudControl CLI Printers +CloudControl CLI Printer module ''' import sys import os -import cccli +import getpass + +from cccli import debug -############################################################################### color = { # regular "red": "\033[0;31m", @@ -38,43 +39,112 @@ color = { "reset": "\033[m", } -############################################################################### -def out(message, fd=sys.stdout, nl=os.linesep): - '''Print a message in fd ended by nl''' - fd.write("%s%s"%(message, nl)) - fd.flush() - -############################################################################### -def err(message, fd=sys.stderr, nl=os.linesep): - out(message, fd, nl) - -############################################################################### -def fatal(message, exitcode=42, fd=sys.stderr, nl=os.linesep): - out("%sFatal%s: %s%s"%(color["lred"], - color["red"], - message, - color["reset"]) - , fd, nl) - if exitcode >= 0: - os._exit(exitcode) - -############################################################################### -def error(message, fd=sys.stderr, nl=os.linesep): - out("%sError%s: %s%s"%(color["lred"], - color["red"], - message, - color["reset"]) - ,fd,nl) - -############################################################################### -def warn(message, fd=sys.stderr, nl=os.linesep): - out("%sWarning%s: %s%s"%(color["lyellow"], - color["yellow"], - message, - color["reset"]) - ,fd,nl) - -############################################################################### -def debug(message, fd=sys.stderr, nl=os.linesep): - if cccli.debug: - out(message, fd, nl) + +class Printer(object): + '''Print relative class''' + def __init__(self, interactive=False): + self.readline = None + if interactive: + import readline + self.readline = readline + self.history = History(self.readline) + + def isinteractive(self): + '''Return if printer is in interactive mode''' + return self.readline is not None + + def out(self, message, fd=sys.stdout, nl=os.linesep, flush=True): + '''Print a message in fd ended by nl''' + fd.write("%s%s"%(message, nl)) + if flush: + fd.flush() + + def err(self, message, fd=sys.stderr, nl=os.linesep): + self.out(message, fd, nl) + + def fatal(self, message, exitcode=42, fd=sys.stderr, nl=os.linesep): + self.out("%sFatal%s: %s%s"%(color["lred"],color["red"],message, color["reset"]), + fd, + nl) + if exitcode >= 0: + os._exit(exitcode) + + def error(self, message, fd=sys.stderr, nl=os.linesep): + self.out("%sError%s: %s%s"%(color["lred"],color["red"],message,color["reset"]), + fd, + nl) + + def warn(self, message, fd=sys.stderr, nl=os.linesep): + self.out("%sWarning%s: %s%s"%(color["lyellow"],color["yellow"],message,color["reset"]), + fd, + nl) + + def debug(self, message, fd=sys.stderr, nl=os.linesep): + if debug: + self.out(message, fd, nl) + + def getline(self, msg, history=True): + s = raw_input(msg) + if not history: + self.history.removelast() + return s + + def getpass(self, msg): + '''Ask for a password. No echo. Not in history''' + return getpass.getpass(msg) + + def ask(self, msg): + '''Used to ask a question. Default answer not saved to history''' + return self.getline(msg, history=False) + + +class History(object): + '''History class''' + def __init__(self, readline): + 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() + + def removelast(self): + '''Remove last history line''' + self.readline.remove_history_item(self.cli.readline.get_current_history_length()) + + def clear(self): + '''Clear history''' + self.readline.clear_history()