"""
SingleDashOptionParser has been deprecated in favor of using the argparse.ArgumentParser
directly.
"""
import argparse
import optparse
import re
import sys
import warnings
import schrodinger
# Monkey patch BadOptionError.__str__ to get single dash error messages
def _bad_option_str(self):
return optparse._("no such option: %s") % re.sub("^--", "-", self.opt_str)
optparse.BadOptionError.__str__ = _bad_option_str
[docs]class SingleDashOption(optparse.Option):
"""
A class to automatically translate long options with single dashes into
double dash long options, but still print out help with the single dash.
"""
[docs] def __init__(self, *args, **kwargs):
self.translated_options = {}
new_args = []
for arg in args:
if _is_single_dash_long_opt(arg):
new_arg = "-" + arg
new_args.append(new_arg)
self.translated_options[new_arg] = arg
else:
new_args.append(arg)
optparse.Option.__init__(self, *new_args, **kwargs)
[docs] def process_option_strings(self, formatted_option_strings):
"""
Post-process the provided formatted options string to replace the
translated double dash options with their original single dash
specifications.
"""
for new_arg, arg in self.translated_options.items():
formatted_option_strings = \
re.sub(new_arg, arg, formatted_option_strings)
# Get rid of -longopt=arg and replace it with -longopt arg.
longopt_eq = arg + "="
longopt_sp = arg + " "
formatted_option_strings = \
re.sub(longopt_eq, longopt_sp, formatted_option_strings)
return formatted_option_strings
[docs]class SingleDashOptionParser(optparse.OptionParser):
"""
An OptionParser subclass that allows long options with a single dash.
Note that this class can therefore not be used for clustering short
options - i.e. "-rf" will be interpreted as a long option "rf", not the
short options "-r" and "-f".
"""
[docs] def __init__(self, *args, **kwargs):
"""
This constructor takes the same options as the standard library's
optparse.OptionParser.
It also takes the keyword argument 'version_source', which should be
a string containing a CVS Revision string like $Revision: 1.26 $. The
'version_source' argument is used to create a standard version
string that also notes the Schrodinger python version number, so
its use should be preferred to specifying a 'version' explicitly.
"""
warnings.warn(
"SingleDashOptionParser has been deprecated for "
"argparse.ArgumentParser", PendingDeprecationWarning)
kwargs['option_class'] = SingleDashOption
kwargs['formatter'] = DelegatingIndentedHelpFormatter()
# Pop the version_source keyword argument from the dictionary, as
# it's not a valid argument for the superclass.
version_source = kwargs.pop("version_source", "")
if 'version' not in kwargs:
kwargs['version'] = version_string(version_source)
# Initialize the superclass.
optparse.OptionParser.__init__(self, *args, **kwargs)
# Explicitly remove the standard --version option and add in the one
# taking -v so that the help summary only lists this option in one
# place.
self.remove_option("--version")
self.add_option("-v",
"-version",
action="version",
help="Show the program's version and exit.")
# Rip out the --help option and add in the -help option to get the
# help message to list this with a single dash.
self.remove_option("--help")
self.add_option("-h",
"-help",
action="help",
help="Show this help message and exit.")
[docs] def parse_args(self, args=None, values=None, ignore_unknown=False):
if args is None:
args = sys.argv[1:]
else:
args = list(args)
new_args = []
while args:
arg = args.pop(0)
if arg == "--":
# If we find the marker separating options from
# arguments, just copy the remainder of arguments
# right over to new_args. EV 66995
new_args.append("--")
new_args.extend(args)
break
elif not arg.startswith("-"):
new_args.append(arg)
else:
opt, opt_str, use_next_arg = self._get_option(
arg, ignore_unknown)
if opt:
new_args.append(opt_str)
if use_next_arg and args:
new_args.append(args.pop(0))
else:
# To get EV 94174 to work, you currently have to assume
# that unknown single dash long options are actually
# long options.
if _is_single_dash_long_opt(arg):
new_args.append("-" + arg)
else:
new_args.append(arg)
# Parse the command-line options found in 'new_args'.
# Any errors except 'BadOptionError' result in a call to 'error()',
# which by default prints the usage message to stderr and calls
# sys.exit() with an error message.
# In case a 'BadOptionError' exception is caught, based on the value
# of the flag 'ignore_unknown', the option is either added to the
# left over arguments list or the 'error()' function is called.
# On success returns a pair (values, args) where 'values' is
# an Values instance (with all your option values) and 'args' is the
# list of arguments left over after parsing options.
rargs = optparse.OptionParser._get_args(self, new_args)
if values is None:
values = optparse.OptionParser.get_default_values(self)
# Store the halves of the argument list as attributes for the
# convenience of callbacks:
# rargs
# the rest of the command-line (the "r" stands for
# "remaining" or "right-hand")
# largs
# the leftover arguments -- ie. what's left after removing
# options and their arguments (the "l" stands for "leftover"
# or "left-hand")
self.rargs = rargs
self.largs = largs = []
self.values = values
while (rargs):
try:
optparse.OptionParser._process_args(self, largs, rargs, values)
except optparse.BadOptionError as err:
if ignore_unknown:
# Get the undefined option that caused the exception.
opt = err.opt_str
# If it is a long option, strip a '-' from the option name.
if opt.startswith('--'):
# <editorial>
# Hack on top of hack on top of hack. Hold my nose
# and make the change.
# </editorial>
# Fix for EV 124231.
if opt not in new_args:
for arg in new_args:
if (arg.startswith(opt) and '=' in arg and
arg[len(opt) + 1:] == rargs[0]):
rargs.pop(0)
opt = arg
opt = opt[1:]
# Append the undefined option to left over args.
largs.append(opt)
else:
optparse.OptionParser.error(self, str(err))
except optparse.OptionValueError as err:
optparse.OptionParser.error(self, str(err))
else:
# Continue processing only if the following conditions are met:
# a) BadOptionError exception was raised.
# b) The flag 'ignore_unknown' is set to True.
break
arguments = largs + rargs
return optparse.OptionParser.check_values(self, values, arguments)
[docs] def has_option(self, option):
if _is_single_dash_long_opt(option):
option = "-" + option
return optparse.OptionParser.has_option(self, option)
[docs] def get_option(self, opt_str):
"""
Return the Option instance with the option string opt_str, or None
if no options have that string.
"""
if _is_single_dash_long_opt(opt_str):
opt_str = "-" + opt_str
return optparse.OptionParser.get_option(self, opt_str)
[docs] def remove_option(self, opt_str):
"""
Remove the Option with the option string opt_str.
"""
if _is_single_dash_long_opt(opt_str):
opt_str = "-" + opt_str
optparse.OptionParser.remove_option(self, opt_str)
def _get_option(self, opt_str, ignore_unknown):
"""
Return an option that matches the opt_str or for which opt_str is an
unambiguous abbreviation. A long option should begin with a single
dash. Options with embedded equals signs are allowed.
"""
orig_opt_str = opt_str
single_dash_long_opt = _is_single_dash_long_opt(opt_str)
if single_dash_long_opt:
opt_str = "-" + opt_str
# 1) Look for exact option string matches.
eq_index = opt_str.find('=')
if eq_index != -1:
option = optparse.OptionParser.get_option(self, opt_str[:eq_index])
else:
option = optparse.OptionParser.get_option(self, opt_str)
if option:
if eq_index != -1:
use_next_arg = False
else:
use_next_arg = option.takes_value()
return option, opt_str, use_next_arg
# 2) Check for unique abbrevations, as is done in optparse.
# If ignoring unknown options, assume that short options are
# unknown, but check longer ones as abbreviations.
if len(opt_str) == 2 and ignore_unknown:
return None, orig_opt_str, False
if len(opt_str) == 2:
# Check to see if a short option is actually an abbreviation for
# a long option.
possibilities = [
opt for opt in self._long_opt if opt.startswith("-" + opt_str)
]
else:
if eq_index != -1:
possibilities = [
opt for opt in self._long_opt
if opt.startswith(opt_str[:eq_index])
]
else:
possibilities = [
opt for opt in self._long_opt if opt.startswith(opt_str)
]
if len(possibilities) == 1:
option = optparse.OptionParser.get_option(self, possibilities[0])
if eq_index != -1:
use_next_arg = False
else:
use_next_arg = option.takes_value()
if len(opt_str) == 2:
return option, "-" + opt_str, use_next_arg
else:
return option, opt_str, use_next_arg
# 3) Check to see if a "single dash long option" is actually a short
# option with concatenated argument.
if single_dash_long_opt:
option = optparse.OptionParser.get_option(self, orig_opt_str[:2])
if option and option.takes_value():
return option, orig_opt_str, False
return None, orig_opt_str, False
[docs] def print_usage(self, file=None):
"""print_usage(file : file = stdout)
Print the usage message for the current program (self.usage) to
'file' (default stdout). Any occurence of the string "%prog" in
self.usage is replaced with the name of the current program
(basename of sys.argv[0]). Does nothing if self.usage is empty
or not defined.
"""
if not self.usage:
return
usage = self.get_usage()
print(usage, end=' ', file=file)
if not re.search(r"-h\b|-help\b", usage):
print(self.expand_prog_name(
" %prog {-h|-help} for a more detailed help message\n"),
file=file)
def _is_single_dash_long_opt(option):
"""
Identify a single dash long option.
"""
if len(option) > 2 and option[0] == "-" and option[1] != "-":
return True
else:
return False
[docs]def version_string(version_source=""):
"""
Form a standard version string from a source string. The source string
is searched for an embedded CVS Revision tag. If one is found, that is
used as the script version. If no Revision tag is found, only
Schrodinger python version information is included.
"""
schrodinger_version = schrodinger.__version__
# This odd construction of the regular expression is necessary to avoid the
# Revision replacement by CVS.
version_re = re.compile(r"\$%s: *(\S+) *\$" % "Revision")
match = version_re.search(version_source)
if match:
version_str = "Script version %s, Schrodinger python version %d" % (
match.group(1), schrodinger_version)
else:
version_str = "Schrodinger python version: %d" % schrodinger_version
return version_str
def _get_option_container(parser, group_header=None):
"""
Internal function to validate use of SingleDashOptionParser or
argparse.ArgumentParser.
:param parser: parser object to be validated
:type parser: SingleDashOptionParser or argparse.ArgumentParser
:param group_options: If True, add group_options under the the
opt_group_header. If False, add_option will apply to the toplevel parser
:type group_options: Boolean
:param opt_group_header: Name of OptionGroup (SDOP) or argument group
(argparse) to add options under
:type opt_group_header: str
:rtype: tuple of (parser object, parser function)
:return: the parser object to add options to and the function by which you
can add arguments
"""
opt_container = parser
if isinstance(parser, argparse.ArgumentParser):
if group_header:
opt_container = parser.add_argument_group(group_header)
add_option = opt_container.add_argument
elif isinstance(parser, SingleDashOptionParser):
if group_header:
opt_container = optparse.OptionGroup(parser, group_header)
parser.add_option_group(opt_container)
add_option = opt_container.add_option
else:
raise TypeError("Unexpected parser argument, needs to be "
"argparse.ArgumentParser or SingleDashOptionParser")
return (opt_container, add_option)