Source code for schrodinger.utils.singledashoptionparser

"""
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 DelegatingIndentedHelpFormatter(optparse.IndentedHelpFormatter): """ An IndentedHelpFormatter class that allows an Option class to alter its own option string if it has a process_option_strings method. """
[docs] def format_option_strings(self, option): """ Return a formatted option string. Allow the provided option to alter the string if it has a process_option_strings() method. """ formatted_option_strings = \ optparse.IndentedHelpFormatter.format_option_strings(self, option) try: return option.process_option_strings(formatted_option_strings) except AttributeError: 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)