Skip to content

Commit

Permalink
Merge pull request #4593 from mwichmann/AddOption-docstrings
Browse files Browse the repository at this point in the history
Tweak SConsOptions docstrings
  • Loading branch information
bdbaddog authored Sep 11, 2024
2 parents 159f77e + 00c12f9 commit a74da54
Showing 1 changed file with 73 additions and 47 deletions.
120 changes: 73 additions & 47 deletions SCons/Script/SConsOptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import shutil
import sys
import textwrap
from typing import Optional

import SCons.Node.FS
import SCons.Platform.virtualenv
Expand Down Expand Up @@ -73,7 +74,7 @@ class SConsValues(optparse.Values):
1. set on the command line.
2. set in an SConscript file via :func:`~SCons.Script.Main.SetOption`.
3. the default setting (from the the ``op.add_option()``
calls in the :func:`Parser` function, below).
calls in the :func:`Parser` function.
The command line always overrides a value set in a SConscript file,
which in turn always overrides default settings. Because we want
Expand Down Expand Up @@ -144,16 +145,14 @@ def __getattr__(self, attr):
]

def set_option(self, name: str, value) -> None:
"""Sets an option *name* from an SConscript file.
"""Set an option value from a :func:`~SCons.Script.Main.SetOption` call.
Vvalidation steps for known (that is, defined in SCons itself)
options are in-line here. Validation should be along the same
lines as for options processed from the command line -
it's kind of a pain to have to duplicate. Project-defined options
can specify callbacks for the command-line version, but will have
no inbuilt validation here. It's up to the build system maintainer
to make sure :func:`~SCons.Script.Main.SetOption` is being used
correctly, we can't really do any better here.
Validation steps for settable options (those defined in SCons
itself) are in-line here. Duplicates the logic for the matching
command-line options in :func:`Parse` - these need to be kept
in sync. Cannot provide validation for options added via
:func:`~SCons.Script.Main.AddOption` since we don't know about those
ahead of time - it is up to the developer to figure that out.
Raises:
UserError: the option is not settable.
Expand Down Expand Up @@ -233,13 +232,32 @@ def set_option(self, name: str, value) -> None:


class SConsOption(optparse.Option):
def convert_value(self, opt, value):
"""SCons added option.
Changes :attr:`CHECK_METHODS` and :attr:`CONST_ACTIONS` settings from
:class:`optparse.Option` base class to tune for our usage.
New function :meth:`_check_nargs_optional` implements the ``nargs=?``
syntax from :mod:`argparse`, and is added to the ``CHECK_METHODS`` list.
Overridden :meth:`convert_value` supports this usage.
"""
# can uncomment to have a place to trap SConsOption creation for debugging:
# def __init__(self, *args, **kwargs):
# super().__init__(*args, **kwargs)

def convert_value(self, opt: str, value):
"""SCons override: recognize nargs="?"."""
if value is not None:
if self.nargs in (1, '?'):
return self.check_value(opt, value)
return tuple([self.check_value(opt, v) for v in value])

def process(self, opt, value, values, parser):
"""Process a value.
Direct copy of optparse version including the comments -
we don't change anything so this could just be dropped.
"""
# First, convert the value(s) to the right type. Howl if any
# value(s) are bogus.
value = self.convert_value(opt, value)
Expand All @@ -250,15 +268,16 @@ def process(self, opt, value, values, parser):
return self.take_action(
self.action, self.dest, opt, value, values, parser)

def _check_nargs_optional(self):
def _check_nargs_optional(self) -> None:
"""SCons added: deal with optional option-arguments."""
if self.nargs == '?' and self._short_opts:
fmt = "option %s: nargs='?' is incompatible with short options"
raise SCons.Errors.UserError(fmt % self._short_opts[0])

CHECK_METHODS = optparse.Option.CHECK_METHODS
if CHECK_METHODS is None:
CHECK_METHODS = []
CHECK_METHODS = CHECK_METHODS + [_check_nargs_optional]
CHECK_METHODS = CHECK_METHODS + [_check_nargs_optional] # added for SCons
CONST_ACTIONS = optparse.Option.CONST_ACTIONS + optparse.Option.TYPED_ACTIONS


Expand All @@ -270,8 +289,8 @@ class SConsOptionGroup(optparse.OptionGroup):
lined up with the normal "SCons Options".
"""

def format_help(self, formatter):
""" Format an option group's help text.
def format_help(self, formatter) -> str:
"""SCons-specific formatting of an option group's help text.
The title is dedented so it's flush with the "SCons Options"
title we print at the top.
Expand All @@ -285,14 +304,15 @@ def format_help(self, formatter):


class SConsBadOptionError(optparse.BadOptionError):
"""Exception used to indicate that invalid command line options were specified
:ivar str opt_str: The offending option specified on command line which is not recognized
:ivar OptionParser parser: The active argument parser
"""Raised if an invalid option value is encountered on the command line.
Attributes:
opt_str: The unrecognized command-line option.
parser: The active argument parser.
"""
# TODO why is 'parser' needed? Not called in current code base.

def __init__(self, opt_str, parser=None) -> None:
def __init__(self, opt_str: str, parser: Optional["SConsOptionParser"] = None) -> None:
self.opt_str = opt_str
self.parser = parser

Expand All @@ -304,17 +324,17 @@ class SConsOptionParser(optparse.OptionParser):
preserve_unknown_options = False
raise_exception_on_error = False

def error(self, msg):
"""Overridden OptionValueError exception handler."""
def error(self, msg: str) -> None:
"""SCons-specific handling of option errors."""
if self.raise_exception_on_error:
raise SConsBadOptionError(msg, self)
else:
self.print_usage(sys.stderr)
sys.stderr.write("SCons Error: %s\n" % msg)
sys.exit(2)

def _process_long_opt(self, rargs, values):
""" SCons-specific processing of long options.
def _process_long_opt(self, rargs, values) -> None:
"""SCons-specific processing of long options.
This is copied directly from the normal
``optparse._process_long_opt()`` method, except that, if configured
Expand All @@ -341,9 +361,9 @@ def _process_long_opt(self, rargs, values):
% (opt, self._match_long_opt(opt))
)
except optparse.BadOptionError:
# SCons addition: if requested, add unknown options to
# the "leftover arguments" list for later processing.
if self.preserve_unknown_options:
# SCons-specific: if requested, add unknown options to
# the "leftover arguments" list for later processing.
self.largs.append(arg)
if had_explicit_value:
# The unknown option will be re-processed later,
Expand All @@ -355,13 +375,15 @@ def _process_long_opt(self, rargs, values):
option = self._long_opt[opt]
if option.takes_value():
nargs = option.nargs
# SCons addition: recognize '?' for nargs
if nargs == '?':
if had_explicit_value:
value = rargs.pop(0)
else:
value = option.const
elif len(rargs) < nargs:
if nargs == 1:
# SCons addition: nicer msg if option had choices
if not option.choices:
self.error(_("%s option requires an argument") % opt)
else:
Expand Down Expand Up @@ -399,10 +421,8 @@ def reparse_local_options(self) -> None:
allow exact matches for long-opts only (no partial argument names!).
Otherwise there could be problems in :meth:`add_local_option`
below. When called from there, we try to reparse the
command-line arguments that
1. haven't been processed so far (`self.largs`), but
2. are possibly not added to the list of options yet.
command-line arguments that haven't been processed so far
(``self.largs``), but are possibly not added to the options list yet.
So, when we only have a value for ``--myargument`` so far,
a command-line argument of ``--myarg=test`` would set it,
Expand Down Expand Up @@ -450,21 +470,22 @@ def reparse_local_options(self) -> None:
self.largs = self.largs + largs_restore

def add_local_option(self, *args, **kw) -> SConsOption:
""" Adds a local option to the parser.
"""Add a local option to the parser.
This is initiated by an :func:`~SCons.Script.Main.AddOption` call to
add a user-defined command-line option. Add the option to a separate
option group for the local options, creating the group if necessary.
This is the implementation of :func:`~SCons.Script.Main.AddOption`,
to add a project-defined command-line option. Local options
are added to a separate option group, which is created if necessary.
The keyword argument *settable* is recognized specially (and
removed from *kw*). If true, the option is marked as modifiable;
by default "local" (project-added) options are not eligible for
for :func:`~SCons.Script.Main.SetOption` calls.
:func:`~SCons.Script.Main.SetOption` calls.
.. versionchanged:: 4.8.0
Added special handling of *settable*.
"""
group: SConsOptionGroup
try:
group = self.local_option_group
except AttributeError:
Expand All @@ -473,6 +494,7 @@ def add_local_option(self, *args, **kw) -> SConsOption:
self.local_option_group = group

settable = kw.pop('settable')
# this gives us an SConsOption due to the setting of self.option_class
result = group.add_option(*args, **kw)
if result:
# The option was added successfully. We now have to add the
Expand All @@ -483,6 +505,7 @@ def add_local_option(self, *args, **kw) -> SConsOption:
# any value overridden on the command line is immediately
# available if the user turns around and does a GetOption()
# right away.
# TODO: what if dest is None?
setattr(self.values.__defaults__, result.dest, result.default)
self.reparse_local_options()
if settable:
Expand All @@ -491,7 +514,7 @@ def add_local_option(self, *args, **kw) -> SConsOption:
return result

def format_local_option_help(self, formatter=None, file=None):
"""Return the help for the project-level ("local") options.
"""Return the help for the project-level ("local") SCons options.
.. versionadded:: 4.6.0
"""
Expand All @@ -514,7 +537,7 @@ def format_local_option_help(self, formatter=None, file=None):
return local_help

def print_local_option_help(self, file=None):
"""Print help for just project-defined options.
"""Print help for just local SCons options.
Writes to *file* (default stdout).
Expand All @@ -527,11 +550,11 @@ def print_local_option_help(self, file=None):

class SConsIndentedHelpFormatter(optparse.IndentedHelpFormatter):
def format_usage(self, usage) -> str:
""" Formats the usage message. """
"""Format the usage message for SCons."""
return "usage: %s\n" % usage

def format_heading(self, heading):
""" Translates heading to "SCons Options"
"""Translate heading to "SCons Options"
Heading of "Options" changed to "SCons Options."
Unfortunately, we have to do this here, because those titles
Expand All @@ -542,11 +565,10 @@ def format_heading(self, heading):
return super().format_heading(heading)

def format_option(self, option):
""" Customized option formatter.
"""SCons-specific option formatter.
A copy of the normal ``optparse.IndentedHelpFormatter.format_option()``
method. This has been snarfed so we can modify text wrapping to
our liking:
A copy of the :meth:`optparse.IndentedHelpFormatter.format_option`
method. Overridden so we can modify text wrapping to our liking:
* add our own regular expression that doesn't break on hyphens
(so things like ``--no-print-directory`` don't get broken).
Expand All @@ -556,21 +578,25 @@ def format_option(self, option):
The help for each option consists of two parts:
* the opt strings and metavars e.g. ("-x", or
"-fFILENAME, --file=FILENAME")
* the opt strings and metavars e.g. (``-x``, or
``-fFILENAME, --file=FILENAME``)
* the user-supplied help string e.g.
("turn on expert mode", "read data from FILENAME")
(``turn on expert mode``, ``read data from FILENAME``)
If possible, we write both of these on the same line::
-x turn on expert mode
But if the opt string list is too long, we put the help
If the opt string list is too long, we put the help
string on a second line, indented to the same column it would
start in if it fit on the first line::
-fFILENAME, --file=FILENAME
read data from FILENAME
Help strings are wrapped for terminal width and do not preserve
any hand-made formatting that may have been used in the ``AddOption``
call, so don't attempt prettying up a list of choices (for example).
"""
result = []
opts = self.option_strings[option]
Expand Down

0 comments on commit a74da54

Please sign in to comment.