# -*- test-case-name: twisted.application.twist.test.test_options -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Command line options for C{twist}.
"""
from sys import stdout, stderr
from textwrap import dedent
from twisted.copyright import version
from twisted.python.usage import Options, UsageError
from twisted.logger import (
LogLevel, InvalidLogLevelError,
textFileLogObserver, jsonFileLogObserver,
)
from twisted.plugin import getPlugins
from ..reactors import installReactor, NoSuchReactor, getReactorTypes
from ..runner._exit import exit, ExitStatus
from ..service import IServiceMaker
openFile = open
class TwistOptions(Options):
"""
Command line options for C{twist}.
"""
defaultReactorName = "default"
defaultLogLevel = LogLevel.info
def __init__(self):
Options.__init__(self)
self["reactorName"] = self.defaultReactorName
self["logLevel"] = self.defaultLogLevel
self["logFile"] = stdout
def getSynopsis(self):
return "{} plugin [plugin_options]".format(
Options.getSynopsis(self)
)
def opt_version(self):
"""
Print version and exit.
"""
exit(ExitStatus.EX_OK, "{}".format(version))
def opt_reactor(self, name):
"""
The name of the reactor to use.
(options: {options})
"""
# Actually actually actually install the reactor right at this very
# moment, before any other code (for example, a sub-command plugin)
# runs and accidentally imports and installs the default reactor.
try:
self["reactor"] = self.installReactor(name)
except NoSuchReactor:
raise UsageError("Unknown reactor: {}".format(name))
else:
self["reactorName"] = name
opt_reactor.__doc__ = dedent(opt_reactor.__doc__).format(
options=", ".join(
'"{}"'.format(rt.shortName) for rt in getReactorTypes()
),
)
def installReactor(self, name):
"""
Install the reactor.
"""
if name == self.defaultReactorName:
from twisted.internet import reactor
return reactor
else:
return installReactor(name)
def opt_log_level(self, levelName):
"""
Set default log level.
(options: {options}; default: "{default}")
"""
try:
self["logLevel"] = LogLevel.levelWithName(levelName)
except InvalidLogLevelError:
raise UsageError("Invalid log level: {}".format(levelName))
opt_log_level.__doc__ = dedent(opt_log_level.__doc__).format(
options=", ".join(
'"{}"'.format(l.name) for l in LogLevel.iterconstants()
),
default=defaultLogLevel.name,
)
def opt_log_file(self, fileName):
"""
Log to file. ("-" for stdout, "+" for stderr; default: "-")
"""
if fileName == "-":
self["logFile"] = stdout
return
if fileName == "+":
self["logFile"] = stderr
return
try:
self["logFile"] = openFile(fileName, "a")
except EnvironmentError as e:
exit(
ExitStatus.EX_IOERR,
"Unable to open log file {!r}: {}".format(fileName, e)
)
def opt_log_format(self, format):
"""
Log file format.
(options: "text", "json"; default: "text" if the log file is a tty,
otherwise "json")
"""
format = format.lower()
if format == "text":
self["fileLogObserverFactory"] = textFileLogObserver
elif format == "json":
self["fileLogObserverFactory"] = jsonFileLogObserver
else:
raise UsageError("Invalid log format: {}".format(format))
self["logFormat"] = format
opt_log_format.__doc__ = dedent(opt_log_format.__doc__)
def selectDefaultLogObserver(self):
"""
Set C{fileLogObserverFactory} to the default appropriate for the
chosen C{logFile}.
"""
if "fileLogObserverFactory" not in self:
logFile = self["logFile"]
if hasattr(logFile, "isatty") and logFile.isatty():
self["fileLogObserverFactory"] = textFileLogObserver
self["logFormat"] = "text"
else:
self["fileLogObserverFactory"] = jsonFileLogObserver
self["logFormat"] = "json"
def parseOptions(self, options=None):
self.selectDefaultLogObserver()
Options.parseOptions(self, options=options)
if "reactor" not in self:
self["reactor"] = self.installReactor(self["reactorName"])
@property
def plugins(self):
if "plugins" not in self:
plugins = {}
for plugin in getPlugins(IServiceMaker):
plugins[plugin.tapname] = plugin
self["plugins"] = plugins
return self["plugins"]
@property
def subCommands(self):
plugins = self.plugins
for name in sorted(plugins):
plugin = plugins[name]
yield (
plugin.tapname,
None,
# Avoid resolving the options attribute right away, in case
# it's a property with a non-trivial getter (eg, one which
# imports modules).
lambda plugin=plugin: plugin.options(),
plugin.description,
)
def postOptions(self):
Options.postOptions(self)
if self.subCommand is None:
raise UsageError("No plugin specified.")