+#!/usr/bin/python3
+
+# todo
+# - command name
+# - better parser
+# - readline: http://bip.weizmann.ac.il/course/python/PyMOTW/PyMOTW/docs/readline/index.html
+# - expression parser
+# - more commands (int, ip, ether, date, file, ...)
+# - how to specify help for commands?
+# - gerer les guillemets, les retours chariot, les espaces
+# - to_str() -> repr() ?
+# - ambiguous match as an exception?
+# - use logging module
+# - cmd -> subcmd
+# - should display command help string if no help for token: how to do it
+
+"""
+match object:
+
+show interface <ethif>|all [detailed]
+ ethif: EthIfCmd()
+
+show interface all
+ { "show": "show",
+ "interface": "interface"
+ "all": "all" }
+
+show interface eth0 detailed
+ { "show": "show",
+ "interface": "interface",
+ "ethif": "eth0",
+ "detailed": "detailed" }
+
+
+
+filter [in <in-iface>]/[out <out-iface>]/[proto ipv4]/[proto ipv6] drop|pass
+ in-iface: EthIfCmd()
+ out-iface: EthIfCmd()
+
+
+Les tokens nommés sont remplis dans un dictionnaire
+Impossible d'avoir 2 fois le même nom, a verifier dans le createur de commande
+ -> faux. exemple au dessus.
+
+Sinon comment gerer le ambiguous match?
+
+
+
+
+complete object:
+
+Pour chaque completion possible (meme vide ?), il faut retourner:
+ - la chaine du token complet
+ - l'aide du token (ou l'objet)
+ - la commande ? (ou alors ça c'est géré dans la partie rdline)
+
+
+
+
+
+legacy = blabla VERSION{1,2}
+new = blabla wpa1/wpa2
+
+legacy = blublu IFACE{1,*}
+new = blublu <interfaces-list>
+
+
+pour la définition d'un token, on pourrait avoir une fonction
+display() qui permettrait d'afficher (<iface1> [<iface2> ...])
+a la place de <interfaces-list>
+(ou pas)
+
+
+"""
+
+import shlex, os
+import expparser
+import re
+import socket
+
+_PYCOL_PKG_DIR = os.path.dirname(__file__)
+DEBUG = False
+
+def debug(*args, **kvargs):
+ if DEBUG:
+ print(*args, **kvargs)
+
+class PycolMatch(dict):
+ def __init__(self):
+ pass
+ def __repr__(self):
+ return "<Match %s>"%(dict.__repr__(self))
+ def merge(self, match_res):
+ for k in match_res:
+ self[k] = match_res[k]
+
+# XXX describe that it's a list of tuples
+class PycolCompleteList(list):
+ def __init__(self, l = []):
+ if not isinstance(l, list):
+ l = [l]
+ list.__init__(self, l)
+ def __repr__(self):
+ return "<CompleteList %s>"%(list.__repr__(self))
+ def merge(self, comp_res):
+ for e in comp_res:
+ if e in self:
+ continue
+ self.append(e)
+ # XXX own(): keep the list but set token ref
+
+class PycolComplete(object):
+ def __init__(self, token, cmd, terminal = True):
+ self.token = token
+ self.cmd = cmd
+ self.terminal = terminal
+
+class PycolContext(list):
+ def __init__(self, l = []):
+ list.__init__(self, l)
+ def complete(self, tokens):
+ res = PycolCompleteList()
+ for cmd in self:
+ res2 = cmd.complete(tokens)
+ res.merge(res2)
+ return res
+
+class CliCmd:
+ def __init__(self, key = None, cb = None, help_str = None):
+ self.key = key
+ assert(key == None or type(key) is str), "key is not a string: %s"%(key)
+ self.cb = cb
+ self.help_str = help_str
+ assert(help_str == None or type(help_str) is str), \
+ "help_str is not a string: %s"%(help_str)
+ self.default_help_str = "no help"
+
+ def match(self, tokens):
+ """return a match object
+ len(tokens) always >= 1
+ the last token is the one to complete (may be empty)
+ the first tokens are the ones to match
+ """
+ return PycolMatch()
+
+ def complete(self, tokens):
+ # XXX return None when no completion
+ # does not match
+ m = self.match(tokens)
+ if m != None or (len(tokens) == 1 and tokens[0] == ""):
+ return PycolCompleteList(PycolComplete(tokens[0], self))
+ return PycolCompleteList()
+
+ def to_expr(self):
+ if self.key != None:
+ return self.key
+ else:
+ return str(self)
+
+ def test_match(self, in_buf):
+ tokens = shlex.split(in_buf, comments = True)
+ match_result = self.match(tokens)
+ debug("test match: %s %s"%(tokens, match_result))
+ if match_result == None:
+ return False
+ else:
+ return True
+
+ # quick test for assert
+ def test_complete(self, in_buf):
+ complete_result = PycolCompleteList()
+ tokens = shlex.split(in_buf, comments = False)
+ # whitespace after the first token, it means we want to complete
+ # the second token
+ debug("test_complete: %s", in_buf)
+ if len(tokens) == 0 or not in_buf.endswith(tokens[-1]):
+ tokens.append("")
+ complete_result = self.complete(tokens)
+ debug("test complete: %s %s"%(tokens, complete_result))
+ return list(map(lambda x:x.token, complete_result))
+
+ def __repr__(self):
+ return "<CliCmd>"
+
+ # XXX say that it can return a multiple lines string
+ def get_help(self):
+ if self.help_str == None:
+ return self.default_help_str
+ else:
+ return self.help_str
+
+class CmdBuilder(CliCmd):
+ def __init__(self, expr, token_desc, key = None, cb = None, help_str = None):
+ super().__init__(key = key, cb = cb, help_str = help_str)
+ assert(isinstance(expr,str)), "expr must be a string"
+ assert(type(token_desc) is dict), "token_desc must be a dict"
+ self.token_desc = token_desc
+ parser = expparser.PycolExprParser()
+ tokens = parser.tokenize(expr)
+ expr = parser.parse(tokens)
+ debug(expr.to_str())
+ self.token_desc_list = []
+ self.cmd = self.__expr2clicmd(expr, token_desc)
+
+ def match(self, tokens):
+ m = self.cmd.match(tokens)
+ if m != None and self.key != None:
+ m[self.key] = " ".join(tokens)
+ return m
+
+ def complete(self, tokens):
+ return self.cmd.complete(tokens)
+
+ def to_expr(self):
+ return self.cmd.to_expr()
+
+ def __repr__(self):
+ return "<CmdBuilder(%s)>"%(self.cmd)
+
+ def __expr2clicmd(self, expr, token_desc):
+ l = []
+ for c in expr.children:
+ l.append(self.__expr2clicmd(c, token_desc))
+ if expr.is_var():
+ varname = expr.op
+ assert(varname in token_desc)
+ cmd = token_desc[varname]
+ cmd.key = varname
+ # keep the order of the variables for help displaying
+ self.token_desc_list.append(varname)
+ elif expr.op == " ":
+ cmd = SeqCliCmd(l)
+ elif expr.op == "|":
+ cmd = OrCliCmd(l)
+ elif expr.op == "[":
+ cmd = OptionalCliCmd(l[0])
+ elif expr.op == "(":
+ cmd = BypassCliCmd(l[0])
+ #elif expr.op == " ": #XXX
+ return cmd
+
+ def get_help(self):
+ if self.help_str == None:
+ help_str = self.default_help_str
+ else:
+ help_str = self.help_str
+ for t in self.token_desc_list:
+ help_str += "\n %s: %s"%(t, self.token_desc[t].get_help())
+ return help_str
+
+ def to_expr(self):
+ return self.cmd.to_expr()
+
+class TextCliCmd(CliCmd):
+ def __init__(self, text, key = None, cb = None, help_str = None):
+ super().__init__(key = key, cb = cb, help_str = help_str)
+ assert(not " " in text) # XXX find better?
+ self.text = text
+ debug("TextCliCmd(%s)"%text)
+
+ def match(self, tokens):
+ if tokens == [self.text]:
+ res = PycolMatch()
+ if self.key:
+ res[self.key] = self.text
+ return res
+ return None
+
+ def complete(self, tokens):
+ # more than one token as input, the string does not match
+ if len(tokens) > 1:
+ return PycolCompleteList()
+ # the beginning of the string does not match
+ if len(tokens) == 1 and not self.text.startswith(tokens[0]):
+ return PycolCompleteList()
+ # complete
+ return PycolCompleteList(PycolComplete(self.text, self))
+
+ def to_expr(self):
+ return self.text
+
+ def __repr__(self):
+ return "<TextCliCmd(%s)>"%(self.text)
+
+
+text_cmd = TextCliCmd("toto")
+
+assert(text_cmd.test_match("") == False)
+assert(text_cmd.test_match("tot") == False)
+assert(text_cmd.test_match("toto toto") == False)
+assert(text_cmd.test_match(" toto") == True)
+assert(text_cmd.test_match("toto ") == True)
+assert(text_cmd.test_match(" toto \n\n") == True)
+assert(text_cmd.test_match("toto # coin") == True)
+assert(text_cmd.test_match("toto") == True)
+
+assert(text_cmd.test_complete("") == ["toto"])
+assert(text_cmd.test_complete("to") == ["toto"])
+assert(text_cmd.test_complete("tot") == ["toto"])
+assert(text_cmd.test_complete(" tot") == ["toto"])
+assert(text_cmd.test_complete("toto") == ["toto"])
+assert(text_cmd.test_complete("d") == [])
+assert(text_cmd.test_complete("toto ") == [])
+assert(text_cmd.test_complete("toto#") == [])
+
+class SeqCliCmd(CliCmd):
+ def __init__(self, cmdlist, key = None):
+ self.cmdlist = cmdlist
+ self.key = key
+ debug("SeqCliCmd(%s)"%cmdlist)
+
+ def match(self, tokens):
+ # only one command, try to match all tokens
+ if len(self.cmdlist) == 1:
+ m = self.cmdlist[0].match(tokens)
+ if m != None and self.key != None:
+ m[self.key] = " ".join(tokens)
+ return m
+ # several commands, try to match the first command with 1 to N tokens,
+ # and do a recursive call for the following
+ n_match = 0
+ ret = PycolMatch()
+ for i in range(len(tokens)+1):
+ m = self.cmdlist[0].match(tokens[:i])
+ if m == None:
+ continue
+ m2 = SeqCliCmd(self.cmdlist[1:]).match(tokens[i:])
+ if m2 == None:
+ continue
+ if n_match >= 1:
+ EXC()
+ ret.merge(m)
+ ret.merge(m2)
+ if self.key != None:
+ ret[self.key] = " ".join(tokens[:i])
+ n_match += 1
+ if n_match > 0:
+ return ret
+ return None
+
+ def complete(self, tokens):
+ debug("----------complete seq <%s>"%tokens)
+
+ match_tokens = tokens[:-1]
+ complete_token = tokens[-1]
+
+ # there is no match_token (only whitespaces), try to complete the first
+ # command
+ if len(match_tokens) == 0:
+ return self.cmdlist[0].complete(tokens)
+
+ debug("match_tokens=<%s> complete_token=<%s>"%(match_tokens, complete_token))
+ res = PycolCompleteList()
+
+ # try to match match_tokens with 1 to N commands
+ for i in range(1, len(self.cmdlist)):
+ # if it does not match, continue
+ if SeqCliCmd(self.cmdlist[0:i]).match(match_tokens) == None:
+ continue
+ # XXX res.own()
+ # if it matches, try to complete the last token
+ res2 = self.cmdlist[i].complete([complete_token])
+ res.merge(res2)
+
+ return res
+
+ def to_expr(self):
+ return " ".join([c.to_expr() for c in self.cmdlist])
+
+ def __repr__(self):
+ return "<SeqCliCmd(%s)>"%(str(self.cmdlist))
+
+seq_cmd = SeqCliCmd([TextCliCmd("toto"), TextCliCmd("titi"), TextCliCmd("tutu")])
+assert(seq_cmd.test_match("") == False)
+assert(seq_cmd.test_match("toto") == False)
+assert(seq_cmd.test_match("titi") == False)
+assert(seq_cmd.test_match("toto d") == False)
+assert(seq_cmd.test_match("toto # titi tutu") == False)
+assert(seq_cmd.test_match("toto titi tutu") == True)
+assert(seq_cmd.test_match(" toto titi tutu") == True)
+assert(seq_cmd.test_match("toto titi tutu #") == True)
+assert(seq_cmd.test_match("toto titi tutu") == True)
+
+assert(seq_cmd.test_complete("") == ["toto"])
+assert(seq_cmd.test_complete("toto ") == ["titi"])
+assert(seq_cmd.test_complete("toto t") == ["titi"])
+assert(seq_cmd.test_complete("toto") == ["toto"])
+assert(seq_cmd.test_complete("titi") == [])
+assert(seq_cmd.test_complete("toto d") == [])
+assert(seq_cmd.test_complete("toto # titi tutu") == [])
+assert(seq_cmd.test_complete("toto titi tut") == ["tutu"])
+assert(seq_cmd.test_complete(" toto titi tut") == ["tutu"])
+assert(seq_cmd.test_complete("toto titi tutu #") == [])
+assert(seq_cmd.test_complete("toto titi tutu") == ["tutu"])
+
+
+class OrCliCmd(CliCmd):
+ def __init__(self, cmdlist, key = None):
+ self.cmdlist = cmdlist
+ self.key = key
+ debug("OrCliCmd(%s)"%cmdlist)
+
+ def match(self, tokens):
+ # try to match all commands
+ for cmd in self.cmdlist:
+ ret = cmd.match(tokens)
+ if ret == None:
+ continue
+ if self.key != None:
+ ret[self.key] = " ".join(tokens)
+ return ret
+ return None
+
+ def complete(self, tokens):
+ debug("----------complete or <%s>"%tokens)
+
+ res = PycolCompleteList()
+ for cmd in self.cmdlist:
+ res2 = cmd.complete(tokens)
+ res.merge(res2)
+
+ return res
+
+ def to_expr(self):
+ return "|".join([c.to_expr() for c in self.cmdlist])
+
+ def __repr__(self):
+ return "<OrCliCmd(%s)>"%(str(self.cmdlist))
+
+or_cmd = OrCliCmd([TextCliCmd("toto"), TextCliCmd("titi"), TextCliCmd("tutu")])
+assert(or_cmd.test_match("") == False)
+assert(or_cmd.test_match("toto") == True)
+assert(or_cmd.test_match("titi") == True)
+assert(or_cmd.test_match("tutu") == True)
+assert(or_cmd.test_match(" toto # titi tutu") == True)
+assert(or_cmd.test_match("toto titi tutu") == False)
+assert(or_cmd.test_match(" toto t") == False)
+assert(or_cmd.test_match("toto d#") == False)
+
+assert(or_cmd.test_complete("") == ["toto", "titi", "tutu"])
+assert(or_cmd.test_complete("t") == ["toto", "titi", "tutu"])
+assert(or_cmd.test_complete("to") == ["toto"])
+assert(or_cmd.test_complete(" tot") == ["toto"])
+assert(or_cmd.test_complete(" titi") == ["titi"])
+assert(or_cmd.test_complete(" titi to") == [])
+assert(or_cmd.test_complete("titid") == [])
+assert(or_cmd.test_complete(" ti#") == [])
+
+
+
+class OptionalCliCmd(CliCmd):
+ def __init__(self, cmd, key = None):
+ self.cmd = cmd
+ self.key = key
+
+ def match(self, tokens):
+ # match an empty buffer
+ if len(tokens) == 0:
+ ret = PycolMatch()
+ if self.key != None:
+ ret[self.key] = ""
+ return ret
+ # else, try to match sub command
+ ret = self.cmd.match(tokens)
+ if ret != None and self.key != None:
+ ret[self.key] = " ".join(tokens)
+ return ret
+
+ def complete(self, tokens):
+ debug("----------complete optional <%s>"%tokens)
+ return self.cmd.complete(tokens)
+
+ def to_expr(self):
+ return "[" + self.cmd.to_expr() + "]"
+
+ def __repr__(self):
+ return "<OptionalCliCmd(%s)>"%(self.cmd)
+
+opt_cmd = OptionalCliCmd(TextCliCmd("toto"))
+assert(opt_cmd.test_match("") == True)
+assert(opt_cmd.test_match("toto") == True)
+assert(opt_cmd.test_match(" toto ") == True)
+assert(opt_cmd.test_match(" toto # tutu") == True)
+assert(opt_cmd.test_match("titi") == False)
+assert(opt_cmd.test_match("toto titi") == False)
+assert(opt_cmd.test_match(" toto t") == False)
+assert(opt_cmd.test_match("toto d#") == False)
+
+assert(opt_cmd.test_complete("") == ["toto"])
+assert(opt_cmd.test_complete("t") == ["toto"])
+assert(opt_cmd.test_complete("to") == ["toto"])
+assert(opt_cmd.test_complete(" tot") == ["toto"])
+assert(opt_cmd.test_complete(" ") == ["toto"])
+assert(opt_cmd.test_complete(" titi to") == [])
+assert(opt_cmd.test_complete("titid") == [])
+assert(opt_cmd.test_complete(" ti#") == [])
+
+class BypassCliCmd(CliCmd):
+ def __init__(self, cmd, key = None):
+ self.cmd = cmd
+ self.key = key
+
+ def match(self, tokens):
+ m = self.cmd.match(tokens)
+ if m != None and self.key != None:
+ m[self.key] = " ".join(tokens)
+ return m
+
+ def complete(self, tokens):
+ return self.cmd.complete(tokens)
+
+ def to_expr(self):
+ return "(" + self.cmd.to_expr() + ")"
+
+ def __repr__(self):
+ return "<BypassCliCmd(%s)>"%(self.cmd)
+
+
+class AnyTextCliCmd(CliCmd):
+ def __init__(self, key = None, help_str = None):
+ debug("AnyTextCliCmd()")
+ self.key = key
+ # XXX factorize all help and key in mother class?
+ if help_str != None:
+ self.help_str = help_str
+ else:
+ self.help_str = "Anytext token"
+
+ def match(self, tokens):
+ debug("----------match anytext <%s>"%tokens)
+ if len(tokens) != 1:
+ return None
+ res = PycolMatch()
+ if self.key:
+ res[self.key] = tokens[0]
+ return res
+
+ def complete(self, tokens):
+ debug("----------complete anytext <%s>"%tokens)
+ # does not match
+ if len(tokens) != 1:
+ return PycolCompleteList()
+ # match, but cannot complete
+ return PycolCompleteList(PycolComplete(tokens[0], self))
+
+ def to_expr(self):
+ return "<anytext>" # XXX
+
+ def __repr__(self):
+ return "<AnyTextCliCmd()>"
+
+ def get_help(self):
+ return self.help_str
+
+
+anytext_cmd = AnyTextCliCmd()
+
+assert(anytext_cmd.test_match("") == False)
+assert(anytext_cmd.test_match("toto toto") == False)
+assert(anytext_cmd.test_match(" toto") == True)
+assert(anytext_cmd.test_match("toto ") == True)
+assert(anytext_cmd.test_match(" toto \n\n") == True)
+assert(anytext_cmd.test_match("toto # coin") == True)
+assert(anytext_cmd.test_match("toto") == True)
+
+assert(anytext_cmd.test_complete("") == [""])
+assert(anytext_cmd.test_complete("to") == ["to"])
+assert(anytext_cmd.test_complete("tot") == ["tot"])
+assert(anytext_cmd.test_complete(" tot") == ["tot"])
+assert(anytext_cmd.test_complete("toto") == ["toto"])
+#assert(anytext_cmd.test_complete("toto#") == []) #XXX later
+
+
+
+
+class IntCliCmd(CliCmd):
+ def __init__(self, val_min = None, val_max = None, key = None,
+ help_str = None):
+ debug("IntCliCmd()")
+ self.key = key
+ # XXX factorize all help and key in mother class?
+ if help_str != None:
+ self.help_str = help_str
+ else:
+ self.help_str = "Int token"
+ # XXX val_min val_max
+
+ def match(self, tokens):
+ debug("----------match int <%s>"%tokens)
+ if len(tokens) != 1:
+ return None
+ try:
+ val = int(tokens[0])
+ except:
+ return None
+ res = PycolMatch()
+ if self.key:
+ res[self.key] = val
+ return res
+
+ def to_expr(self):
+ return "<int>" # XXX
+
+ def __repr__(self):
+ return "<IntCliCmd()>"
+
+ def get_help(self):
+ return self.help_str
+
+
+int_cmd = IntCliCmd()
+
+assert(int_cmd.test_match("") == False)
+assert(int_cmd.test_match("toto") == False)
+assert(int_cmd.test_match(" 13") == True)
+assert(int_cmd.test_match("-12342 ") == True)
+assert(int_cmd.test_match(" 33 \n\n") == True)
+assert(int_cmd.test_match("1 # coin") == True)
+assert(int_cmd.test_match("0") == True)
+
+assert(int_cmd.test_complete("") == [""])
+assert(int_cmd.test_complete("1") == ["1"])
+assert(int_cmd.test_complete("12") == ["12"])
+assert(int_cmd.test_complete(" ee") == [])
+
+
+class RegexpCliCmd(CliCmd):
+ def __init__(self, regexp, cmd = None, key = None,
+ store_re_match = False, help_str = None):
+ debug("RegexpCliCmd()")
+ self.regexp = regexp
+ self.key = key
+ self.cmd = cmd
+ self.store_re_match = store_re_match
+ # XXX factorize all help and key in mother class?
+ if help_str != None:
+ self.help_str = help_str
+ else:
+ self.help_str = "Regexp token"
+
+ def match(self, tokens):
+ debug("----------match regexp <%s>"%tokens)
+ if len(tokens) != 1:
+ return None
+ m = re.fullmatch(self.regexp, tokens[0])
+ if m == None:
+ return None
+ if self.cmd != None:
+ res = self.cmd.match(tokens)
+ if res == None:
+ return None
+ else:
+ res = PycolMatch()
+ if self.key:
+ if self.store_re_match:
+ res[self.key] = m
+ else:
+ res[self.key] = tokens[0]
+ return res
+
+ def complete(self, tokens):
+ if self.cmd == None:
+ return CliCmd.complete(self, tokens)
+ res = PycolCompleteList()
+ completions = self.cmd.complete(tokens)
+ for c in completions:
+ if self.match([c[0]]):
+ res.append(PycolComplete(c[0], self))
+ return res
+
+ def __repr__(self):
+ return "<RegexpCliCmd()>"
+
+ def get_help(self):
+ return self.help_str
+
+
+regexp_cmd = RegexpCliCmd("x[123]")
+
+assert(regexp_cmd.test_match("") == False)
+assert(regexp_cmd.test_match("toto") == False)
+assert(regexp_cmd.test_match(" x1") == True)
+assert(regexp_cmd.test_match("x3 ") == True)
+assert(regexp_cmd.test_match(" x2\n\n") == True)
+assert(regexp_cmd.test_match("x1 # coin") == True)
+
+assert(regexp_cmd.test_complete("") == [""]) # XXX handle this case at upper level?
+assert(regexp_cmd.test_complete("x1") == ["x1"])
+assert(regexp_cmd.test_complete(" x2") == ["x2"])
+assert(regexp_cmd.test_complete(" ee") == [])
+
+
+
+class IPv4CliCmd(CliCmd):
+ def __init__(self, cmd = None, key = None, help_str = None):
+ debug("IPv4CliCmd()")
+ self.key = key
+ self.cmd = cmd
+ # XXX factorize all help and key in mother class?
+ if help_str != None:
+ self.help_str = help_str
+ else:
+ self.help_str = "IPv4 token"
+
+ def match(self, tokens):
+ debug("----------match IPv4 <%s>"%tokens)
+ if len(tokens) != 1:
+ return None
+ try:
+ val = socket.inet_aton(tokens[0])
+ except:
+ return None
+ if len(tokens[0].split(".")) != 4:
+ return None
+ res = PycolMatch()
+ if self.key:
+ res[self.key] = tokens[0]
+ return res
+
+ def complete(self, tokens):
+ if self.cmd == None:
+ return CliCmd.complete(self, tokens)
+ res = PycolCompleteList()
+ completions = self.cmd.complete(tokens)
+ for c in completions:
+ if self.match([c[0]]):
+ res.append(PycolComplete(c[0], self))
+ return res
+
+ def to_expr(self):
+ return "<IPv4>" # XXX
+
+ def __repr__(self):
+ return "<IPv4CliCmd()>"
+
+ def get_help(self):
+ return self.help_str
+
+
+ipv4_cmd = IPv4CliCmd()
+
+assert(ipv4_cmd.test_match("") == False)
+assert(ipv4_cmd.test_match("toto") == False)
+assert(ipv4_cmd.test_match(" 13.3.1.3") == True)
+assert(ipv4_cmd.test_match("255.255.255.255 ") == True)
+assert(ipv4_cmd.test_match(" 0.0.0.0 \n\n") == True)
+assert(ipv4_cmd.test_match("1.2.3.4 # coin") == True)
+assert(ipv4_cmd.test_match("300.2.2.2") == False)
+
+assert(ipv4_cmd.test_complete("") == [""])
+assert(ipv4_cmd.test_complete("1.2.3.4") == ["1.2.3.4"])
+assert(ipv4_cmd.test_complete(" ee") == [])
+
+
+
+
+class FileCliCmd(CliCmd):
+ def __init__(self, key = None, help_str = None):
+ debug("FileCliCmd()")
+ self.key = key
+ # XXX factorize all help and key in mother class?
+ if help_str != None:
+ self.help_str = help_str
+ else:
+ self.help_str = "File token"
+
+ def match(self, tokens):
+ debug("----------match File <%s>"%tokens)
+ if len(tokens) != 1:
+ return None
+ if not os.path.exists(tokens[0]):
+ return None
+ res = PycolMatch()
+ if self.key:
+ res[self.key] = tokens[0]
+ return res
+
+ def complete(self, tokens):
+ debug("----------complete File <%s>"%tokens)
+ res = PycolCompleteList()
+ if len(tokens) != 1:
+ return res
+ dirname = os.path.dirname(tokens[0])
+ basename = os.path.basename(tokens[0])
+ if dirname != "" and not os.path.exists(dirname):
+ return res
+ debug("dirname=%s basename=%s"%(dirname, basename))
+ if dirname == "":
+ ls = os.listdir()
+ else:
+ ls = os.listdir(dirname)
+ debug("ls=%s"%(str(ls)))
+ for f in ls:
+ path = os.path.join(dirname, f)
+ debug(path)
+ if path.startswith(tokens[0]):
+ if os.path.isdir(path):
+ path += "/"
+ res.append(PycolComplete(path, self, terminal = False))
+ else:
+ res.append(PycolComplete(path, self, terminal = True))
+
+ debug(res)
+ return res
+
+ def to_expr(self):
+ return "<File>" # XXX
+
+ def __repr__(self):
+ return "<FileCliCmd()>"
+
+ def get_help(self):
+ return self.help_str
+
+
+file_cmd = FileCliCmd()
+
+assert(file_cmd.test_match("") == False)
+assert(file_cmd.test_match("DEDEDzx") == False)
+assert(file_cmd.test_match("/tmp") == True)
+assert(file_cmd.test_match("/etc/passwd ") == True)
+
+assert(file_cmd.test_complete("/tm") == ["/tmp/"])
+assert(file_cmd.test_complete(" eededezezzzc") == [])
+
+
+
+
+class ChoiceCliCmd(CliCmd):
+ def __init__(self, choice, key = None, cb = None, help_str = None):
+ super().__init__(key = key, cb = cb, help_str = help_str)
+ if isinstance(choice, str):
+ self.get_list_func = lambda:[choice]
+ elif isinstance(choice, list):
+ self.get_list_func = lambda:choice
+ else: # XXX isinstance(func)
+ self.get_list_func = choice
+ debug("ChoiceCliCmd(%s)"%self.get_list_func)
+
+ def match(self, tokens):
+ # more than one token as input, does not match
+ if len(tokens) > 1:
+ return None
+ l = self.get_list_func()
+ for e in l:
+ if tokens == [e]:
+ res = PycolMatch()
+ if self.key:
+ res[self.key] = e
+ return res
+ return None
+
+ def complete(self, tokens):
+ # more than one token as input, does not match
+ if len(tokens) > 1:
+ return PycolCompleteList()
+ l = self.get_list_func()
+ complete = PycolCompleteList()
+ for e in l:
+ # the beginning of the string does not match
+ if len(tokens) == 1 and not e.startswith(tokens[0]):
+ continue
+ complete.append(PycolComplete(e, self))
+ return complete
+
+ def __repr__(self):
+ return "<ChoiceCliCmd(%s)>"%(self.get_list_func)
+
+
+choice_cmd = ChoiceCliCmd(["toto", "titi"])
+
+assert(choice_cmd.test_match("") == False)
+assert(choice_cmd.test_match("tot") == False)
+assert(choice_cmd.test_match("toto toto") == False)
+assert(choice_cmd.test_match(" toto") == True)
+assert(choice_cmd.test_match("titi ") == True)
+assert(choice_cmd.test_match(" toto \n\n") == True)
+assert(choice_cmd.test_match("toto # coin") == True)
+assert(choice_cmd.test_match("toto") == True)
+
+assert(choice_cmd.test_complete("") == ["toto", "titi"])
+assert(choice_cmd.test_complete("to") == ["toto"])
+assert(choice_cmd.test_complete("tot") == ["toto"])
+assert(choice_cmd.test_complete(" tot") == ["toto"])
+assert(choice_cmd.test_complete("toto") == ["toto"])
+assert(choice_cmd.test_complete("d") == [])
+assert(choice_cmd.test_complete("toto ") == [])
+assert(choice_cmd.test_complete("toto#") == [])
+
+
+if __name__ == '__main__':
+ # XXX add tests
+ cmd = CmdBuilder(
+ "toto a|b [coin] [bar]",
+ token_desc = {
+ "toto": TextCliCmd("toto", help_str = "help for toto"),
+ "a": TextCliCmd("a", help_str = "help for a"),
+ "b": TextCliCmd("b", help_str = "help for b"),
+ "coin": TextCliCmd("coin", help_str = "help for coin"),
+ "bar": TextCliCmd("bar", help_str = "help for bar"),
+ }
+ )
+ print(cmd.to_expr())
+ assert(cmd.test_match("toto a") == True)
+ assert(cmd.test_match("toto b coin") == True)
+ assert(cmd.test_match("toto") == False)
+
+
+
+# float
+
+# file
+
+# mac
+
+# ipv6
+
+# date