initial revision of pycol
[pycol.git] / clicmd.py
1 #!/usr/bin/python3
2
3 # todo
4 # - command name
5 # - better parser
6 # - readline: http://bip.weizmann.ac.il/course/python/PyMOTW/PyMOTW/docs/readline/index.html
7 # - expression parser
8 # - more commands (int, ip, ether, date, file, ...)
9 # - how to specify help for commands?
10 # - gerer les guillemets, les retours chariot, les espaces
11 # - to_str() -> repr() ?
12 # - ambiguous match as an exception?
13 # - use logging module
14 # - cmd -> subcmd
15 # - should display command help string if no help for token: how to do it
16
17 """
18 match object:
19
20 show interface <ethif>|all [detailed]
21   ethif: EthIfCmd()
22
23 show interface all
24  { "show": "show",
25    "interface": "interface"
26    "all": "all" }
27
28 show interface eth0 detailed
29  { "show": "show",
30    "interface": "interface",
31    "ethif": "eth0",
32    "detailed": "detailed" }
33
34
35
36 filter [in <in-iface>]/[out <out-iface>]/[proto ipv4]/[proto ipv6] drop|pass
37   in-iface: EthIfCmd()
38   out-iface: EthIfCmd()
39
40
41 Les tokens nommés sont remplis dans un dictionnaire
42 Impossible d'avoir 2 fois le même nom, a verifier dans le createur de commande
43  -> faux. exemple au dessus.
44
45 Sinon comment gerer le ambiguous match?
46
47
48
49
50 complete object:
51
52 Pour chaque completion possible (meme vide ?), il faut retourner:
53   - la chaine du token complet
54   - l'aide du token (ou l'objet)
55   - la commande ? (ou alors ça c'est géré dans la partie rdline)
56
57
58
59
60
61 legacy = blabla VERSION{1,2}
62 new = blabla wpa1/wpa2
63
64 legacy = blublu IFACE{1,*}
65 new = blublu <interfaces-list>
66
67
68 pour la définition d'un token, on pourrait avoir une fonction
69 display() qui permettrait d'afficher (<iface1> [<iface2> ...])
70 a la place de <interfaces-list>
71 (ou pas)
72
73
74 """
75
76 import shlex, os
77 import expparser
78 import re
79 import socket
80
81 _PYCOL_PKG_DIR = os.path.dirname(__file__)
82 DEBUG = False
83
84 def debug(*args, **kvargs):
85     if DEBUG:
86         print(*args, **kvargs)
87
88 class PycolMatch(dict):
89     def __init__(self):
90         pass
91     def __repr__(self):
92         return "<Match %s>"%(dict.__repr__(self))
93     def merge(self, match_res):
94         for k in match_res:
95             self[k] = match_res[k]
96
97 # XXX describe that it's a list of tuples
98 class PycolCompleteList(list):
99     def __init__(self, l = []):
100         if not isinstance(l, list):
101             l = [l]
102         list.__init__(self, l)
103     def __repr__(self):
104         return "<CompleteList %s>"%(list.__repr__(self))
105     def merge(self, comp_res):
106         for e in comp_res:
107             if e in self:
108                 continue
109             self.append(e)
110     # XXX own(): keep the list but set token ref
111
112 class PycolComplete(object):
113     def __init__(self, token, cmd, terminal = True):
114         self.token = token
115         self.cmd = cmd
116         self.terminal = terminal
117
118 class PycolContext(list):
119     def __init__(self, l = []):
120         list.__init__(self, l)
121     def complete(self, tokens):
122         res = PycolCompleteList()
123         for cmd in self:
124             res2 = cmd.complete(tokens)
125             res.merge(res2)
126         return res
127
128 class CliCmd:
129     def __init__(self, key = None, cb = None, help_str = None):
130         self.key = key
131         assert(key == None or type(key) is str), "key is not a string: %s"%(key)
132         self.cb = cb
133         self.help_str = help_str
134         assert(help_str == None or type(help_str) is str), \
135             "help_str is not a string: %s"%(help_str)
136         self.default_help_str = "no help"
137
138     def match(self, tokens):
139         """return a match object
140         len(tokens) always >= 1
141         the last token is the one to complete (may be empty)
142         the first tokens are the ones to match
143         """
144         return PycolMatch()
145
146     def complete(self, tokens):
147         # XXX return None when no completion
148         # does not match
149         m = self.match(tokens)
150         if m != None or (len(tokens) == 1 and tokens[0] == ""):
151             return PycolCompleteList(PycolComplete(tokens[0], self))
152         return PycolCompleteList()
153
154     def to_expr(self):
155         if self.key != None:
156             return self.key
157         else:
158             return str(self)
159
160     def test_match(self, in_buf):
161         tokens = shlex.split(in_buf, comments = True)
162         match_result = self.match(tokens)
163         debug("test match: %s %s"%(tokens, match_result))
164         if match_result == None:
165             return False
166         else:
167             return True
168
169     # quick test for assert
170     def test_complete(self, in_buf):
171         complete_result = PycolCompleteList()
172         tokens = shlex.split(in_buf, comments = False)
173         # whitespace after the first token, it means we want to complete
174         # the second token
175         debug("test_complete: %s", in_buf)
176         if len(tokens) == 0 or not in_buf.endswith(tokens[-1]):
177             tokens.append("")
178         complete_result = self.complete(tokens)
179         debug("test complete: %s %s"%(tokens, complete_result))
180         return list(map(lambda x:x.token, complete_result))
181
182     def __repr__(self):
183         return "<CliCmd>"
184
185     # XXX say that it can return a multiple lines string
186     def get_help(self):
187         if self.help_str == None:
188             return self.default_help_str
189         else:
190             return self.help_str
191
192 class CmdBuilder(CliCmd):
193     def __init__(self, expr, token_desc, key = None, cb = None, help_str = None):
194         super().__init__(key = key, cb = cb, help_str = help_str)
195         assert(isinstance(expr,str)), "expr must be a string"
196         assert(type(token_desc) is dict), "token_desc must be a dict"
197         self.token_desc = token_desc
198         parser = expparser.PycolExprParser()
199         tokens = parser.tokenize(expr)
200         expr = parser.parse(tokens)
201         debug(expr.to_str())
202         self.token_desc_list = []
203         self.cmd = self.__expr2clicmd(expr, token_desc)
204
205     def match(self, tokens):
206         m = self.cmd.match(tokens)
207         if m != None and self.key != None:
208             m[self.key] = " ".join(tokens)
209         return m
210
211     def complete(self, tokens):
212         return self.cmd.complete(tokens)
213
214     def to_expr(self):
215         return self.cmd.to_expr()
216
217     def __repr__(self):
218         return "<CmdBuilder(%s)>"%(self.cmd)
219
220     def __expr2clicmd(self, expr, token_desc):
221         l = []
222         for c in expr.children:
223             l.append(self.__expr2clicmd(c, token_desc))
224         if expr.is_var():
225             varname = expr.op
226             assert(varname in token_desc)
227             cmd = token_desc[varname]
228             cmd.key = varname
229             # keep the order of the variables for help displaying
230             self.token_desc_list.append(varname)
231         elif expr.op == " ":
232             cmd = SeqCliCmd(l)
233         elif expr.op == "|":
234             cmd = OrCliCmd(l)
235         elif expr.op == "[":
236             cmd = OptionalCliCmd(l[0])
237         elif  expr.op == "(":
238             cmd = BypassCliCmd(l[0])
239         #elif expr.op == " ": #XXX
240         return cmd
241
242     def get_help(self):
243         if self.help_str == None:
244             help_str = self.default_help_str
245         else:
246             help_str = self.help_str
247         for t in self.token_desc_list:
248             help_str += "\n  %s: %s"%(t, self.token_desc[t].get_help())
249         return help_str
250
251     def to_expr(self):
252         return self.cmd.to_expr()
253
254 class TextCliCmd(CliCmd):
255     def __init__(self, text, key = None, cb = None, help_str = None):
256         super().__init__(key = key, cb = cb, help_str = help_str)
257         assert(not " " in text) # XXX find better?
258         self.text = text
259         debug("TextCliCmd(%s)"%text)
260
261     def match(self, tokens):
262         if tokens == [self.text]:
263             res = PycolMatch()
264             if self.key:
265                 res[self.key] = self.text
266             return res
267         return None
268
269     def complete(self, tokens):
270         # more than one token as input, the string does not match
271         if len(tokens) > 1:
272             return PycolCompleteList()
273         # the beginning of the string does not match
274         if len(tokens) == 1 and not self.text.startswith(tokens[0]):
275             return PycolCompleteList()
276         # complete
277         return PycolCompleteList(PycolComplete(self.text, self))
278
279     def to_expr(self):
280         return self.text
281
282     def __repr__(self):
283         return "<TextCliCmd(%s)>"%(self.text)
284
285
286 text_cmd = TextCliCmd("toto")
287
288 assert(text_cmd.test_match("") == False)
289 assert(text_cmd.test_match("tot") == False)
290 assert(text_cmd.test_match("toto toto") == False)
291 assert(text_cmd.test_match(" toto") == True)
292 assert(text_cmd.test_match("toto ") == True)
293 assert(text_cmd.test_match("    toto \n\n") == True)
294 assert(text_cmd.test_match("toto # coin") == True)
295 assert(text_cmd.test_match("toto") == True)
296
297 assert(text_cmd.test_complete("") == ["toto"])
298 assert(text_cmd.test_complete("to") == ["toto"])
299 assert(text_cmd.test_complete("tot") == ["toto"])
300 assert(text_cmd.test_complete(" tot") == ["toto"])
301 assert(text_cmd.test_complete("toto") == ["toto"])
302 assert(text_cmd.test_complete("d") == [])
303 assert(text_cmd.test_complete("toto ") == [])
304 assert(text_cmd.test_complete("toto#") == [])
305
306 class SeqCliCmd(CliCmd):
307     def __init__(self, cmdlist, key = None):
308         self.cmdlist = cmdlist
309         self.key = key
310         debug("SeqCliCmd(%s)"%cmdlist)
311
312     def match(self, tokens):
313         # only one command, try to match all tokens
314         if len(self.cmdlist) == 1:
315             m = self.cmdlist[0].match(tokens)
316             if m != None and self.key != None:
317                 m[self.key] = " ".join(tokens)
318             return m
319         # several commands, try to match the first command with 1 to N tokens,
320         # and do a recursive call for the following
321         n_match = 0
322         ret = PycolMatch()
323         for i in range(len(tokens)+1):
324             m = self.cmdlist[0].match(tokens[:i])
325             if m == None:
326                 continue
327             m2 = SeqCliCmd(self.cmdlist[1:]).match(tokens[i:])
328             if m2 == None:
329                 continue
330             if n_match >= 1:
331                 EXC()
332             ret.merge(m)
333             ret.merge(m2)
334             if self.key != None:
335                 ret[self.key] = " ".join(tokens[:i])
336             n_match += 1
337         if n_match > 0:
338             return ret
339         return None
340
341     def complete(self, tokens):
342         debug("----------complete seq <%s>"%tokens)
343
344         match_tokens = tokens[:-1]
345         complete_token = tokens[-1]
346
347         # there is no match_token (only whitespaces), try to complete the first
348         # command
349         if len(match_tokens) == 0:
350             return self.cmdlist[0].complete(tokens)
351
352         debug("match_tokens=<%s> complete_token=<%s>"%(match_tokens, complete_token))
353         res = PycolCompleteList()
354
355         # try to match match_tokens with 1 to N commands
356         for i in range(1, len(self.cmdlist)):
357             # if it does not match, continue
358             if SeqCliCmd(self.cmdlist[0:i]).match(match_tokens) == None:
359                 continue
360             # XXX res.own()
361             # if it matches, try to complete the last token
362             res2 = self.cmdlist[i].complete([complete_token])
363             res.merge(res2)
364
365         return res
366
367     def to_expr(self):
368         return " ".join([c.to_expr() for c in self.cmdlist])
369
370     def __repr__(self):
371         return "<SeqCliCmd(%s)>"%(str(self.cmdlist))
372
373 seq_cmd = SeqCliCmd([TextCliCmd("toto"), TextCliCmd("titi"), TextCliCmd("tutu")])
374 assert(seq_cmd.test_match("") == False)
375 assert(seq_cmd.test_match("toto") == False)
376 assert(seq_cmd.test_match("titi") == False)
377 assert(seq_cmd.test_match("toto d") == False)
378 assert(seq_cmd.test_match("toto  # titi tutu") == False)
379 assert(seq_cmd.test_match("toto titi tutu") == True)
380 assert(seq_cmd.test_match("  toto   titi tutu") == True)
381 assert(seq_cmd.test_match("toto titi    tutu #") == True)
382 assert(seq_cmd.test_match("toto titi tutu") == True)
383
384 assert(seq_cmd.test_complete("") == ["toto"])
385 assert(seq_cmd.test_complete("toto ") == ["titi"])
386 assert(seq_cmd.test_complete("toto t") == ["titi"])
387 assert(seq_cmd.test_complete("toto") == ["toto"])
388 assert(seq_cmd.test_complete("titi") == [])
389 assert(seq_cmd.test_complete("toto d") == [])
390 assert(seq_cmd.test_complete("toto  # titi tutu") == [])
391 assert(seq_cmd.test_complete("toto titi tut") == ["tutu"])
392 assert(seq_cmd.test_complete("  toto   titi tut") == ["tutu"])
393 assert(seq_cmd.test_complete("toto titi    tutu #") == [])
394 assert(seq_cmd.test_complete("toto titi tutu") == ["tutu"])
395
396
397 class OrCliCmd(CliCmd):
398     def __init__(self, cmdlist, key = None):
399         self.cmdlist = cmdlist
400         self.key = key
401         debug("OrCliCmd(%s)"%cmdlist)
402
403     def match(self, tokens):
404         # try to match all commands
405         for cmd in self.cmdlist:
406             ret = cmd.match(tokens)
407             if ret == None:
408                 continue
409             if self.key != None:
410                 ret[self.key] = " ".join(tokens)
411             return ret
412         return None
413
414     def complete(self, tokens):
415         debug("----------complete or <%s>"%tokens)
416
417         res = PycolCompleteList()
418         for cmd in self.cmdlist:
419             res2 = cmd.complete(tokens)
420             res.merge(res2)
421
422         return res
423
424     def to_expr(self):
425         return "|".join([c.to_expr() for c in self.cmdlist])
426
427     def __repr__(self):
428         return "<OrCliCmd(%s)>"%(str(self.cmdlist))
429
430 or_cmd = OrCliCmd([TextCliCmd("toto"), TextCliCmd("titi"), TextCliCmd("tutu")])
431 assert(or_cmd.test_match("") == False)
432 assert(or_cmd.test_match("toto") == True)
433 assert(or_cmd.test_match("titi") == True)
434 assert(or_cmd.test_match("tutu") == True)
435 assert(or_cmd.test_match(" toto  # titi tutu") == True)
436 assert(or_cmd.test_match("toto titi tutu") == False)
437 assert(or_cmd.test_match("  toto   t") == False)
438 assert(or_cmd.test_match("toto d#") == False)
439
440 assert(or_cmd.test_complete("") == ["toto", "titi", "tutu"])
441 assert(or_cmd.test_complete("t") == ["toto", "titi", "tutu"])
442 assert(or_cmd.test_complete("to") == ["toto"])
443 assert(or_cmd.test_complete("  tot") == ["toto"])
444 assert(or_cmd.test_complete(" titi") == ["titi"])
445 assert(or_cmd.test_complete(" titi to") == [])
446 assert(or_cmd.test_complete("titid") == [])
447 assert(or_cmd.test_complete("  ti#") == [])
448
449
450
451 class OptionalCliCmd(CliCmd):
452     def __init__(self, cmd, key = None):
453         self.cmd = cmd
454         self.key = key
455
456     def match(self, tokens):
457         # match an empty buffer
458         if len(tokens) == 0:
459             ret = PycolMatch()
460             if self.key != None:
461                 ret[self.key] = ""
462             return ret
463         # else, try to match sub command
464         ret = self.cmd.match(tokens)
465         if ret != None and self.key != None:
466             ret[self.key] = " ".join(tokens)
467         return ret
468
469     def complete(self, tokens):
470         debug("----------complete optional <%s>"%tokens)
471         return self.cmd.complete(tokens)
472
473     def to_expr(self):
474         return "[" + self.cmd.to_expr() + "]"
475
476     def __repr__(self):
477         return "<OptionalCliCmd(%s)>"%(self.cmd)
478
479 opt_cmd = OptionalCliCmd(TextCliCmd("toto"))
480 assert(opt_cmd.test_match("") == True)
481 assert(opt_cmd.test_match("toto") == True)
482 assert(opt_cmd.test_match("  toto ") == True)
483 assert(opt_cmd.test_match("  toto # tutu") == True)
484 assert(opt_cmd.test_match("titi") == False)
485 assert(opt_cmd.test_match("toto titi") == False)
486 assert(opt_cmd.test_match("  toto   t") == False)
487 assert(opt_cmd.test_match("toto d#") == False)
488
489 assert(opt_cmd.test_complete("") == ["toto"])
490 assert(opt_cmd.test_complete("t") == ["toto"])
491 assert(opt_cmd.test_complete("to") == ["toto"])
492 assert(opt_cmd.test_complete("  tot") == ["toto"])
493 assert(opt_cmd.test_complete("  ") == ["toto"])
494 assert(opt_cmd.test_complete(" titi to") == [])
495 assert(opt_cmd.test_complete("titid") == [])
496 assert(opt_cmd.test_complete("  ti#") == [])
497
498 class BypassCliCmd(CliCmd):
499     def __init__(self, cmd, key = None):
500         self.cmd = cmd
501         self.key = key
502
503     def match(self, tokens):
504         m = self.cmd.match(tokens)
505         if m != None and self.key != None:
506             m[self.key] = " ".join(tokens)
507         return m
508
509     def complete(self, tokens):
510         return self.cmd.complete(tokens)
511
512     def to_expr(self):
513         return "(" + self.cmd.to_expr() + ")"
514
515     def __repr__(self):
516         return "<BypassCliCmd(%s)>"%(self.cmd)
517
518
519 class AnyTextCliCmd(CliCmd):
520     def __init__(self, key = None, help_str = None):
521         debug("AnyTextCliCmd()")
522         self.key = key
523         # XXX factorize all help and key in mother class?
524         if help_str != None:
525             self.help_str = help_str
526         else:
527             self.help_str = "Anytext token"
528
529     def match(self, tokens):
530         debug("----------match anytext <%s>"%tokens)
531         if len(tokens) != 1:
532             return None
533         res = PycolMatch()
534         if self.key:
535             res[self.key] = tokens[0]
536         return res
537
538     def complete(self, tokens):
539         debug("----------complete anytext <%s>"%tokens)
540         # does not match
541         if len(tokens) != 1:
542             return PycolCompleteList()
543         # match, but cannot complete
544         return PycolCompleteList(PycolComplete(tokens[0], self))
545
546     def to_expr(self):
547         return "<anytext>" # XXX
548
549     def __repr__(self):
550         return "<AnyTextCliCmd()>"
551
552     def get_help(self):
553         return self.help_str
554
555
556 anytext_cmd = AnyTextCliCmd()
557
558 assert(anytext_cmd.test_match("") == False)
559 assert(anytext_cmd.test_match("toto toto") == False)
560 assert(anytext_cmd.test_match(" toto") == True)
561 assert(anytext_cmd.test_match("toto ") == True)
562 assert(anytext_cmd.test_match("    toto \n\n") == True)
563 assert(anytext_cmd.test_match("toto # coin") == True)
564 assert(anytext_cmd.test_match("toto") == True)
565
566 assert(anytext_cmd.test_complete("") == [""])
567 assert(anytext_cmd.test_complete("to") == ["to"])
568 assert(anytext_cmd.test_complete("tot") == ["tot"])
569 assert(anytext_cmd.test_complete(" tot") == ["tot"])
570 assert(anytext_cmd.test_complete("toto") == ["toto"])
571 #assert(anytext_cmd.test_complete("toto#") == []) #XXX later
572
573
574
575
576 class IntCliCmd(CliCmd):
577     def __init__(self, val_min = None, val_max = None, key = None,
578                  help_str = None):
579         debug("IntCliCmd()")
580         self.key = key
581         # XXX factorize all help and key in mother class?
582         if help_str != None:
583             self.help_str = help_str
584         else:
585             self.help_str = "Int token"
586         # XXX val_min val_max
587
588     def match(self, tokens):
589         debug("----------match int <%s>"%tokens)
590         if len(tokens) != 1:
591             return None
592         try:
593             val = int(tokens[0])
594         except:
595             return None
596         res = PycolMatch()
597         if self.key:
598             res[self.key] = val
599         return res
600
601     def to_expr(self):
602         return "<int>" # XXX
603
604     def __repr__(self):
605         return "<IntCliCmd()>"
606
607     def get_help(self):
608         return self.help_str
609
610
611 int_cmd = IntCliCmd()
612
613 assert(int_cmd.test_match("") == False)
614 assert(int_cmd.test_match("toto") == False)
615 assert(int_cmd.test_match(" 13") == True)
616 assert(int_cmd.test_match("-12342 ") == True)
617 assert(int_cmd.test_match("    33 \n\n") == True)
618 assert(int_cmd.test_match("1 # coin") == True)
619 assert(int_cmd.test_match("0") == True)
620
621 assert(int_cmd.test_complete("") == [""])
622 assert(int_cmd.test_complete("1") == ["1"])
623 assert(int_cmd.test_complete("12") == ["12"])
624 assert(int_cmd.test_complete(" ee") == [])
625
626
627 class RegexpCliCmd(CliCmd):
628     def __init__(self, regexp, cmd = None, key = None,
629                  store_re_match = False, help_str = None):
630         debug("RegexpCliCmd()")
631         self.regexp = regexp
632         self.key = key
633         self.cmd = cmd
634         self.store_re_match = store_re_match
635         # XXX factorize all help and key in mother class?
636         if help_str != None:
637             self.help_str = help_str
638         else:
639             self.help_str = "Regexp token"
640
641     def match(self, tokens):
642         debug("----------match regexp <%s>"%tokens)
643         if len(tokens) != 1:
644             return None
645         m = re.fullmatch(self.regexp, tokens[0])
646         if m == None:
647             return None
648         if self.cmd != None:
649             res = self.cmd.match(tokens)
650             if res == None:
651                 return None
652         else:
653             res = PycolMatch()
654         if self.key:
655             if self.store_re_match:
656                 res[self.key] = m
657             else:
658                 res[self.key] = tokens[0]
659         return res
660
661     def complete(self, tokens):
662         if self.cmd == None:
663             return CliCmd.complete(self, tokens)
664         res = PycolCompleteList()
665         completions = self.cmd.complete(tokens)
666         for c in completions:
667             if self.match([c[0]]):
668                 res.append(PycolComplete(c[0], self))
669         return res
670
671     def __repr__(self):
672         return "<RegexpCliCmd()>"
673
674     def get_help(self):
675         return self.help_str
676
677
678 regexp_cmd = RegexpCliCmd("x[123]")
679
680 assert(regexp_cmd.test_match("") == False)
681 assert(regexp_cmd.test_match("toto") == False)
682 assert(regexp_cmd.test_match(" x1") == True)
683 assert(regexp_cmd.test_match("x3 ") == True)
684 assert(regexp_cmd.test_match("    x2\n\n") == True)
685 assert(regexp_cmd.test_match("x1 # coin") == True)
686
687 assert(regexp_cmd.test_complete("") == [""]) # XXX handle this case at upper level?
688 assert(regexp_cmd.test_complete("x1") == ["x1"])
689 assert(regexp_cmd.test_complete("  x2") == ["x2"])
690 assert(regexp_cmd.test_complete(" ee") == [])
691
692
693
694 class IPv4CliCmd(CliCmd):
695     def __init__(self, cmd = None, key = None, help_str = None):
696         debug("IPv4CliCmd()")
697         self.key = key
698         self.cmd = cmd
699         # XXX factorize all help and key in mother class?
700         if help_str != None:
701             self.help_str = help_str
702         else:
703             self.help_str = "IPv4 token"
704
705     def match(self, tokens):
706         debug("----------match IPv4 <%s>"%tokens)
707         if len(tokens) != 1:
708             return None
709         try:
710             val = socket.inet_aton(tokens[0])
711         except:
712             return None
713         if len(tokens[0].split(".")) != 4:
714             return None
715         res = PycolMatch()
716         if self.key:
717             res[self.key] = tokens[0]
718         return res
719
720     def complete(self, tokens):
721         if self.cmd == None:
722             return CliCmd.complete(self, tokens)
723         res = PycolCompleteList()
724         completions = self.cmd.complete(tokens)
725         for c in completions:
726             if self.match([c[0]]):
727                 res.append(PycolComplete(c[0], self))
728         return res
729
730     def to_expr(self):
731         return "<IPv4>" # XXX
732
733     def __repr__(self):
734         return "<IPv4CliCmd()>"
735
736     def get_help(self):
737         return self.help_str
738
739
740 ipv4_cmd = IPv4CliCmd()
741
742 assert(ipv4_cmd.test_match("") == False)
743 assert(ipv4_cmd.test_match("toto") == False)
744 assert(ipv4_cmd.test_match(" 13.3.1.3") == True)
745 assert(ipv4_cmd.test_match("255.255.255.255 ") == True)
746 assert(ipv4_cmd.test_match("    0.0.0.0 \n\n") == True)
747 assert(ipv4_cmd.test_match("1.2.3.4 # coin") == True)
748 assert(ipv4_cmd.test_match("300.2.2.2") == False)
749
750 assert(ipv4_cmd.test_complete("") == [""])
751 assert(ipv4_cmd.test_complete("1.2.3.4") == ["1.2.3.4"])
752 assert(ipv4_cmd.test_complete(" ee") == [])
753
754
755
756
757 class FileCliCmd(CliCmd):
758     def __init__(self, key = None, help_str = None):
759         debug("FileCliCmd()")
760         self.key = key
761         # XXX factorize all help and key in mother class?
762         if help_str != None:
763             self.help_str = help_str
764         else:
765             self.help_str = "File token"
766
767     def match(self, tokens):
768         debug("----------match File <%s>"%tokens)
769         if len(tokens) != 1:
770             return None
771         if not os.path.exists(tokens[0]):
772             return None
773         res = PycolMatch()
774         if self.key:
775             res[self.key] = tokens[0]
776         return res
777
778     def complete(self, tokens):
779         debug("----------complete File <%s>"%tokens)
780         res = PycolCompleteList()
781         if len(tokens) != 1:
782             return res
783         dirname = os.path.dirname(tokens[0])
784         basename = os.path.basename(tokens[0])
785         if dirname != "" and not os.path.exists(dirname):
786             return res
787         debug("dirname=%s basename=%s"%(dirname, basename))
788         if dirname == "":
789             ls = os.listdir()
790         else:
791             ls = os.listdir(dirname)
792         debug("ls=%s"%(str(ls)))
793         for f in ls:
794             path = os.path.join(dirname, f)
795             debug(path)
796             if path.startswith(tokens[0]):
797                 if os.path.isdir(path):
798                     path += "/"
799                     res.append(PycolComplete(path, self, terminal = False))
800                 else:
801                     res.append(PycolComplete(path, self, terminal = True))
802
803         debug(res)
804         return res
805
806     def to_expr(self):
807         return "<File>" # XXX
808
809     def __repr__(self):
810         return "<FileCliCmd()>"
811
812     def get_help(self):
813         return self.help_str
814
815
816 file_cmd = FileCliCmd()
817
818 assert(file_cmd.test_match("") == False)
819 assert(file_cmd.test_match("DEDEDzx") == False)
820 assert(file_cmd.test_match("/tmp") == True)
821 assert(file_cmd.test_match("/etc/passwd ") == True)
822
823 assert(file_cmd.test_complete("/tm") == ["/tmp/"])
824 assert(file_cmd.test_complete(" eededezezzzc") == [])
825
826
827
828
829 class ChoiceCliCmd(CliCmd):
830     def __init__(self, choice, key = None, cb = None, help_str = None):
831         super().__init__(key = key, cb = cb, help_str = help_str)
832         if isinstance(choice, str):
833             self.get_list_func = lambda:[choice]
834         elif isinstance(choice, list):
835             self.get_list_func = lambda:choice
836         else: # XXX isinstance(func)
837             self.get_list_func = choice
838         debug("ChoiceCliCmd(%s)"%self.get_list_func)
839
840     def match(self, tokens):
841         # more than one token as input, does not match
842         if len(tokens) > 1:
843             return None
844         l = self.get_list_func()
845         for e in l:
846             if tokens == [e]:
847                 res = PycolMatch()
848                 if self.key:
849                     res[self.key] = e
850                 return res
851         return None
852
853     def complete(self, tokens):
854         # more than one token as input, does not match
855         if len(tokens) > 1:
856             return PycolCompleteList()
857         l = self.get_list_func()
858         complete = PycolCompleteList()
859         for e in l:
860             # the beginning of the string does not match
861             if len(tokens) == 1 and not e.startswith(tokens[0]):
862                 continue
863             complete.append(PycolComplete(e, self))
864         return complete
865
866     def __repr__(self):
867         return "<ChoiceCliCmd(%s)>"%(self.get_list_func)
868
869
870 choice_cmd = ChoiceCliCmd(["toto", "titi"])
871
872 assert(choice_cmd.test_match("") == False)
873 assert(choice_cmd.test_match("tot") == False)
874 assert(choice_cmd.test_match("toto toto") == False)
875 assert(choice_cmd.test_match(" toto") == True)
876 assert(choice_cmd.test_match("titi ") == True)
877 assert(choice_cmd.test_match("    toto \n\n") == True)
878 assert(choice_cmd.test_match("toto # coin") == True)
879 assert(choice_cmd.test_match("toto") == True)
880
881 assert(choice_cmd.test_complete("") == ["toto", "titi"])
882 assert(choice_cmd.test_complete("to") == ["toto"])
883 assert(choice_cmd.test_complete("tot") == ["toto"])
884 assert(choice_cmd.test_complete(" tot") == ["toto"])
885 assert(choice_cmd.test_complete("toto") == ["toto"])
886 assert(choice_cmd.test_complete("d") == [])
887 assert(choice_cmd.test_complete("toto ") == [])
888 assert(choice_cmd.test_complete("toto#") == [])
889
890
891 if __name__ == '__main__':
892     # XXX add tests
893     cmd = CmdBuilder(
894             "toto a|b [coin] [bar]",
895             token_desc = {
896                 "toto": TextCliCmd("toto", help_str = "help for toto"),
897                 "a": TextCliCmd("a", help_str = "help for a"),
898                 "b": TextCliCmd("b", help_str = "help for b"),
899                 "coin": TextCliCmd("coin", help_str = "help for coin"),
900                 "bar": TextCliCmd("bar", help_str = "help for bar"),
901             }
902     )
903     print(cmd.to_expr())
904     assert(cmd.test_match("toto a") == True)
905     assert(cmd.test_match("toto b coin") == True)
906     assert(cmd.test_match("toto") == False)
907
908
909
910 # float
911
912 # file
913
914 # mac
915
916 # ipv6
917
918 # date