cli.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. # copyright 2003-2011 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
  2. # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
  3. #
  4. # This file is part of logilab-common.
  5. #
  6. # logilab-common is free software: you can redistribute it and/or modify it under
  7. # the terms of the GNU Lesser General Public License as published by the Free
  8. # Software Foundation, either version 2.1 of the License, or (at your option) any
  9. # later version.
  10. #
  11. # logilab-common is distributed in the hope that it will be useful, but WITHOUT
  12. # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  13. # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
  14. # details.
  15. #
  16. # You should have received a copy of the GNU Lesser General Public License along
  17. # with logilab-common. If not, see <http://www.gnu.org/licenses/>.
  18. """Command line interface helper classes.
  19. It provides some default commands, a help system, a default readline
  20. configuration with completion and persistent history.
  21. Example::
  22. class BookShell(CLIHelper):
  23. def __init__(self):
  24. # quit and help are builtins
  25. # CMD_MAP keys are commands, values are topics
  26. self.CMD_MAP['pionce'] = _("Sommeil")
  27. self.CMD_MAP['ronfle'] = _("Sommeil")
  28. CLIHelper.__init__(self)
  29. help_do_pionce = ("pionce", "pionce duree", _("met ton corps en veille"))
  30. def do_pionce(self):
  31. print 'nap is good'
  32. help_do_ronfle = ("ronfle", "ronfle volume", _("met les autres en veille"))
  33. def do_ronfle(self):
  34. print 'fuuuuuuuuuuuu rhhhhhrhrhrrh'
  35. cl = BookShell()
  36. """
  37. __docformat__ = "restructuredtext en"
  38. from logilab.common.compat import raw_input, builtins
  39. if not hasattr(builtins, '_'):
  40. builtins._ = str
  41. def init_readline(complete_method, histfile=None):
  42. """Init the readline library if available."""
  43. try:
  44. import readline
  45. readline.parse_and_bind("tab: complete")
  46. readline.set_completer(complete_method)
  47. string = readline.get_completer_delims().replace(':', '')
  48. readline.set_completer_delims(string)
  49. if histfile is not None:
  50. try:
  51. readline.read_history_file(histfile)
  52. except IOError:
  53. pass
  54. import atexit
  55. atexit.register(readline.write_history_file, histfile)
  56. except:
  57. print 'readline is not available :-('
  58. class Completer :
  59. """Readline completer."""
  60. def __init__(self, commands):
  61. self.list = commands
  62. def complete(self, text, state):
  63. """Hook called by readline when <tab> is pressed."""
  64. n = len(text)
  65. matches = []
  66. for cmd in self.list :
  67. if cmd[:n] == text :
  68. matches.append(cmd)
  69. try:
  70. return matches[state]
  71. except IndexError:
  72. return None
  73. class CLIHelper:
  74. """An abstract command line interface client which recognize commands
  75. and provide an help system.
  76. """
  77. CMD_MAP = {'help': _("Others"),
  78. 'quit': _("Others"),
  79. }
  80. CMD_PREFIX = ''
  81. def __init__(self, histfile=None) :
  82. self._topics = {}
  83. self.commands = None
  84. self._completer = Completer(self._register_commands())
  85. init_readline(self._completer.complete, histfile)
  86. def run(self):
  87. """loop on user input, exit on EOF"""
  88. while True:
  89. try:
  90. line = raw_input('>>> ')
  91. except EOFError:
  92. print
  93. break
  94. s_line = line.strip()
  95. if not s_line:
  96. continue
  97. args = s_line.split()
  98. if args[0] in self.commands:
  99. try:
  100. cmd = 'do_%s' % self.commands[args[0]]
  101. getattr(self, cmd)(*args[1:])
  102. except EOFError:
  103. break
  104. except:
  105. import traceback
  106. traceback.print_exc()
  107. else:
  108. try:
  109. self.handle_line(s_line)
  110. except:
  111. import traceback
  112. traceback.print_exc()
  113. def handle_line(self, stripped_line):
  114. """Method to overload in the concrete class (should handle
  115. lines which are not commands).
  116. """
  117. raise NotImplementedError()
  118. # private methods #########################################################
  119. def _register_commands(self):
  120. """ register available commands method and return the list of
  121. commands name
  122. """
  123. self.commands = {}
  124. self._command_help = {}
  125. commands = [attr[3:] for attr in dir(self) if attr[:3] == 'do_']
  126. for command in commands:
  127. topic = self.CMD_MAP[command]
  128. help_method = getattr(self, 'help_do_%s' % command)
  129. self._topics.setdefault(topic, []).append(help_method)
  130. self.commands[self.CMD_PREFIX + command] = command
  131. self._command_help[command] = help_method
  132. return self.commands.keys()
  133. def _print_help(self, cmd, syntax, explanation):
  134. print _('Command %s') % cmd
  135. print _('Syntax: %s') % syntax
  136. print '\t', explanation
  137. print
  138. # predefined commands #####################################################
  139. def do_help(self, command=None) :
  140. """base input of the help system"""
  141. if command in self._command_help:
  142. self._print_help(*self._command_help[command])
  143. elif command is None or command not in self._topics:
  144. print _("Use help <topic> or help <command>.")
  145. print _("Available topics are:")
  146. topics = sorted(self._topics.keys())
  147. for topic in topics:
  148. print '\t', topic
  149. print
  150. print _("Available commands are:")
  151. commands = self.commands.keys()
  152. commands.sort()
  153. for command in commands:
  154. print '\t', command[len(self.CMD_PREFIX):]
  155. else:
  156. print _('Available commands about %s:') % command
  157. print
  158. for command_help_method in self._topics[command]:
  159. try:
  160. if callable(command_help_method):
  161. self._print_help(*command_help_method())
  162. else:
  163. self._print_help(*command_help_method)
  164. except:
  165. import traceback
  166. traceback.print_exc()
  167. print 'ERROR in help method %s'% (
  168. command_help_method.func_name)
  169. help_do_help = ("help", "help [topic|command]",
  170. _("print help message for the given topic/command or \
  171. available topics when no argument"))
  172. def do_quit(self):
  173. """quit the CLI"""
  174. raise EOFError()
  175. def help_do_quit(self):
  176. return ("quit", "quit", _("quit the application"))