205 lines
5.6 KiB
Python
205 lines
5.6 KiB
Python
# -*- 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.")
|