[tor-commits] [arm/master] Class and formatting interpretor refactoring

atagar at torproject.org atagar at torproject.org
Tue Aug 30 16:59:17 UTC 2011


commit c8d21d593887bb537c144b71f15c7ed38f1880ed
Author: Damian Johnson <atagar at 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.





More information about the tor-commits mailing list