interactive.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. # Copyright 2024 Google LLC
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. from __future__ import absolute_import
  15. import shlex
  16. import traceback
  17. from log_hashing.logdehash import LogDehash
  18. import prompt_toolkit
  19. from .commander import PebbleCommander
  20. class InteractivePebbleCommander(object):
  21. """ Interactive Pebble Commander.
  22. Most/all UI implementations should either use this directly or sub-class it.
  23. """
  24. def __init__(self, loghash_path=None, tty=None, capfile=None):
  25. self.cmdr = PebbleCommander(tty=tty, interactive=True, capfile=capfile)
  26. if loghash_path is None:
  27. loghash_path = "build/src/fw/loghash_dict.json"
  28. self.dehasher = LogDehash(loghash_path)
  29. self.cmdr.attach_log_listener(self.log_listener)
  30. def __enter__(self):
  31. return self
  32. def __exit__(self, exc_type, exc_value, traceback):
  33. self.close()
  34. def __del__(self):
  35. self.close()
  36. def close(self):
  37. try:
  38. self.cmdr.close()
  39. except:
  40. pass
  41. def attach_prompt_toolkit(self):
  42. """ Attaches prompt_toolkit things
  43. """
  44. self.history = prompt_toolkit.history.InMemoryHistory()
  45. self.cli = prompt_toolkit.CommandLineInterface(
  46. application=prompt_toolkit.shortcuts.create_prompt_application(u"> ",
  47. history=self.history),
  48. eventloop=prompt_toolkit.shortcuts.create_eventloop())
  49. self.patch_context = self.cli.patch_stdout_context(raw=True)
  50. self.patch_context.__enter__()
  51. def log_listener(self, msg):
  52. """ This is called on every incoming log message.
  53. `msg` is the raw log message class, without any dehashing.
  54. Subclasses should override this probably.
  55. """
  56. line_dict = self.dehasher.dehash(msg)
  57. line = self.dehasher.commander_format_line(line_dict)
  58. print line
  59. def dispatch_command(self, string):
  60. """ Dispatches a command string.
  61. Subclasses should not override this.
  62. """
  63. args = shlex.split(string)
  64. # Starting with '!' passes the rest of the line directly to prompt.
  65. # Otherwise we try to run a command; if that fails, the line goes to prompt.
  66. if string.startswith("!"):
  67. string = string[1:] # Chop off the '!' marker
  68. else:
  69. cmd = self.cmdr.get_command(args[0])
  70. if cmd: # If we provide the command, run it.
  71. return cmd(*args[1:])
  72. return self.cmdr.send_prompt_command(string)
  73. def input_handle(self, string):
  74. """ Handles an input line.
  75. Generally the flow is to handle any UI-specific commands, then pass on to
  76. dispatch_command.
  77. Subclasses should override this probably.
  78. """
  79. # Handle "quit" strings
  80. if string in ["exit", "q", "quit"]:
  81. return False
  82. try:
  83. resp = self.dispatch_command(string)
  84. if resp is not None:
  85. print "\x1b[1m" + '\n'.join(resp) + "\x1b[m"
  86. except:
  87. print "An error occurred!"
  88. traceback.print_exc()
  89. return True
  90. def get_command(self):
  91. """ Get a command input line.
  92. If there is no line, return an empty string or None.
  93. This may block.
  94. Subclasses should override this probably.
  95. """
  96. if self.cli is None:
  97. self.attach_prompt_toolkit()
  98. doc = self.cli.run(reset_current_buffer=True)
  99. if doc:
  100. return doc.text
  101. else:
  102. return None
  103. def command_loop(self):
  104. """ The main command loop.
  105. Subclasses could override this, but it's probably not useful to do.
  106. """
  107. while True:
  108. try:
  109. cmd = self.get_command()
  110. if cmd and not self.input_handle(cmd):
  111. break
  112. except (KeyboardInterrupt, EOFError):
  113. break