#!/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
