commit c8d21d593887bb537c144b71f15c7ed38f1880ed Author: Damian Johnson atagar@torproject.org Date: Mon Aug 29 09:50:15 2011 -0700
Class and formatting interpretor refactoring
This moves around the interpretor code a bit to allow for arbitrary attribute drawing and introduce an interpretor class (needed for when implementing the '/write' option). --- src/cli/interpretorPanel.py | 41 +++++---- src/util/torInterpretor.py | 191 ++++++++++++++++++++++--------------------- 2 files changed, 121 insertions(+), 111 deletions(-)
diff --git a/src/cli/interpretorPanel.py b/src/cli/interpretorPanel.py index 95866db..02868ce 100644 --- a/src/cli/interpretorPanel.py +++ b/src/cli/interpretorPanel.py @@ -8,7 +8,7 @@ import curses from util import panel, textInput, torInterpretor, uiTools
USAGE_INFO = "to use this panel press enter" -PROMPT_LINE = [(torInterpretor.PROMPT, torInterpretor.Formats.PROMPT), (USAGE_INFO, torInterpretor.Formats.USAGE)] +PROMPT_LINE = [torInterpretor.PROMPT, (USAGE_INFO, torInterpretor.USAGE_FORMAT)]
# limits used for cropping BACKLOG_LIMIT = 100 @@ -17,34 +17,37 @@ LINES_LIMIT = 2000 # lazy loaded curses formatting constants FORMATS = {}
-def getFormat(format): +def getFormat(formatAttr): """ - Provides the curses drawing attributes for a torInterpretor.Formats enum. - This returns plain formatting if the entry doesn't exist. + Provides the curses drawing attributes for torInterpretor formatting + attributes.
Arguments: - format - format enum to fetch + formatAttr - list of formatting attributes """
# initializes formats if they haven't yet been loaded if not FORMATS: - FORMATS[torInterpretor.Formats.PROMPT] = curses.A_BOLD | uiTools.getColor("green") - FORMATS[torInterpretor.Formats.INPUT] = uiTools.getColor("cyan") - FORMATS[torInterpretor.Formats.INPUT_INTERPRETOR] = curses.A_BOLD | uiTools.getColor("magenta") - FORMATS[torInterpretor.Formats.INPUT_CMD] = curses.A_BOLD | uiTools.getColor("green") - FORMATS[torInterpretor.Formats.INPUT_ARG] = curses.A_BOLD | uiTools.getColor("cyan") - FORMATS[torInterpretor.Formats.OUTPUT] = uiTools.getColor("blue") - FORMATS[torInterpretor.Formats.USAGE] = uiTools.getColor("cyan") - FORMATS[torInterpretor.Formats.HELP] = uiTools.getColor("magenta") - FORMATS[torInterpretor.Formats.ERROR] = curses.A_BOLD | uiTools.getColor("red") + for colorEnum in torInterpretor.Color.values(): + FORMATS[colorEnum] = uiTools.getColor(colorEnum.lower()) + + FORMATS[torInterpretor.Attr.BOLD] = curses.A_BOLD + FORMATS[torInterpretor.Attr.UNDERLINE] = curses.A_UNDERLINE + FORMATS[torInterpretor.Attr.HILIGHT] = curses.A_STANDOUT + + cursesFormatting = curses.A_NORMAL + + for attr in formatAttr: + cursesFormatting |= FORMATS.get(attr, curses.A_NORMAL)
- return FORMATS.get(format, curses.A_NORMAL) + return cursesFormatting
class InterpretorPanel(panel.Panel): def __init__(self, stdscr): panel.Panel.__init__(self, stdscr, "interpretor", 0) self.isInputMode = False self.scroll = 0 + self.interpretor = torInterpretor.ControlInterpretor() self.previousCommands = [] # user input, newest to oldest self.contents = [PROMPT_LINE] # (msg, format enum) tuples being displayed (oldest to newest)
@@ -61,18 +64,18 @@ class InterpretorPanel(panel.Panel): self.redraw(True)
# intercepts input so user can cycle through the history - torCommands = torInterpretor.TorCommandOptions() + torCommands = torInterpretor.TorControlCompleter()
validator = textInput.BasicValidator() validator = textInput.HistoryValidator(self.previousCommands, validator) validator = textInput.TabCompleter(torCommands.getMatches, validator)
- xOffset = len(torInterpretor.PROMPT) + xOffset = len(torInterpretor.PROMPT[0]) if len(self.contents) > self.maxY - 1: xOffset += 3 # offset for scrollbar
inputLine = min(self.maxY - 1, len(self.contents)) - inputFormat = getFormat(torInterpretor.Formats.INPUT) + inputFormat = getFormat(torInterpretor.INPUT_FORMAT) input = self.getstr(inputLine, xOffset, "", inputFormat, validator = validator) input, isDone = input.strip(), False
@@ -84,7 +87,7 @@ class InterpretorPanel(panel.Panel): self.previousCommands = self.previousCommands[:BACKLOG_LIMIT]
try: - inputEntry, outputEntry = torInterpretor.handleQuery(input) + inputEntry, outputEntry = self.interpretor.handleQuery(input) except torInterpretor.InterpretorClosed: isDone = True
diff --git a/src/util/torInterpretor.py b/src/util/torInterpretor.py index cffe200..c7bcebb 100644 --- a/src/util/torInterpretor.py +++ b/src/util/torInterpretor.py @@ -8,9 +8,6 @@ import readline # simply importing this provides history to raw_input
from util import enum, torTools
-PROMPT = ">>> " -Formats = enum.Enum("PROMPT", "INPUT", "INPUT_INTERPRETOR", "INPUT_CMD", "INPUT_ARG", "OUTPUT", "USAGE", "HELP", "ERROR") - TERM_COLORS = ("BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE")
Color = enum.Enum(*TERM_COLORS) @@ -21,6 +18,16 @@ FG_ENCODING = dict([(Color.values()[i], str(30 + i)) for i in range(8)]) BG_ENCODING = dict([(BgColor.values()[i], str(40 + i)) for i in range(8)]) ATTR_ENCODING = {Attr.BOLD: "1", Attr.UNDERLINE: "4", Attr.HILIGHT: "7"}
+PROMPT = (">>> ", (Attr.BOLD, Color.GREEN)) +INPUT_FORMAT = (Color.CYAN, ) +INPUT_INTERPRETOR_FORMAT = (Attr.BOLD, Color.MAGENTA) +INPUT_CMD_FORMAT = (Attr.BOLD, Color.GREEN) +INPUT_ARG_FORMAT = (Attr.BOLD, Color.CYAN) +OUTPUT_FORMAT = (Color.BLUE, ) +USAGE_FORMAT = (Color.CYAN, ) +HELP_FORMAT = (Color.MAGENTA, ) +ERROR_FORMAT = (Attr.BOLD, Color.RED) + CSI = "\x1B[%sm" RESET = CSI % "0"
@@ -64,7 +71,7 @@ def format(msg, *attr): return (CSI % ";".join(encodings)) + msg + RESET else: return msg
-class TorCommandOptions: +class TorControlCompleter: """ Command autocompleter, fetching the valid options from the attached Tor instance. @@ -169,12 +176,96 @@ class TorCommandOptions: if not state: return cmd else: state -= 1
+class ControlInterpretor: + """ + Interpretor that handles queries to the control port, providing usability + imporvements like irc style help optoins. This tracks input and responses. + """ + + def __init__(self): + self.queries = [] # requests made, newest to oldest + self.contents = [] # (msg, format list) tuples of both input and output (oldest to newest) + + def handleQuery(self, input): + """ + Processes the given input. Requests starting with a '/' are special + commands to the interpretor, and anything else is sent to the control port. + This returns an input/output tuple, each entry being a list of lines, each + line having a list of (msg, format) tuples for the content to be displayed. + This raises a InterpretorClosed if the interpretor should be shut down. + + Arguments: + input - user input to be processed + """ + + input = input.strip() + inputEntry, outputEntry = [PROMPT], [] + conn = torTools.getConn() + + # input falls into three general categories: + # - interpretor command which starts with a '/' + # - controller commands handled by torTools (this allows for caching, + # proper handling by the rest of arm, etc) + # - unrecognized controller command, this has the possability of confusing + # arm... + + if input.startswith("/"): + # interpretor command + inputEntry.append((input, INPUT_INTERPRETOR_FORMAT)) + outputEntry.append(("Not yet implemented...", ERROR_FORMAT)) # TODO: implement + + # TODO: add /help option + # TODO: add /write option + else: + # controller command + if " " in input: cmd, arg = input.split(" ", 1) + else: cmd, arg = input, "" + + # makes commands uppercase to match the spec + cmd = cmd.upper() + + inputEntry.append((cmd + " ", INPUT_CMD_FORMAT)) + if arg: inputEntry.append((arg, INPUT_ARG_FORMAT)) + + if cmd == "GETINFO": + try: + response = conn.getInfo(arg, suppressExc = False) + outputEntry.append((response, OUTPUT_FORMAT)) + except Exception, exc: + outputEntry.append((str(exc), ERROR_FORMAT)) + elif cmd == "SETCONF": + if "=" in arg: + param, value = arg.split("=", 1) + + try: + conn.setOption(param.strip(), value.strip()) + except Exception, exc: + outputEntry.append((str(exc), ERROR_FORMAT)) + else: + # TODO: resets the attribute + outputEntry.append(("Not yet implemented...", ERROR_FORMAT)) # TODO: implement + else: + try: + response = conn.getTorCtl().sendAndRecv("%s\r\n" % input) + + for entry in response: + # Response entries are tuples with the response code, body, and + # extra info. For instance: + # ('250', 'version=0.2.2.23-alpha (git-b85eb949b528f4d7)', None) + + if len(entry) == 3: + outputEntry.append((entry[1], OUTPUT_FORMAT)) + except Exception, exc: + outputEntry.append((str(exc), ERROR_FORMAT)) + + return (_splitOnNewlines(inputEntry), _splitOnNewlines(outputEntry)) + def prompt(): prompt = format(">>> ", Color.GREEN, Attr.BOLD) input = ""
# sets up tab autocompetion - torCommands = TorCommandOptions() + torCommands = TorControlCompleter() readline.parse_and_bind("tab: complete") readline.set_completer(torCommands.complete)
@@ -184,17 +275,7 @@ def prompt(): # command by itself.
readline.set_completer_delims("\n") - - formatMap = {} # mapping of Format to Color and Attr enums - formatMap[Formats.PROMPT] = (Attr.BOLD, Color.GREEN) - formatMap[Formats.INPUT] = (Color.CYAN, ) - formatMap[Formats.INPUT_INTERPRETOR] = (Attr.BOLD, Color.MAGENTA) - formatMap[Formats.INPUT_CMD] = (Attr.BOLD, Color.GREEN) - formatMap[Formats.INPUT_ARG] = (Attr.BOLD, Color.CYAN) - formatMap[Formats.OUTPUT] = (Color.BLUE, ) - formatMap[Formats.USAGE] = (Color.CYAN, ) - formatMap[Formats.HELP] = (Color.MAGENTA, ) - formatMap[Formats.ERROR] = (Attr.BOLD, Color.RED) + interpretor = ControlInterpretor()
while input != "/quit": try: @@ -205,90 +286,16 @@ def prompt(): print break
- _, outputEntry = handleQuery(input) + _, outputEntry = interpretor.handleQuery(input)
for line in outputEntry: outputLine = ""
for msg, msgFormat in line: - outputLine += format(msg, *formatMap[msgFormat]) + outputLine += format(msg, *msgFormat)
print outputLine
-def handleQuery(input): - """ - Processes the given input. Requests starting with a '/' are special - commands to the interpretor, and anything else is sent to the control port. - This returns an input/output tuple, each entry being a list of lines, each - line having a list of (msg, format) tuples for the content to be displayed. - This raises a InterpretorClosed if the interpretor should be shut down. - - Arguments: - input - user input to be processed - """ - - input = input.strip() - inputEntry, outputEntry = [(PROMPT, Formats.PROMPT)], [] - conn = torTools.getConn() - - # input falls into three general categories: - # - interpretor command which starts with a '/' - # - controller commands handled by torTools (this allows for caching, - # proper handling by the rest of arm, etc) - # - unrecognized controller command, this has the possability of confusing - # arm... - - if input.startswith("/"): - # interpretor command - inputEntry.append((input, Formats.INPUT_INTERPRETOR)) - outputEntry.append(("Not yet implemented...", Formats.ERROR)) # TODO: implement - - # TODO: add /help option - # TODO: add /write option - else: - # controller command - if " " in input: cmd, arg = input.split(" ", 1) - else: cmd, arg = input, "" - - # makes commands uppercase to match the spec - cmd = cmd.upper() - - inputEntry.append((cmd + " ", Formats.INPUT_CMD)) - if arg: inputEntry.append((arg, Formats.INPUT_ARG)) - - if cmd == "GETINFO": - try: - response = conn.getInfo(arg, suppressExc = False) - outputEntry.append((response, Formats.OUTPUT)) - except Exception, exc: - outputEntry.append((str(exc), Formats.ERROR)) - elif cmd == "SETCONF": - if "=" in arg: - param, value = arg.split("=", 1) - - try: - conn.setOption(param.strip(), value.strip()) - except Exception, exc: - outputEntry.append((str(exc), Formats.ERROR)) - else: - # TODO: resets the attribute - outputEntry.append(("Not yet implemented...", Formats.ERROR)) # TODO: implement - else: - try: - response = conn.getTorCtl().sendAndRecv("%s\r\n" % input) - - for entry in response: - # Response entries are tuples with the response code, body, and - # extra info. For instance: - # ('250', 'version=0.2.2.23-alpha (git-b85eb949b528f4d7)', None) - - if len(entry) == 3: - outputEntry.append((entry[1], Formats.OUTPUT)) - except Exception, exc: - outputEntry.append((str(exc), Formats.ERROR)) - - return (_splitOnNewlines(inputEntry), _splitOnNewlines(outputEntry)) - def _splitOnNewlines(entry): """ Splits a list of (msg, format) tuples on newlines into a list of lines.