[tor-commits] [arm/master] Dropping the interpretor panel
atagar at torproject.org
atagar at torproject.org
Tue Dec 18 16:12:20 UTC 2012
commit 582bd8e556ea26cc1c7e2ba2aa3a6b8d859fdd2b
Author: Damian Johnson <atagar at torproject.org>
Date: Tue Dec 18 08:09:27 2012 -0800
Dropping the interpretor panel
Since introducing the interpretor panel to arm the program's been plagued
with stability issues (https://trac.torproject.org/6439). I suspect that the
root cause is related to the readline module somehow being imported, though
I tried to keep that import away from the cli use cases.
The interpretor panel was a sweet idea and will live on as a separate
application. However, as a part of arm it has been surprisingly unused (I've
*NEVER* heard of someone making use of it). Oh well...
---
README | 2 -
armrc.sample | 1 -
src/cli/__init__.py | 2 +-
src/cli/controller.py | 5 -
src/cli/interpretorPanel.py | 137 ------
src/starter.py | 14 +-
src/util/__init__.py | 2 +-
src/util/torInterpretor.py | 1065 -------------------------------------------
8 files changed, 6 insertions(+), 1222 deletions(-)
diff --git a/README b/README
index e5676f7..4d0b4c1 100644
--- a/README
+++ b/README
@@ -206,7 +206,6 @@ Layout:
logPanel.py - (page 1) displays tor, arm, and stem events
configPanel.py - (page 3) editor panel for the tor configuration
torrcPanel.py - (page 4) displays torrc and validation
- interpretorPanel.py - (page 5) interpretor for control port access
util/
__init__.py
@@ -221,7 +220,6 @@ Layout:
sysTools.py - helper for system calls, providing client side caching
textInput.py - expands the capabilities of text input fields
torConfig.py - functions for working with the torrc and config options
- torInterpretor.py - provides a shell around raw control port access
torTools.py - Stem wrapper, providing caching and derived information
uiTools.py - helper functions for presenting the user interface
diff --git a/armrc.sample b/armrc.sample
index 5fb9564..458fcbe 100644
--- a/armrc.sample
+++ b/armrc.sample
@@ -18,7 +18,6 @@ features.panels.show.log true
features.panels.show.connection true
features.panels.show.config true
features.panels.show.torrc true
-features.panels.show.interpretor true
# Read the proc contents directly instead of calling ps, netstat, and other
# resolvers. This provides very sizable performance benefits (around 90%
diff --git a/src/cli/__init__.py b/src/cli/__init__.py
index fa23640..b0d6bd8 100644
--- a/src/cli/__init__.py
+++ b/src/cli/__init__.py
@@ -2,5 +2,5 @@
Panels, popups, and handlers comprising the arm user interface.
"""
-__all__ = ["configPanel", "controller", "headerPanel", "interpretorPanel", "logPanel", "popups", "torrcPanel", "wizard"]
+__all__ = ["configPanel", "controller", "headerPanel", "logPanel", "popups", "torrcPanel", "wizard"]
diff --git a/src/cli/controller.py b/src/cli/controller.py
index de77e5a..1f2bf2b 100644
--- a/src/cli/controller.py
+++ b/src/cli/controller.py
@@ -15,7 +15,6 @@ import cli.headerPanel
import cli.logPanel
import cli.configPanel
import cli.torrcPanel
-import cli.interpretorPanel
import cli.graphing.graphPanel
import cli.graphing.bandwidthStats
import cli.graphing.connStats
@@ -37,7 +36,6 @@ CONFIG = {"startup.events": "N3",
"features.panels.show.connection": True,
"features.panels.show.config": True,
"features.panels.show.torrc": True,
- "features.panels.show.interpretor": True,
"features.redrawRate": 5,
"features.refreshRate": 5,
"features.confirmQuit": True,
@@ -99,9 +97,6 @@ def initController(stdscr, startTime):
if CONFIG["features.panels.show.torrc"]:
pagePanels.append([cli.torrcPanel.TorrcPanel(stdscr, cli.torrcPanel.Config.TORRC, config)])
- if CONFIG["features.panels.show.interpretor"]:
- pagePanels.append([cli.interpretorPanel.InterpretorPanel(stdscr)])
-
# initializes the controller
ARM_CONTROLLER = Controller(stdscr, stickyPanels, pagePanels)
diff --git a/src/cli/interpretorPanel.py b/src/cli/interpretorPanel.py
deleted file mode 100644
index 246636c..0000000
--- a/src/cli/interpretorPanel.py
+++ /dev/null
@@ -1,137 +0,0 @@
-"""
-Panel providing raw control port access with syntax hilighting, usage
-information, tab completion, and other usability features.
-"""
-
-import curses
-
-from util import panel, textInput, torInterpretor, torTools, uiTools
-
-USAGE_INFO = "to use this panel press enter"
-PROMPT_LINE = [torInterpretor.PROMPT, (USAGE_INFO, torInterpretor.USAGE_FORMAT)]
-
-# lazy loaded mapping of interpretor attributes to curses formatting constants
-FORMATS = {}
-
-def getFormat(formatAttr):
- """
- Provides the curses drawing attributes for torInterpretor formats.
-
- Arguments:
- formatAttr - list of formatting attributes
- """
-
- # initializes formats if they haven't yet been loaded
- if not FORMATS:
- 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 cursesFormatting
-
-class InterpretorPanel(panel.Panel):
- def __init__(self, stdscr):
- panel.Panel.__init__(self, stdscr, "interpretor", 0)
- self.interpretor = torInterpretor.ControlInterpretor()
- self.inputCompleter = torInterpretor.TorControlCompleter()
- self.isInputMode = False
- self.scroll = 0
-
- def prompt(self):
- """
- Enables the interpretor, prompting for input until the user enters esc or
- a blank line.
- """
-
- self.isInputMode = True
- panel.CURSES_LOCK.acquire()
-
- while self.isInputMode:
- self.redraw(True)
-
- # intercepts input so user can cycle through the history
- validator = textInput.BasicValidator()
- validator = textInput.HistoryValidator(list(reversed(self.interpretor.getBacklog())), validator)
- validator = textInput.TabCompleter(self.inputCompleter.getMatches, validator)
-
- xOffset = len(torInterpretor.PROMPT[0])
- displayLength = len(self.interpretor.getDisplayContents(PROMPT_LINE))
- if displayLength > self.maxY - 1:
- xOffset += 3 # offset for scrollbar
-
- inputLine = min(self.maxY - 1, displayLength)
- inputFormat = getFormat(torInterpretor.INPUT_FORMAT)
- input = self.getstr(inputLine, xOffset, "", inputFormat, validator = validator)
- if input == None: input = ""
- input, isDone = input.strip(), False
-
- if not input:
- # terminate input when we get a blank line
- isDone = True
- else:
- try:
- self.interpretor.handleQuery(input)
- except torInterpretor.InterpretorClosed:
- # Makes our control connection check if its been closed or not
- torTools.getConn().isAlive()
-
- isDone = True
-
- if isDone:
- self.isInputMode = False
- self.redraw(True)
-
- panel.CURSES_LOCK.release()
-
- def handleKey(self, key):
- isKeystrokeConsumed = True
- if uiTools.isSelectionKey(key):
- self.prompt()
- elif uiTools.isScrollKey(key) and not self.isInputMode:
- pageHeight = self.getPreferredSize()[0] - 1
- displayLength = len(self.interpretor.getDisplayContents(PROMPT_LINE))
- newScroll = uiTools.getScrollPosition(key, self.scroll, pageHeight, displayLength)
-
- if self.scroll != newScroll:
- self.scroll = newScroll
- self.redraw(True)
- else: isKeystrokeConsumed = False
-
- return isKeystrokeConsumed
-
- def draw(self, width, height):
- # page title
- usageMsg = " (enter \"/help\" for usage or a blank line to stop)" if self.isInputMode else ""
- self.addstr(0, 0, "Control Interpretor%s:" % usageMsg, curses.A_STANDOUT)
-
- xOffset = 0
- displayContents = self.interpretor.getDisplayContents(PROMPT_LINE)
- if len(displayContents) > height - 1:
- # if we're in input mode then make sure the last line is visible
- if self.isInputMode:
- self.scroll = len(displayContents) - height + 1
-
- xOffset = 3
- self.addScrollBar(self.scroll, self.scroll + height - 1, len(displayContents), 1)
-
- # draws prior commands and output
- drawLine = 1
- for entry in displayContents[self.scroll:]:
- cursor = xOffset
-
- for msg, formatEntry in entry:
- format = getFormat(formatEntry)
- self.addstr(drawLine, cursor, msg, format)
- cursor += len(msg)
-
- drawLine += 1
- if drawLine >= height: break
-
diff --git a/src/starter.py b/src/starter.py
index 01a6086..2b5063e 100644
--- a/src/starter.py
+++ b/src/starter.py
@@ -25,7 +25,6 @@ import util.panel
import util.procTools
import util.sysTools
import util.torConfig
-import util.torInterpretor
import util.torTools
import util.uiTools
@@ -55,8 +54,8 @@ CONFIG = {"startup.controlPassword": None,
"log.configDescriptions.persistance.saveFailed": util.log.NOTICE,
"log.savingDebugLog": util.log.NOTICE}
-OPT = "gpi:s:c:dbe:vh"
-OPT_EXPANDED = ["prompt", "interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"]
+OPT = "gi:s:c:dbe:vh"
+OPT_EXPANDED = ["interface=", "socket=", "config=", "debug", "blind", "event=", "version", "help"]
HELP_MSG = """Usage arm [OPTION]
Terminal status monitor for Tor relays.
@@ -298,7 +297,6 @@ def _dumpConfig():
if __name__ == '__main__':
startTime = time.time()
param = dict([(key, None) for key in CONFIG.keys()])
- launchPrompt = False
isDebugMode = False
configPath = DEFAULT_CONFIG # path used for customized configuration
@@ -329,7 +327,6 @@ if __name__ == '__main__':
param["startup.interface.port"] = controlPort
elif opt in ("-s", "--socket"):
param["startup.interface.socket"] = arg
- elif opt in ("-p", "--prompt"): launchPrompt = True
elif opt in ("-c", "--config"): configPath = arg # sets path of user's config
elif opt in ("-d", "--debug"): isDebugMode = True # dumps all logs
elif opt in ("-b", "--blind"):
@@ -388,7 +385,7 @@ if __name__ == '__main__':
# - tor is running (otherwise it would be kinda confusing, "tor is running
# but why does arm say that it's shut down?")
- if launchPrompt or util.torTools.isTorRunning():
+ if util.torTools.isTorRunning():
config.set("features.allowDetachedStartup", "false")
# revises defaults to match user's configuration
@@ -514,8 +511,5 @@ if __name__ == '__main__':
if util.uiTools.isUnicodeAvailable():
locale.setlocale(locale.LC_ALL, "")
- if launchPrompt:
- util.torInterpretor.showPrompt()
- else:
- cli.controller.startTorMonitor(time.time() - initTime)
+ cli.controller.startTorMonitor(time.time() - initTime)
diff --git a/src/util/__init__.py b/src/util/__init__.py
index 2b8f928..973527e 100644
--- a/src/util/__init__.py
+++ b/src/util/__init__.py
@@ -4,5 +4,5 @@ application's status, making cross platform system calls, parsing tor data,
and safely working with curses (hiding some of the gory details).
"""
-__all__ = ["conf", "connections", "enum", "gtkTools", "hostnames", "log", "panel", "procTools", "procName", "sysTools", "textInput", "torConfig", "torInterpretor", "torTools", "uiTools"]
+__all__ = ["conf", "connections", "enum", "gtkTools", "hostnames", "log", "panel", "procTools", "procName", "sysTools", "textInput", "torConfig", "torTools", "uiTools"]
diff --git a/src/util/torInterpretor.py b/src/util/torInterpretor.py
deleted file mode 100644
index 5023045..0000000
--- a/src/util/torInterpretor.py
+++ /dev/null
@@ -1,1065 +0,0 @@
-"""
-Provides an interactive interpretor for working with the Tor control port. This
-adds usability features like IRC style interpretor commands and, when ran
-directly, history and tab completion.
-"""
-
-import re
-import sys
-
-import version
-
-import stem
-
-from util import connections, enum, hostnames, torConfig, torTools, uiTools
-
-COLOR_PROMPT = True # provides a colored interpretor prompt
-INFO_HOSTNAMES = False # hostname lookups in /info results
-
-# initial location /write will save to when no path is specified
-DEFAULT_WRITE_PATH = "/tmp/torInterpretor_output"
-
-INIT_MSG = """Arm %s Control Interpretor
-Enter \"/help\" for usage information and \"/quit\" to stop.
-""" % version.VERSION
-
-TERM_COLORS = ("BLACK", "RED", "GREEN", "YELLOW", "BLUE", "MAGENTA", "CYAN", "WHITE")
-
-Color = enum.Enum(*TERM_COLORS)
-BgColor = enum.Enum(*["BG_" + color for color in TERM_COLORS])
-Attr = enum.Enum("BOLD", "UNDERLINE", "HILIGHT")
-
-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"
-
-# limits used for cropping
-BACKLOG_LIMIT = 100
-EVENTS_LIMIT = 200
-CONTENT_LIMIT = 20000
-
-MULTILINE_UNIMPLEMENTED_NOTICE = "Multi-line control options like this are not yet implemented."
-
-GENERAL_HELP = """Interpretor commands include:
- /help - provides information for interpretor and tor commands/config options
- /info - general information for a relay
- /find - searches backlog for lines with the given regex
- /events - prints events that we've received
- /write - saves backlog to a given location
- /quit - shuts down the interpretor
-
-Tor commands include:
- GETINFO - queries information from tor
- GETCONF, SETCONF, RESETCONF - show or edit a configuration option
- SIGNAL - issues control signal to the process (for resetting, stopping, etc)
- SETEVENTS - configures the events tor will notify us of
-
- USEFEATURE - enables custom behavior for the controller
- SAVECONF - writes tor's current configuration to our torrc
- LOADCONF - loads the given input like it was part of our torrc
- MAPADDRESS - replaces requests for one address with another
- POSTDESCRIPTOR - adds a relay descriptor to our cache
- EXTENDCIRCUIT - create or extend a tor circuit
- SETCIRCUITPURPOSE - configures the purpose associated with a circuit
- CLOSECIRCUIT - closes the given circuit
- ATTACHSTREAM - associates an application's stream with a tor circuit
- REDIRECTSTREAM - sets a stream's destination
- CLOSESTREAM - closes the given stream
- RESOLVE - issues an asynchronous dns or rdns request over tor
- TAKEOWNERSHIP - instructs tor to quit when this control connection is closed
- PROTOCOLINFO - queries version and controller authentication information
- QUIT - disconnect the control connection
-
-For more information use '/help [OPTION]'."""
-
-HELP_HELP = """Provides usage information for the given interpretor, tor command, or tor
-configuration option.
-
-Example:
- /help info # provides a description of the '/info' option
- /help GETINFO # usage information for tor's GETINFO controller option
- /help ExitPolicy # description of tor's ExitPolicy configuration option"""
-
-HELP_WRITE = """Writes the interpretor's backlog to the given path. If no location is
-specified then this saves to the last path specified (initially '%s').""" % DEFAULT_WRITE_PATH
-
-HELP_EVENTS = """Provides events that we've received belonging to the given event types. If
-no types are specified then this provides all the messages that we've
-received."""
-
-HELP_INFO = """Provides general information for a relay that's currently in the consensus.
-If no relay is specified then this provides information on ourselves."""
-
-HELP_FIND = """Searches the backlog for lines matching a given regular expression pattern.
-Results are deduplicated and the matching portions bolded."""
-
-HELP_QUIT = """Terminates the interpretor."""
-
-HELP_GETINFO = """Queries the tor process for information. Options are...
-"""
-
-HELP_GETCONF = """Provides the current value for a given configuration value. Options include...
-"""
-
-HELP_SETCONF = """Sets the given configuration parameters. Values can be quoted or non-quoted
-strings, and reverts the option to 0 or NULL if not provided.
-
-Examples:
- * Sets a contact address and resets our family to NULL
- SETCONF MyFamily ContactInfo=foo at bar.com
-
- * Sets an exit policy that only includes port 80/443
- SETCONF ExitPolicy=\"accept *:80, accept *:443, reject *:*\""""
-
-HELP_RESETCONF = """Reverts the given configuration options to their default values. If a value
-is provided then this behaves in the same way as SETCONF.
-
-Examples:
- * Returns both of our accounting parameters to their defaults
- RESETCONF AccountingMax AccountingStart
-
- * Uses the default exit policy and sets our nickname to be 'Goomba'
- RESETCONF ExitPolicy Nickname=Goomba"""
-
-HELP_SIGNAL = """Issues a signal that tells the tor process to reload its torrc, dump its
-stats, halt, etc.
-"""
-
-SIGNAL_DESCRIPTIONS = (
- ("RELOAD / HUP", "reload our torrc"),
- ("SHUTDOWN / INT", "gracefully shut down, waiting 30 seconds if we're a relay"),
- ("DUMP / USR1", "logs information about open connections and circuits"),
- ("DEBUG / USR2", "makes us log at the DEBUG runlevel"),
- ("HALT / TERM", "immediately shut down"),
- ("CLEARDNSCACHE", "clears any cached DNS results"),
- ("NEWNYM", "clears the DNS cache and uses new circuits for future connections")
-)
-
-HELP_SETEVENTS = """Sets the events that we will receive. This turns off any events that aren't
-listed so sending 'SETEVENTS' without any values will turn off all event reporting.
-
-For Tor versions between 0.1.1.9 and 0.2.2.1 adding 'EXTENDED' causes some
-events to give us additional information. After version 0.2.2.1 this is
-always on.
-
-Events include...
-"""
-
-HELP_USEFEATURE = """Customizes the behavior of the control port. Options include...
-"""
-
-HELP_SAVECONF = """Writes Tor's current configuration to its torrc."""
-
-HELP_LOADCONF = """Reads the given text like it belonged to our torrc.
-
-Example:
- +LOADCONF
- # sets our exit policy to just accept ports 80 and 443
- ExitPolicy accept *:80
- ExitPolicy accept *:443
- ExitPolicy reject *:*
- ."""
-
-HELP_MAPADDRESS = """Replaces future requests for one address with another.
-
-Example:
- MAPADDRESS 0.0.0.0=torproject.org 1.2.3.4=tor.freehaven.net"""
-
-HELP_POSTDESCRIPTOR = """Simulates getting a new relay descriptor."""
-
-HELP_EXTENDCIRCUIT = """Extends the given circuit or create a new one if the CircuitID is zero. The
-PATH is a comma separated list of fingerprints. If it isn't set then this
-uses Tor's normal path selection."""
-
-HELP_SETCIRCUITPURPOSE = """Sets the purpose attribute for a circuit."""
-
-HELP_CLOSECIRCUIT = """Closes the given circuit. If "IfUnused" is included then this only closes
-the circuit if it isn't currently being used."""
-
-HELP_ATTACHSTREAM = """Attaches a stream with the given built circuit (tor picks one on its own if
-CircuitID is zero). If HopNum is given then this hop is used to exit the
-circuit, otherwise the last relay is used."""
-
-HELP_REDIRECTSTREAM = """Sets the destination for a given stream. This can only be done after a
-stream is created but before it's attached to a circuit."""
-
-HELP_CLOSESTREAM = """Closes the given stream, the reason being an integer matching a reason as
-per section 6.3 of the tor-spec."""
-
-HELP_RESOLVE = """Performs IPv4 DNS resolution over tor, doing a reverse lookup instead if
-"mode=reverse" is included. This request is processed in the background and
-results in a ADDRMAP event with the response."""
-
-HELP_TAKEOWNERSHIP = """Instructs Tor to gracefully shut down when this control connection is closed."""
-
-HELP_PROTOCOLINFO = """Provides bootstrapping information that a controller might need when first
-starting, like Tor's version and controller authentication. This can be done
-before authenticating to the control port."""
-
-HELP_OPTIONS = {
- "HELP": ("/help [OPTION]", HELP_HELP),
- "WRITE": ("/write [PATH]", HELP_WRITE),
- "EVENTS": ("/events [types]", HELP_EVENTS),
- "INFO": ("/info [relay fingerprint, nickname, or IP address]", HELP_INFO),
- "FIND": ("/find PATTERN", HELP_FIND),
- "QUIT": ("/quit", HELP_QUIT),
- "GETINFO": ("GETINFO OPTION", HELP_GETINFO),
- "GETCONF": ("GETCONF OPTION", HELP_GETCONF),
- "SETCONF": ("SETCONF PARAM[=VALUE]", HELP_SETCONF),
- "RESETCONF": ("RESETCONF PARAM[=VALUE]", HELP_RESETCONF),
- "SIGNAL": ("SIGNAL SIG", HELP_SIGNAL),
- "SETEVENTS": ("SETEVENTS [EXTENDED] [EVENTS]", HELP_SETEVENTS),
- "USEFEATURE": ("USEFEATURE OPTION", HELP_USEFEATURE),
- "SAVECONF": ("SAVECONF", HELP_SAVECONF),
- "LOADCONF": ("LOADCONF...", HELP_LOADCONF),
- "MAPADDRESS": ("MAPADDRESS SOURCE_ADDR=DESTINATION_ADDR", HELP_MAPADDRESS),
- "POSTDESCRIPTOR": ("POSTDESCRIPTOR [purpose=general/controller/bridge] [cache=yes/no]...", HELP_POSTDESCRIPTOR),
- "EXTENDCIRCUIT": ("EXTENDCIRCUIT CircuitID [PATH] [purpose=general/controller]", HELP_EXTENDCIRCUIT),
- "SETCIRCUITPURPOSE": ("SETCIRCUITPURPOSE CircuitID purpose=general/controller", HELP_SETCIRCUITPURPOSE),
- "CLOSECIRCUIT": ("CLOSECIRCUIT CircuitID [IfUnused]", HELP_CLOSECIRCUIT),
- "ATTACHSTREAM": ("ATTACHSTREAM StreamID CircuitID [HOP=HopNum]", HELP_ATTACHSTREAM),
- "REDIRECTSTREAM": ("REDIRECTSTREAM StreamID Address [Port]", HELP_REDIRECTSTREAM),
- "CLOSESTREAM": ("CLOSESTREAM StreamID Reason [Flag]", HELP_CLOSESTREAM),
- "RESOLVE": ("RESOLVE [mode=reverse] address", HELP_RESOLVE),
- "TAKEOWNERSHIP": ("TAKEOWNERSHIP", HELP_TAKEOWNERSHIP),
- "PROTOCOLINFO": ("PROTOCOLINFO [ProtocolVersion]", HELP_PROTOCOLINFO),
-}
-
-class InterpretorClosed(Exception):
- """
- Exception raised when the interpretor should be shut down.
- """
-
- pass
-
-def format(msg, *attr):
- """
- Simple terminal text formatting, using ANSI escape sequences from:
- https://secure.wikimedia.org/wikipedia/en/wiki/ANSI_escape_code#CSI_codes
-
- toolkits providing similar capabilities:
- * django.utils.termcolors
- https://code.djangoproject.com/browser/django/trunk/django/utils/termcolors.py
-
- * termcolor
- http://pypi.python.org/pypi/termcolor
-
- * colorama
- http://pypi.python.org/pypi/colorama
-
- Arguments:
- msg - string to be formatted
- attr - text attributes, this can be Color, BgColor, or Attr enums and are
- case insensitive (so strings like "red" are fine)
- """
-
- encodings = []
- for textAttr in attr:
- textAttr, encoding = enum.toCamelCase(textAttr), None
- encoding = FG_ENCODING.get(textAttr, encoding)
- encoding = BG_ENCODING.get(textAttr, encoding)
- encoding = ATTR_ENCODING.get(textAttr, encoding)
- if encoding: encodings.append(encoding)
-
- if encodings:
- return (CSI % ";".join(encodings)) + msg + RESET
- else: return msg
-
-class TorControlCompleter:
- """
- Command autocompleter, fetching the valid options from the attached Tor
- instance.
- """
-
- def __init__(self):
- self._prefix = None
- self._prefixMatches = []
-
- self.commands = []
- conn = torTools.getConn()
-
- # adds all of the valid GETINFO options
- infoOptions = conn.getInfo("info/names", None)
- if infoOptions:
- for line in infoOptions.split("\n"):
- if " " in line:
- # skipping non-existant options mentioned in:
- # https://trac.torproject.org/projects/tor/ticket/3844
-
- if line.startswith("config/*") or line.startswith("dir-usage"):
- continue
-
- infoOpt = line.split(" ", 1)[0]
-
- # strips off the ending asterisk if it accepts a value
- if infoOpt.endswith("*"): infoOpt = infoOpt[:-1]
-
- self.commands.append("GETINFO %s" % infoOpt)
- else: self.commands.append("GETINFO ")
-
- # adds all of the valid GETCONF / SETCONF / RESETCONF options
- confOptions = conn.getInfo("config/names", None)
- if confOptions:
- # individual options are '<name> <type>' pairs
- confEntries = [opt.split(" ", 1)[0] for opt in confOptions.split("\n")]
- self.commands += ["GETCONF %s" % conf for conf in confEntries]
- self.commands += ["SETCONF %s " % conf for conf in confEntries]
- self.commands += ["RESETCONF %s" % conf for conf in confEntries]
- else:
- self.commands.append("GETCONF ")
- self.commands.append("SETCONF ")
- self.commands.append("RESETCONF ")
-
- # adds all of the valid SETEVENTS options
- eventOptions = conn.getInfo("events/names", None)
- if eventOptions:
- self.commands += ["SETEVENTS %s" % event for event in eventOptions.split(" ")]
- else: self.commands.append("SETEVENTS ")
-
- # adds all of the valid USEFEATURE options
- featureOptions = conn.getInfo("features/names", None)
- if featureOptions:
- self.commands += ["USEFEATURE %s" % feature for feature in featureOptions.split(" ")]
- else: self.commands.append("USEFEATURE ")
-
- # adds all of the valid SIGNAL options
- # this can't yet be fetched dynamically, as per:
- # https://trac.torproject.org/projects/tor/ticket/3842
-
- signals = ("RELOAD", "SHUTDOWN", "DUMP", "DEBUG", "HALT", "HUP", "INT",
- "USR1", "USR2", "TERM", "NEWNYM", "CLEARDNSCACHE")
- self.commands += ["SIGNAL %s" % sig for sig in signals]
-
- # shouldn't use AUTHENTICATE since we only provide the prompt with an
- # authenticated controller connection
- #self.commands.append("AUTHENTICATE")
-
- # other options
- self.commands.append("SAVECONF")
- self.commands.append("MAPADDRESS ")
- self.commands.append("EXTENDCIRCUIT ")
- self.commands.append("SETCIRCUITPURPOSE ")
- #self.commands.append("SETROUTERPURPOSE ") # deprecated option
- self.commands.append("ATTACHSTREAM ")
- self.commands.append("+POSTDESCRIPTOR ") # TODO: needs to support multiline options for this (ugg)
- self.commands.append("REDIRECTSTREAM ")
- self.commands.append("CLOSESTREAM ")
- self.commands.append("CLOSECIRCUIT ")
- self.commands.append("RESOLVE ")
- self.commands.append("PROTOCOLINFO ")
- self.commands.append("+LOADCONF") # TODO: another multiline...
- self.commands.append("TAKEOWNERSHIP")
- self.commands.append("QUIT") # TODO: give a confirmation when the user does this?
-
- # adds interpretor commands
- for cmd in HELP_OPTIONS:
- if HELP_OPTIONS[cmd][0].startswith("/"):
- self.commands.append("/" + cmd.lower())
-
- # adds help options for the previous commands
- baseCmd = set([cmd.split(" ")[0].replace("+", "").replace("/", "") for cmd in self.commands])
- for cmd in baseCmd:
- self.commands.append("/help " + cmd)
-
- # adds /help for tor configuration options
- for opt in torConfig.getConfigOptions():
- self.commands.append("/help " + opt)
-
- def getMatches(self, text):
- """
- Provides all options that match the given input. This is case insensetive.
-
- Arguments:
- text - user input text to be matched against
- """
-
- return [cmd for cmd in self.commands if cmd.lower().startswith(text.lower())]
-
- def complete(self, text, state):
- """
- Provides case insensetive autocompletion options, acting as a functor for
- the readlines set_completer function.
- """
-
- if text != self._prefix:
- self._prefix = text
- self._prefixMatches = self.getMatches(text)
-
- # Tab completion fills in the first common prefix which can be
- # problematic if they don't all match. For instance completing "Map" will
- # result in ["MAPADDRESS", "MapAddress"], which gets truncated to the
- # common prefix of just "M" when the user presses tab.
-
- if len(self._prefixMatches) > 1:
- prefixToResult = {}
-
- for match in self._prefixMatches:
- prefix = match[:len(text)]
-
- if prefix in prefixToResult:
- prefixToResult[prefix].append(match)
- else:
- prefixToResult[prefix] = [match]
-
- if len(prefixToResult) > 1:
- # we have multiple prefixes, pick the one with the most results
- self._prefixMatches = None
-
- for matchSet in prefixToResult.values():
- if not self._prefixMatches or len(self._prefixMatches) < len(matchSet):
- self._prefixMatches = matchSet
-
- if state < len(self._prefixMatches):
- return self._prefixMatches[state]
- else: return None
-
-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.backlog = [] # prior requests the user has made
- self.contents = [] # (msg, format list) tuples for what's been displayed
- self.writePath = DEFAULT_WRITE_PATH # last location we've saved to
- self.eventBuffer = [] # unread event messages
- self.loggedEvents = [] # event types that we're listening for
-
- # TODO: Dropping event listening since...
- # a. it was a complete hack
- # b. said hack isn't worth porting to stem
- # c. I'm likely about to separate the interpretor from arm anyway
-
- #torTools.getConn().addEventListener(TorEventObserver(self.registerEvent))
-
- def registerEvent(self, event):
- """
- Adds the event to our buffer so it'll be in '/events' output.
- """
-
- if event.type in self.loggedEvents:
- self.eventBuffer.append(event)
-
- eventsCrop = len(self.eventBuffer) - EVENTS_LIMIT
- if eventsCrop > 0: self.eventBuffer = self.eventBuffer[eventsCrop:]
-
- def getBacklog(self):
- """
- Provides the backlog of prior user input.
- """
-
- return self.backlog
-
- def getDisplayContents(self, appendPrompt = None):
- """
- Provides a list of lines to be displayed, each being a list of (msg,
- format) tuples for the content to be displayed. This is ordered as the
- oldest to newest.
-
- Arguments:
- appendPrompt - adds the given line to the end
- """
-
- if appendPrompt:
- return self.contents + [appendPrompt]
- else: return self.contents
-
- def doHelp(self, arg, outputEntry):
- """
- Performs the '/help' operation, giving usage information for the given
- argument or a general summary if there wasn't one.
- """
-
- arg = arg.upper()
-
- # If there's multiple arguments then just take the first. This is
- # particularly likely if they're trying to query a full command (for
- # instance "/help GETINFO version")
-
- arg = arg.split(" ")[0]
-
- # strip slash if someone enters an interpretor command (ex. "/help /help")
- if arg.startswith("/"): arg = arg[1:]
-
- if arg:
- if arg in HELP_OPTIONS:
- # Provides information for the tor or interpretor argument. This bolds
- # the usage information and indents the description after it.
- usage, description = HELP_OPTIONS[arg]
-
- outputEntry.append((usage + "\n", OUTPUT_FORMAT + (Attr.BOLD, )))
-
- for line in description.split("\n"):
- outputEntry.append((" " + line + "\n", OUTPUT_FORMAT))
-
- if arg == "GETINFO":
- # if this is the GETINFO option then also list the valid options
- infoOptions = torTools.getConn().getInfo("info/names", None)
-
- if infoOptions:
- for line in infoOptions.split("\n"):
- if line.startswith("config/*") or line.startswith("dir-usage"):
- continue
-
- lineMatch = re.match("^(.+) -- (.+)$", line)
-
- if lineMatch:
- opt, description = lineMatch.groups()
-
- outputEntry.append(("%-33s" % opt, OUTPUT_FORMAT + (Attr.BOLD, )))
- outputEntry.append((" - %s\n" % description, OUTPUT_FORMAT))
- elif arg == "GETCONF":
- # lists all of the configuration options
-
- confOptions = torTools.getConn().getInfo("config/names", None)
- if confOptions:
- confEntries = [opt.split(" ", 1)[0] for opt in confOptions.split("\n")]
-
- # displays two columns of 42 characters
- for i in range(0, len(confEntries), 2):
- lineEntries = confEntries[i : i+2]
-
- lineContent = ""
- for entry in lineEntries:
- lineContent += "%-42s" % entry
-
- outputEntry.append((lineContent + "\n", OUTPUT_FORMAT))
-
- outputEntry.append(("For more information use '/help [CONFIG OPTION]'.", OUTPUT_FORMAT + (Attr.BOLD, )))
- elif arg == "SIGNAL":
- # lists descriptions for all of the signals
- for signal, description in SIGNAL_DESCRIPTIONS:
- outputEntry.append(("%-15s" % signal, OUTPUT_FORMAT + (Attr.BOLD, )))
- outputEntry.append((" - %s\n" % description, OUTPUT_FORMAT))
- elif arg == "SETEVENTS":
- # lists all of the event types
- eventOptions = torTools.getConn().getInfo("events/names", None)
- if eventOptions:
- eventEntries = eventOptions.split()
-
- # displays four columns of 20 characters
- for i in range(0, len(eventEntries), 4):
- lineEntries = eventEntries[i : i+4]
-
- lineContent = ""
- for entry in lineEntries:
- lineContent += "%-20s" % entry
-
- outputEntry.append((lineContent + "\n", OUTPUT_FORMAT))
- elif arg == "USEFEATURE":
- # lists the feature options
- featureOptions = torTools.getConn().getInfo("features/names", None)
- if featureOptions:
- outputEntry.append((featureOptions + "\n", OUTPUT_FORMAT))
- elif arg in ("LOADCONF", "POSTDESCRIPTOR"):
- # gives a warning that this option isn't yet implemented
- outputEntry.append(("\n" + MULTILINE_UNIMPLEMENTED_NOTICE + "\n", ERROR_FORMAT))
- else:
- # check if this is a configuration option
- manEntry = torConfig.getConfigDescription(arg)
-
- if manEntry:
- # provides basic usage information in bold, followed an indented
- # copy of the man page description (wrapped to eighty characters)
-
- helpTitle = "%s %s (category: %s)\n" % (manEntry.option, manEntry.argUsage, manEntry.category)
- outputEntry.append((helpTitle, OUTPUT_FORMAT + (Attr.BOLD, )))
-
- descLines = manEntry.description.split("\n")
-
- for line in descLines:
- if not line:
- outputEntry.append(("\n", OUTPUT_FORMAT))
- else:
- while line:
- drawPortion, line = uiTools.cropStr(line, 88, 4, 4, uiTools.Ending.HYPHEN, True)
- outputEntry.append((" %s\n" % drawPortion.strip(), OUTPUT_FORMAT))
- else:
- outputEntry.append(("No help information available for '%s'..." % arg, ERROR_FORMAT))
- else:
- # provides the GENERAL_HELP with everything bolded except descriptions
- for line in GENERAL_HELP.split("\n"):
- cmdStart = line.find(" - ")
-
- if cmdStart != -1:
- outputEntry.append((line[:cmdStart], OUTPUT_FORMAT + (Attr.BOLD, )))
- outputEntry.append((line[cmdStart:] + "\n", OUTPUT_FORMAT))
- else:
- outputEntry.append((line + "\n", OUTPUT_FORMAT + (Attr.BOLD, )))
-
- def doEvents(self, arg, outputEntry):
- """
- Performs the '/events' operation, dumping the events that we've received
- belonging to the given types. If no types are specified then this provides
- all buffered events.
- """
-
- bufferedEvents = self.eventBuffer
-
- # if we have an argument then restrict it to those event types
- eventTypes = arg.upper().split()
- if eventTypes:
- bufferedEvents = [event for event in self.eventBuffer if event.type in eventTypes]
-
- for event in bufferedEvents:
- outputEntry.append((event.getDisplayMessage() + "\n", OUTPUT_FORMAT))
-
- def doWrite(self, arg, outputEntry):
- """
- Performs the '/write' operation, which attempts to save the backlog to a
- given path, defaulting to the last location we write to.
- """
-
- if arg: self.writePath = arg
- outputLines = []
-
- for line in self.contents:
- outputLines.append("".join([msg for msg, _ in line]))
-
- try:
- outputFile = open(self.writePath, "w")
- outputFile.write("\n".join(outputLines))
- outputFile.close()
- outputEntry.append(("Interpretor backlog written to: %s" % self.writePath, OUTPUT_FORMAT))
- except IOError, exc:
- outputEntry.append(("Unable to write to '%s': %s" % (self.writePath, exc), ERROR_FORMAT))
-
- def doFind(self, arg, outputEntry):
- """
- Performs the '/find' operation, which lists output from the backlog which
- matches the given regex. Results are deduplicated and matches are bolded.
- """
-
- argMatcher = None
-
- if not arg:
- outputEntry.append(("Nothing to match against", ERROR_FORMAT))
- else:
- try: argMatcher = re.compile("(%s)" % arg)
- except: outputEntry.append(("Unable to compile regex '%s'" % arg, ERROR_FORMAT))
-
- if argMatcher:
- printedLines = []
-
- for line in self.contents:
- lineText = "".join([msg for msg, _ in line])
-
- # skip if this was user input or a duplicate
- if lineText.startswith(PROMPT[0]) or lineText in printedLines:
- continue
-
- match = argMatcher.search(lineText)
- if match:
- # outputs the matching line, with the match itself bolded
- outputEntry.append((lineText[:match.start()], OUTPUT_FORMAT))
- outputEntry.append((match.group(), (OUTPUT_FORMAT + (Attr.BOLD, ))))
- outputEntry.append((lineText[match.end():] + "\n", OUTPUT_FORMAT))
- printedLines.append(lineText)
-
- def doInfo(self, arg, outputEntry):
- """
- Performs the '/info' operation, looking up a relay by fingerprint, IP
- address, or nickname and printing its descriptor and consensus entries in a
- pretty fashion.
- """
-
- fingerprint, conn = None, torTools.getConn()
-
- # TODO: also recognize <ip>:<port> entries?
-
- # determines the fingerprint, leaving it unset and adding an error message
- # if unsuccessful
- if not arg:
- # uses our fingerprint if we're a relay, otherwise gives an error
- fingerprint = conn.getInfo("fingerprint", None)
-
- if not fingerprint:
- outputEntry.append(("We aren't a relay, no information to provide", ERROR_FORMAT))
- elif len(arg) == 40 and re.match("^[0-9a-fA-F]+$", arg):
- # we got a fingerprint (fourty character hex string)
- fingerprint = arg
- elif connections.isValidIpAddress(arg):
- # we got an ip address, look up the fingerprint
- fpMatches = conn.getRelayFingerprint(arg, getAllMatches = True)
-
- if len(fpMatches) == 0:
- outputEntry.append(("No relays found at %s" % arg, ERROR_FORMAT))
- elif len(fpMatches) == 1:
- fingerprint = fpMatches[0][1]
- else:
- outputEntry.append(("Multiple relays at %s, specify which by giving a port" % arg, ERROR_FORMAT))
-
- for i in range(len(fpMatches)):
- relayEntry = outputEntry[i]
- outputEntry.append((" %i. or port: %-5s fingerprint: %s" % (i + 1, relayEntry[0], relayEntry[1]), ERROR_FORMAT))
- else:
- # we got something else, treat it as a nickname
- fingerprint = conn.getNicknameFingerprint(arg)
-
- if not fingerprint:
- outputEntry.append(("No relay with the nickname of '%s' found" % arg, ERROR_FORMAT))
-
- if fingerprint:
- consensusEntry = conn.getConsensusEntry(fingerprint)
-
- # The nickname, address, and port lookups are all based on the consensus
- # entry so if this succeeds we should be pretty confident that those
- # queries will work too.
-
- if not consensusEntry:
- outputEntry.append(("Unable to find consensus information for %s" % fingerprint, ERROR_FORMAT))
- return
-
- address, port = conn.getRelayAddress(fingerprint, (None, None))
-
- # ... but not sure enough that we won't check
- if not address or not port: return
-
- locale = conn.getInfo("ip-to-country/%s" % address, "??")
-
- if INFO_HOSTNAMES:
- hostname = hostnames.resolve(address, 10)
- else:
- hostname = None
-
- # TODO: Most of the following is copied from the _getDetailContent method
- # of cli/connections/connEntry.py - useful bits should be refactored.
- consensusLines = consensusEntry.split("\n")
-
- firstLineComp = consensusLines[0].split(" ")
- if len(firstLineComp) >= 9:
- _, nickname, _, _, pubDate, pubTime, _, orPort, _ = firstLineComp[:9]
- else: nickname, pubDate, pubTime, orPort = "", "", "", ""
-
- flags = "unknown"
- if len(consensusLines) >= 2 and consensusLines[1].startswith("s "):
- flags = consensusLines[1][2:]
-
- exitPolicy = conn.getRelayExitPolicy(fingerprint)
-
- if exitPolicy: policyLabel = exitPolicy.getSummary()
- else: policyLabel = "unknown"
-
- # fetches information from the descriptor if it's available
- torVersion, platform, contact = "", "", ""
- descriptorEntry = conn.getDescriptorEntry(fingerprint)
-
- if descriptorEntry:
- for descLine in descriptorEntry.split("\n"):
- if descLine.startswith("platform"):
- # has the tor version and platform, ex:
- # platform Tor 0.2.1.29 (r318f470bc5f2ad43) on Linux x86_64
-
- torVersion = descLine[13:descLine.find(" ", 13)]
- platform = descLine[descLine.rfind(" on ") + 4:]
- elif descLine.startswith("contact"):
- contact = descLine[8:]
-
- # clears up some highly common obscuring
- for alias in (" at ", " AT "): contact = contact.replace(alias, "@")
- for alias in (" dot ", " DOT "): contact = contact.replace(alias, ".")
-
- break # contact lines come after the platform
-
- headingAttr, infoAttr = (Attr.BOLD, Color.BLUE), ()
-
- outputEntry.append(("%s (%s)\n" % (nickname, fingerprint), infoAttr))
-
- hostnameLabel = ", %s" % hostname if hostname else ""
- outputEntry.append(("address: ", headingAttr))
- outputEntry.append(("%s:%s (%s%s)\n" % (address, port, locale, hostnameLabel), infoAttr))
-
- outputEntry.append(("published: ", headingAttr))
- outputEntry.append(("%s %s" % (pubTime, pubDate) + "\n", infoAttr))
-
- if torVersion and platform:
- outputEntry.append(("os: ", headingAttr))
- outputEntry.append((platform + "\n", infoAttr))
-
- outputEntry.append(("version: ", headingAttr))
- outputEntry.append((torVersion + "\n", infoAttr))
-
- outputEntry.append(("flags: ", headingAttr))
- outputEntry.append((flags.replace(" ", ", ") + "\n", infoAttr))
-
- outputEntry.append(("exit policy: ", headingAttr))
- outputEntry.append((policyLabel + "\n", infoAttr))
-
- if contact:
- outputEntry.append(("contact: ", headingAttr))
- outputEntry.append((contact + "\n", infoAttr))
-
- 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
- """
-
- conn = torTools.getConn()
-
- # abort if the control connection has been severed
- if not conn.isAlive():
- raise InterpretorClosed("Control connection has been closed")
-
- input = input.strip()
-
- # appends new input, cropping if too long
- self.backlog.append(input)
- backlogCrop = len(self.backlog) - BACKLOG_LIMIT
- if backlogCrop > 0: self.backlog = self.backlog[backlogCrop:]
-
- inputEntry, outputEntry = [PROMPT], []
-
- # 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 " " in input: cmd, arg = input.split(" ", 1)
- else: cmd, arg = input, ""
-
- if cmd.startswith("/"):
- # interpretor command
- inputEntry.append((input, INPUT_INTERPRETOR_FORMAT))
-
- if cmd == "/quit": raise InterpretorClosed()
- elif cmd == "/help": self.doHelp(arg, outputEntry)
- elif cmd == "/write": self.doWrite(arg, outputEntry)
- elif cmd == "/find": self.doFind(arg, outputEntry)
- elif cmd == "/events": self.doEvents(arg, outputEntry)
- elif cmd == "/info": self.doInfo(arg, outputEntry)
- else:
- outputEntry.append(("Not yet implemented...", ERROR_FORMAT)) # TODO: implement
-
- # appends a newline so all interpretor commands have a blank before the prompt
- if outputEntry:
- lastEntry = outputEntry[-1]
- outputEntry[-1] = (lastEntry[0].rstrip() + "\n", lastEntry[1])
-
- # TODO: add /help option
- else:
- # controller command
- cmd = cmd.upper() # makes commands uppercase to match the spec
-
- inputEntry.append((cmd + " ", INPUT_CMD_FORMAT))
- if arg: inputEntry.append((arg, INPUT_ARG_FORMAT))
-
- if cmd == "GETINFO":
- try:
- for param in arg.split():
- response = conn.getInfo(param)
- outputEntry.append((response + "\n", OUTPUT_FORMAT))
- except Exception, exc:
- outputEntry.append((str(exc), ERROR_FORMAT))
- elif cmd == "SETCONF" or cmd == "RESETCONF":
- # arguments can either be '<param>', '<param>=<value>', or
- # '<param>="<value>"' entries
- paramList = []
-
- while arg:
- # TODO: I'm a little dubious of this for LineList values (like the
- # ExitPolicy) since they're parsed as a single value. However, tor
- # seems to be happy to get a single comma separated string (though it
- # echos back faithfully rather than being parsed) so leaving this
- # alone for now.
-
- quotedMatch = re.match(r'^(\S+)=\"([^"]+)\"', arg)
- nonquotedMatch = re.match(r'^(\S+)=(\S+)', arg)
-
- if quotedMatch:
- # we're dealing with a '<param>="<value>"' entry
- param, value = quotedMatch.groups()
-
- paramList.append((param, value))
- arg = arg[len(param) + len(value) + 3:].strip()
- elif nonquotedMatch:
- # we're dealing with a '<param>=<value>' entry
- param, value = nonquotedMatch.groups()
-
- paramList.append((param, value))
- arg = arg[len(param) + len(value) + 1:].strip()
- else:
- # starts with just a param
- param = arg.split()[0]
- paramList.append((param, None))
- arg = arg[len(param):].strip()
-
- try:
- isReset = cmd == "RESETCONF"
- conn.setOptions(paramList, isReset)
- except Exception, exc:
- outputEntry.append((str(exc), ERROR_FORMAT))
- elif cmd == "SETEVENTS":
- return ([], []) # dropping support, see the comment for the event listener above for why
-
- self.loggedEvents = arg.split()
- #allEvents = torTools.getConn().setControllerEvents(self.loggedEvents)
- setEvents = set(self.loggedEvents).intersection(allEvents)
-
- if setEvents:
- outputEntry.append(("Successfully set event listening to: %s\n" % ", ".join(setEvents), OUTPUT_FORMAT))
- else:
- if not self.loggedEvents:
- # we purposefully disabled event listening
- outputEntry.append(("Disabled event listening\n", OUTPUT_FORMAT))
- else:
- # we tried to set events but they all failed
- outputEntry.append(("Unable to set any of the events. Event listening disabled.\n", ERROR_FORMAT))
- elif cmd.replace("+", "") in ("LOADCONF", "POSTDESCRIPTOR"):
- # provides a notice that multi-line controller input isn't yet implemented
- outputEntry.append((MULTILINE_UNIMPLEMENTED_NOTICE + "\n", ERROR_FORMAT))
- else:
- try:
- controller = conn.controller
- if controller is None: raise stem.SocketClosed()
-
- response = controller.msg(input)
-
- if cmd == "QUIT":
- raise InterpretorClosed("Closing the connection")
-
- for _, _, content in response.content():
- outputEntry.append((content + '\n', OUTPUT_FORMAT))
- except Exception, exc:
- if isinstance(exc, InterpretorClosed):
- raise exc
- else:
- outputEntry.append((str(exc), ERROR_FORMAT))
-
- # converts to lists split on newlines
- inputLines = _splitOnNewlines(inputEntry)
- outputLines = _splitOnNewlines(outputEntry)
-
- # appends new contents, cropping if too long
- # TODO: it would be nice if InterpretorClosed exceptions were added to the content too
- self.contents += inputLines + outputLines
- cropLines = len(self.contents) - CONTENT_LIMIT
- if cropLines > 0: self.contents = self.contents[cropLines:]
-
- return (inputLines, outputLines)
-
-def showPrompt():
- # When displaying the prompt we want the readline module to be imported so
- # we get its neat features like history scrollback. HOWEVER, importing
- # readline prior to initializing curses causes terminal bugs, most noticeably
- # screen resizing.
-
- import readline
-
- # For Python 2.6 and earlier cycling history via the readline module with
- # up/down is buggy with a color prompt. For more information see:
- # http://bugs.python.org/issue12972
- #
- # To work around this while keeping a color prompt I'm padding the prompt
- # with extra reset encodings so its length is non-rendered higher (around
- # sixty characters). There's two ways that this can go wrong...
- # - if the user uses up/down to display input longer than this non-rendered
- # length then the original bug will manifest (screwed up prompt)
- # - if the terminal's width is smaller than the non-rendered prompt length
- # then the cursor and some movement will be displaced
-
- if COLOR_PROMPT:
- prompt = format(">>> ", Color.GREEN, Attr.BOLD)
-
- majorVersion = sys.version_info[0]
- minorVersion = sys.version_info[1]
-
- if majorVersion <= 2 and minorVersion <= 6:
- prompt += "\x1b[0m" * 10
- else:
- prompt = ">>> "
-
- input = ""
-
- # sets up tab autocompetion
- torCommands = TorControlCompleter()
- readline.parse_and_bind("tab: complete")
- readline.set_completer(torCommands.complete)
-
- # Essentially disables autocompletion by word delimiters. This is because
- # autocompletion options are full commands (ex. "GETINFO version") so we want
- # "GETINFO" to match to all the options rather than be treated as a complete
- # command by itself.
-
- readline.set_completer_delims("\n")
- interpretor = ControlInterpretor()
-
- print INIT_MSG
-
- while True:
- try:
- input = raw_input(prompt)
- _, outputEntry = interpretor.handleQuery(input)
- except (KeyboardInterrupt, Exception), exc:
- if isinstance(exc, InterpretorClosed) and str(exc):
- print format(str(exc), *ERROR_FORMAT)
-
- # moves cursor to the next line and terminates (most commonly
- # KeyboardInterrupt and EOFErro)
- print
-
- torTools.NO_SPAWN = True
- torTools.getConn().close()
-
- # stop daemons
- hostnames.stop()
-
- break
-
- for line in outputEntry:
- outputLine = ""
-
- for msg, msgFormat in line:
- outputLine += format(msg, *msgFormat)
-
- print outputLine
-
-def _splitOnNewlines(entry):
- """
- Splits a list of (msg, format) tuples on newlines into a list of lines.
-
- Arguments:
- entry - list of display tuples
- """
-
- results, tmpLine = [], []
- entry = list(entry) # shallow copy
-
- while entry:
- msg, msgFormat = entry.pop(0)
-
- if "\n" in msg:
- msg, remainder = msg.split("\n", 1)
- entry.insert(0, (remainder, msgFormat))
-
- tmpLine.append((msg, msgFormat))
- results.append(tmpLine)
- tmpLine = []
- else:
- tmpLine.append((msg, msgFormat))
-
- if tmpLine: results.append(tmpLine)
- return results
-
More information about the tor-commits
mailing list