[PATCH] Switch Anaconda to argparse

Anne Mulhern amulhern at redhat.com
Thu May 15 15:49:04 UTC 2014





----- Original Message -----
> From: "Martin Kolman" <mkolman at redhat.com>
> To: anaconda-patches at lists.fedorahosted.org
> Sent: Thursday, May 15, 2014 11:17:35 AM
> Subject: [PATCH] Switch Anaconda to argparse
> 
> Optparse is deprecated since Python 2.7 and unlike argparse will not be
> developer
> further.
> 
> Signed-off-by: Martin Kolman <mkolman at redhat.com>
> ---
>  anaconda                        | 153 ++++++++++-----------
>  pyanaconda/anaconda_argparse.py | 294
>  ++++++++++++++++++++++++++++++++++++++++
>  pyanaconda/anaconda_optparse.py | 250 ----------------------------------
>  3 files changed, 371 insertions(+), 326 deletions(-)
>  create mode 100644 pyanaconda/anaconda_argparse.py
>  delete mode 100644 pyanaconda/anaconda_optparse.py
> 
> diff --git a/anaconda b/anaconda
> index d206215..2cb014e 100755
> --- a/anaconda
> +++ b/anaconda
> @@ -257,17 +257,16 @@ def getAnacondaVersion():
>      from pyanaconda import _isys
>      return _isys.getAnacondaVersion()
>  
> -def parseOptions(argv=None, cmdline=None):
> -    from pyanaconda.anaconda_optparse import AnacondaOptionParser
> -    from pyanaconda.anaconda_optparse import HelpTextParser
> +def parseArguments(argv=None, boot_cmdline=None):
> +    from pyanaconda.anaconda_argparse import AnacondaArgumentParser
> +    from pyanaconda.anaconda_argparse import HelpTextParser
>  
>      # NOTE: for each long option (like '--repo'), AnacondaOptionParser
>      # checks the boot arguments for bootarg_prefix+option ('inst.repo').
>      # If require_prefix is False, it also accepts the option without the
>      # bootarg_prefix ('repo').
>      # See anaconda_optparse.py and BootArgs (in flags.py) for details.
> -    op = AnacondaOptionParser(version="%prog " + getAnacondaVersion(),
> -                              bootarg_prefix="inst.", require_prefix=False)
> +    ap = AnacondaArgumentParser(bootarg_prefix="inst.",
> require_prefix=False)
>      help_parser = HelpTextParser("/usr/share/anaconda/anaconda_options.txt")
>  
>      # NOTE: store_false options will *not* get negated when the user does
> @@ -279,96 +278,98 @@ def parseOptions(argv=None, cmdline=None):
>      # b) be prepared to maintain it for a very long time
>      # If this seems like too much trouble, *don't add a new option*!
>  
> +    # Version
> +    ap.add_argument('--version', action='version', version="%(prog)s " +
> getAnacondaVersion())
> +
>      # Interface
> -    op.add_option("-C", "--cmdline", dest="display_mode",
> action="store_const", const="c",
> -                  default="g")
> -    op.add_option("-G", "--graphical", dest="display_mode",
> action="store_const", const="g")
> -    op.add_option("-T", "--text", dest="display_mode", action="store_const",
> const="t")
> -    op.add_option("-S", "--script", dest="display_mode",
> action="store_const", const="s")
> +    ap.add_argument("-C", "--cmdline", dest="display_mode",
> action="store_const", const="c",
> +                    default="g")
> +    ap.add_argument("-G", "--graphical", dest="display_mode",
> action="store_const", const="g")
> +    ap.add_argument("-T", "--text", dest="display_mode",
> action="store_const", const="t")
> +    ap.add_argument("-S", "--script", dest="display_mode",
> action="store_const", const="s")
>  
>      # Network
> -    op.add_option("--noipv4", action="store_true", default=False)
> -    op.add_option("--noipv6", action="store_true", default=False)
> -    op.add_option("--proxy")
> +    ap.add_argument("--noipv4", action="store_true", default=False)
> +    ap.add_argument("--noipv6", action="store_true", default=False)
> +    ap.add_argument("--proxy")
>  
>      # Method of operation
> -    op.add_option("--autostep", action="store_true", default=False)
> -    op.add_option("-d", "--debug", dest="debug", action="store_true",
> default=False)
> -    op.add_option("--ks", dest="ksfile", action="store_const",
> const="/run/install/ks.cfg")
> -    op.add_option("--kickstart", dest="ksfile")
> -    op.add_option("--rescue", dest="rescue", action="store_true",
> default=False)
> -    op.add_option("--targetarch", "rpmarch", dest="targetArch",
> type="string")
> -    op.add_option("--armplatform", dest="armPlatform", type="string")
> -    op.add_option("--multilib", dest="multiLib", action="store_true",
> default=False)
> -
> -    op.add_option("-m", "--method", dest="method", default=None)
> -    op.add_option("--askmethod", dest="askmethod", action="store_true",
> default=False)
> -    op.add_option("--repo", dest="method", default=None)
> -    op.add_option("--stage2", dest="stage2", default=None)
> -    op.add_option("--noverifyssl", action="store_true", default=False)
> -
> -    op.add_option("--liveinst", action="store_true", default=False)
> +    ap.add_argument("--autostep", action="store_true", default=False)
> +    ap.add_argument("-d", "--debug", dest="debug", action="store_true",
> default=False)
> +    ap.add_argument("--ks", dest="ksfile", action="store_const",
> const="/run/install/ks.cfg")
> +    ap.add_argument("--kickstart", dest="ksfile")
> +    ap.add_argument("--rescue", dest="rescue", action="store_true",
> default=False)
> +    ap.add_argument("--targetarch", "rpmarch", dest="targetArch", type=str)
> +    ap.add_argument("--armplatform", dest="armPlatform", type=str)
> +    ap.add_argument("--multilib", dest="multiLib", action="store_true",
> default=False)
> +
> +    ap.add_argument("-m", "--method", dest="method", default=None)
> +    ap.add_argument("--askmethod", dest="askmethod", action="store_true",
> default=False)
> +    ap.add_argument("--repo", dest="method", default=None)
> +    ap.add_argument("--stage2", dest="stage2", default=None)
> +    ap.add_argument("--noverifyssl", action="store_true", default=False)
> +    ap.add_argument("--liveinst", action="store_true", default=False)
>  
>      # Display
> -    op.add_option("--headless", dest="isHeadless", action="store_true",
> default=False)
> -    op.add_option("--nofb")
> -    op.add_option("--resolution", dest="runres", default=None)
> -    op.add_option("--usefbx", dest="xdriver", action="store_const",
> const="fbdev")
> -    op.add_option("--vnc", action="store_true", default=False)
> -    op.add_option("--vncconnect")
> -    op.add_option("--vncpassword", default="")
> -    op.add_option("--xdriver", dest="xdriver", action="store",
> type="string", default=None)
> +    ap.add_argument("--headless", dest="isHeadless", action="store_true",
> default=False)
> +    ap.add_argument("--nofb")
> +    ap.add_argument("--resolution", dest="runres", default=None)
> +    ap.add_argument("--usefbx", dest="xdriver", action="store_const",
> const="fbdev")
> +    ap.add_argument("--vnc", action="store_true", default=False)
> +    ap.add_argument("--vncconnect")
> +    ap.add_argument("--vncpassword", default="")
> +    ap.add_argument("--xdriver", dest="xdriver", action="store", type=str,
> default=None)
>  
>      # Language
> -    op.add_option("--keymap")
> -    op.add_option("--kbdtype")
> -    op.add_option("--lang")
> +    ap.add_argument("--keymap")
> +    ap.add_argument("--kbdtype")
> +    ap.add_argument("--lang")
>  
>      # Obvious
> -    op.add_option("--loglevel")
> -    op.add_option("--syslog")
> +    ap.add_argument("--loglevel")
> +    ap.add_argument("--syslog")
>  
> -    op.add_option("--noselinux", dest="selinux", action="store_false",
> default=True)
> -    op.add_option("--selinux", action="store_true")
> +    ap.add_argument("--noselinux", dest="selinux", action="store_false",
> default=True)
> +    ap.add_argument("--selinux", action="store_true")
>  
> -    op.add_option("--nompath", dest="mpath", action="store_false",
> default=True)
> -    op.add_option("--mpath", action="store_true")
> +    ap.add_argument("--nompath", dest="mpath", action="store_false",
> default=True)
> +    ap.add_argument("--mpath", action="store_true")
>  
> -    op.add_option("--nodmraid", dest="dmraid", action="store_false",
> default=True)
> -    op.add_option("--dmraid", action="store_true")
> +    ap.add_argument("--nodmraid", dest="dmraid", action="store_false",
> default=True)
> +    ap.add_argument("--dmraid", action="store_true")
>  
> -    op.add_option("--noibft", dest="ibft", action="store_false",
> default=True)
> -    op.add_option("--ibft", action="store_true")
> -    op.add_option("--noiscsi", dest="iscsi", action="store_false",
> default=False)
> -    op.add_option("--iscsi", action="store_true")
> +    ap.add_argument("--noibft", dest="ibft", action="store_false",
> default=True)
> +    ap.add_argument("--ibft", action="store_true")
> +    ap.add_argument("--noiscsi", dest="iscsi", action="store_false",
> default=False)
> +    ap.add_argument("--iscsi", action="store_true")
>  
>      # Geolocation
> -    op.add_option("--geoloc")
> +    ap.add_argument("--geoloc")
>  
>      # Miscellaneous
> -    op.add_option("--module", action="append", default=[])
> -    op.add_option("--nomount", dest="rescue_nomount", action="store_true",
> default=False)
> -    op.add_option("--updates", dest="updateSrc", action="store",
> type="string")
> -    op.add_option("--dlabel", action="store_true", default=False)
> -    op.add_option("--image", action="append", dest="images", default=[],
> -       metavar="IMAGE_SPEC", help=help_parser.help_text("image"))
> -    op.add_option("--dirinstall", action="store_true", default=False,
> -       help=help_parser.help_text("dirinstall"))
> -    op.add_option("--memcheck", action="store_true", default=True)
> -    op.add_option("--nomemcheck", action="store_false", dest="memcheck")
> -    op.add_option("--leavebootorder", action="store_true", default=False)
> -    op.add_option("--noeject", action="store_false", dest="eject",
> default=True)
> -    op.add_option("--extlinux", action="store_true", default=False)
> -    op.add_option("--dnf", action="store_true", default=False)
> -    op.add_option("--mpathfriendlynames", action="store_true", default=True)
> +    ap.add_argument("--module", action="append", default=[])
> +    ap.add_argument("--nomount", dest="rescue_nomount", action="store_true",
> default=False)
> +    ap.add_argument("--updates", dest="updateSrc", action="store", type=str)
> +    ap.add_argument("--dlabel", action="store_true", default=False)
> +    ap.add_argument("--image", action="append", dest="images", default=[],
> +                    metavar="IMAGE_SPEC",
> help=help_parser.help_text("image"))
> +    ap.add_argument("--dirinstall", action="store_true", default=False,
> +                    help=help_parser.help_text("dirinstall"))
> +    ap.add_argument("--memcheck", action="store_true", default=True)
> +    ap.add_argument("--nomemcheck", action="store_false", dest="memcheck")
> +    ap.add_argument("--leavebootorder", action="store_true", default=False)
> +    ap.add_argument("--noeject", action="store_false", dest="eject",
> default=True)
> +    ap.add_argument("--extlinux", action="store_true", default=False)
> +    ap.add_argument("--dnf", action="store_true", default=False)
> +    ap.add_argument("--mpathfriendlynames", action="store_true",
> default=True)
>  
>      # some defaults change based on cmdline flags
> -    if cmdline is not None:
> -        if "console" in cmdline:
> -            op.set_defaults(display_mode="t")
> +    if boot_cmdline is not None:
> +        if "console" in boot_cmdline:
> +            ap.set_defaults(display_mode="t")
>  
> -    (options, extraArgs) = op.parse_args(argv, cmdline=cmdline)
> -    return (options, extraArgs, op.deprecated_bootargs)
> +    namespace = ap.parse_args(argv, boot_cmdline=boot_cmdline)
> +    return (namespace, ap.deprecated_bootargs)
>  
>  def setupPythonPath():
>      # First add our updates path
> @@ -747,7 +748,7 @@ if __name__ == "__main__":
>      # check if the CLI help is requested and return it at once,
>      # without importing random stuff and spamming stdout
>      if ("--help" in sys.argv) or ("-h" in sys.argv):
> -        parseOptions()
> +        parseArguments()
>  
>      print "Starting installer, one moment..."
>  
> @@ -777,7 +778,7 @@ if __name__ == "__main__":
>  
>      # do this early so we can set flags before initializing logging
>      from pyanaconda.flags import flags, can_touch_runtime_system
> -    (opts, args, depr) = parseOptions(cmdline=flags.cmdline)
> +    (opts, depr) = parseArguments(boot_cmdline=flags.cmdline)
>  
>      if opts.askmethod:
>          flags.askmethod = True
> @@ -1143,7 +1144,7 @@ if __name__ == "__main__":
>      if anaconda.displayMode == 'c':
>          flags.ksprompt = False
>  
> -    from pyanaconda.anaconda_optparse import name_path_pairs
> +    from pyanaconda.anaconda_argparse import name_path_pairs
>  
>      image_count = 0
>      try:
> diff --git a/pyanaconda/anaconda_argparse.py
> b/pyanaconda/anaconda_argparse.py
> new file mode 100644
> index 0000000..5faddee
> --- /dev/null
> +++ b/pyanaconda/anaconda_argparse.py
> @@ -0,0 +1,294 @@
> +#
> +# anaconda_optparse.py: option parsing for anaconda (CLI and boot args)
> +#
> +# Copyright (C) 2012 Red Hat, Inc.  All rights reserved.
> +#
> +# This program is free software; you can redistribute it and/or modify
> +# it under the terms of the GNU General Public License as published by
> +# the Free Software Foundation; either version 2 of the License, or
> +# (at your option) any later version.
> +#
> +# This program is distributed in the hope that it will be useful,
> +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +# GNU General Public License for more details.
> +#
> +# You should have received a copy of the GNU General Public License
> +# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> +#
> +# Authors:
> +#   Will Woods <wwoods at redhat.com>
> +
> +DESCRIPTION = "Anaconda is the installation program used by Fedora," \
> +              "Red Hat Enterprise Linux and some other distributions."
> +
> +import itertools
> +import os
> +
> +from argparse import ArgumentParser, ArgumentError
> +
> +from pyanaconda.flags import BootArgs
> +
> +import logging
> +log = logging.getLogger("anaconda")
> +
> +class AnacondaArgumentParser(ArgumentParser):
> +    """
> +    Subclass of ArgumentParser that also examines boot arguments.
> +
> +    If the "bootarg_prefix" keyword argument is set, it's assumed that all
> +    bootargs will start with that prefix.
> +
> +    "require_prefix" is a bool:
> +        False: accept the argument with or without the prefix.
> +        True: ignore the argument without the prefix. (default)
> +    """
> +    def __init__(self, *args, **kwargs):
> +        self._boot_arg = dict()
> +        self.deprecated_bootargs = []
> +        self.bootarg_prefix = kwargs.pop("bootarg_prefix", "")
> +        self.require_prefix = kwargs.pop("require_prefix", True)
> +        ArgumentParser.__init__(self, description=DESCRIPTION, *args,
> **kwargs)
> +
> +    def add_argument(self, *args, **kwargs):
> +        """
> +        Add a new option - like ArgumentParser.add_argument.
> +
> +        The long options will be added to the list of boot args, unless
> +        the keyword argument 'bootarg' is set to False.
> +
> +        Positional arguments that don't start with '-' are considered extra
> +        boot args to look for.
> +
> +        NOTE: conflict_handler is currently ignored for boot args - they
> will
> +        always raise ArgumentError if they conflict.
> +        """
> +        # TODO: add kwargs to make an option commandline-only or
> boot-arg-only
> +        flags = [a for a in args if a.startswith('-')]
> +        bootargs = [a for a in args if not a.startswith('-')]
> +        do_bootarg = kwargs.pop("bootarg", True)
> +        option = super(AnacondaArgumentParser, self).add_argument(*flags,
> **kwargs)
> +        # make a generator that returns only the long opts without the --
> prefix
> +        long_opts = (o[2:] for o in option.option_strings if
> o.startswith("--"))
> +        bootargs += (flag for flag in long_opts)
> +        if do_bootarg:
> +            for b in bootargs:
> +                if b in self._boot_arg:
> +                    raise ArgumentError(
> +                        "conflicting bootopt string: %s" % b, option)
> +                else:
> +                    self._boot_arg[b] = option
> +        return option
> +
> +    def _get_bootarg_option(self, arg):
> +        """
> +        Find the correct Option for a given bootarg (if one exists)
> +
> +        :param string arg: boot option
> +
> +        :returns: argparse option object or None if no suitable option is
> found
> +        :rtype argparse option or None
> +        """
> +        if self.bootarg_prefix and arg.startswith(self.bootarg_prefix):
> +            prefixed_option = True
> +            arg = arg[len(self.bootarg_prefix):]
> +        else:
> +            prefixed_option = False
> +        option = self._boot_arg.get(arg)
> +
> +        if self.require_prefix and not prefixed_option:
> +            return None
> +        if option and self.bootarg_prefix and not prefixed_option:
> +            self.deprecated_bootargs.append(arg)
> +        return option
> +
> +    def parse_boot_cmdline(self, boot_cmdline, namespace):
> +        """
> +        Parse the boot cmdline and set appropriate namespace according to
> +        the options set by add_argument.
> +
> +        boot_cmdline can be given as a string (to be parsed by BootArgs), or
> a
> +        dict (or any object with .iteritems()) of {bootarg:value} pairs.
> +
> +        If boot_cmdline is None, the boot_cmdline data will be whatever
> BootArgs reads
> +        by default (/proc/cmdline, /run/initramfs/etc/cmdline,
> /etc/cmdline).
> +
> +        If an option requires a value but the boot arg doesn't provide one,
> +        we'll quietly not set anything.
> +
> +        :param boot_cmdline: the Anaconda boot command line arguments
> +        :type boot_cmdline: string, dict or None
> +
> +        :param namespace: argparse Namespace instance
> +        :type namespace: argparse Namespace
> +
> +        :returns: an argparse Namespace instance
> +        :rtype: Namespace
> +        """
> +        if boot_cmdline is None or type(boot_cmdline) is str:
> +            bootargs = BootArgs(boot_cmdline)
> +        else:
> +            bootargs = boot_cmdline
> +        self.deprecated_bootargs = []
> +        # go over all options corresponding to current boot cmdline
> +        # and do any modifications necessary
> +        # NOTE: program cmdline overrides boot cmdline
> +        for arg, val in bootargs.iteritems():
> +            option = self._get_bootarg_option(arg)
> +            if option is None:
> +                # this boot option is unknown to Anaconda, skip it
> +                continue
> +            if getattr(namespace, option.dest) is not None:
> +                # if the option is already set on program command line,
> +                # we ignore any boot options that might modify it
> +                continue
> +            if option.nargs != 0 and val is None:
> +                # nargs == 0 -> option does not take any values, skip it
> +                continue  # TODO: emit a warning or something there?
> +            if option.nargs == 0 and option.const is True and val in ("0",
> "no", "off"):
> +                # nargs == 0 & constr == True -> store_true
> +                # (we could also check the class, but it begins with an
> +                # underscore, so it would be ugly)
> +                # special case: "mpath=0" would otherwise set mpath to True
> +                setattr(namespace, option.dest, False)
> +                continue
> +            setattr(namespace, option.dest, val)
> +        return namespace
> +
> +    # pylint: disable=arguments-differ
> +    def parse_args(self, args=None, boot_cmdline=None):
> +        """
> +        Like ArgumentParser.parse_args(), but also parses the boot cmdline.
> +        (see parse_boot_cmdline for details on that process.)
> +        Program cmdline arguments will override boot cmdline arguments.
> +
> +        :param args: program command line arguments
> +        :type args: string or None
> +
> +        :param boot_cmdline: the Anaconda boot command line arguments
> +        :type boot_cmdline: string, dict or None
> +
> +        :returns: an argparse Namespace instance
> +        :rtype: Namespace
> +        """
> +        # parse arguments (if any) and return the resulting namespace
> +        namespace = ArgumentParser.parse_args(self, args)
> +        # now parse boot options (if any) and modify the namespace
> accordingly
> +        namespace = self.parse_boot_cmdline(boot_cmdline, namespace)
> +        # and return the resulting namespace
> +        return namespace
> +
> +def name_path_pairs(image_specs):
> +    """Processes and verifies image file specifications. Generates pairs
> +       of names and paths.
> +
> +       :param image_specs: a list of image specifications
> +       :type image_specs: list of str
> +
> +       Each image spec in image_specs has format <path>[:<name>] where
> +       <path> is the path to a local file and <name> is an optional
> +       name used to identify the disk in UI. <name> may not contain colons
> +       or slashes.
> +
> +       If no name given in specification, synthesizes name from basename
> +       of path. Since two distinct paths may have the same basename, handles
> +       name collisions by synthesizing a different name for the colliding
> +       name.
> +
> +       Raises an exception if:
> +         * A path is empty
> +         * A path specifies a non-existant file
> +         * A path specifies a directory
> +         * Duplicate paths are specified
> +         * A name contains a "/"
> +    """
> +    image_specs = (spec.rsplit(":", 1) for spec in image_specs)
> +    path_name_pairs = ((image_spec[0], image_spec[1].strip() if
> len(image_spec) == 2 else None) for image_spec in image_specs)
> +
> +    paths_seen = []
> +    names_seen = []
> +    for (path, name) in path_name_pairs:
> +        if path == "":
> +            raise ValueError("empty path specified for image file")
> +        path = os.path.abspath(path)
> +        if not os.path.exists(path):
> +            raise ValueError("non-existant path %s specified for image file"
> % path)
> +        if os.path.isdir(path):
> +            raise ValueError("directory path %s specified for image file" %
> path)
> +        if path in paths_seen:
> +            raise ValueError("path %s specified twice for image file" %
> path)
> +        paths_seen.append(path)
> +
> +        if name and "/" in name:
> +            raise ValueError("improperly formatted image file name %s,
> includes slashes" % name)
> +
> +        if not name:
> +            name = os.path.splitext(os.path.basename(path))[0]
> +
> +        if name in names_seen:
> +            names = ("%s_%d" % (name, n) for n in itertools.count())
> +            name = itertools.dropwhile(lambda n: n in names_seen,
> names).next()
> +        names_seen.append(name)
> +
> +        yield name, path
> +
> +class HelpTextParser(object):
> +    """Class to parse help text from file and make it available to option
> +       parser.
> +    """
> +
> +    def __init__(self, path):
> +        """ Initializer
> +            :param path: The absolute path to the help text file
> +        """
> +        if not os.path.isabs(path):
> +            raise ValueError("path %s is not an absolute path" % path)
> +        self._path = path
> +
> +        self._help_text = None
> +
> +    def read(self, lines):
> +        """Reads option, help text pairs from a text file.
> +
> +           Each pair is separated from the next by an empty line.
> +           The option comes first, followed by any number of lines of help
> text.
> +
> +           :param lines: a sequence of lines of text
> +        """
> +        if not lines:
> +            return
> +        expect_option = True
> +        option = None
> +        text = []
> +        for line in (line.strip() for line in lines):
> +            if line == "":
> +                expect_option = True
> +            elif expect_option:
> +                if option:
> +                    yield option, " ".join(text)
> +                option = line
> +                text = []
> +                expect_option = False
> +            else:
> +                text.append(line)
> +        yield option, " ".join(text)
> +
> +    def help_text(self, option):
> +        """
> +        Returns the help text corresponding to the given command-line
> option.
> +        If no help text is available, returns the empty string.
> +
> +        :param str option: The name of the option
> +
> +        :rtype: str
> +        """
> +        if self._help_text is None:
> +            self._help_text = {}
> +            try:
> +                with open(self._path) as lines:
> +                    for parsed_option, parsed_text in self.read(lines):
> +                        self._help_text[parsed_option] = parsed_text
> +            except StandardError:
> +                log.error("error reading help text file %s", self._path)
> +
> +        return self._help_text.get(option, "")
> diff --git a/pyanaconda/anaconda_optparse.py
> b/pyanaconda/anaconda_optparse.py
> deleted file mode 100644
> index 7fedf3e..0000000
> --- a/pyanaconda/anaconda_optparse.py
> +++ /dev/null
> @@ -1,250 +0,0 @@
> -#
> -# anaconda_optparse.py: option parsing for anaconda (CLI and boot args)
> -#
> -# Copyright (C) 2012 Red Hat, Inc.  All rights reserved.
> -#
> -# This program is free software; you can redistribute it and/or modify
> -# it under the terms of the GNU General Public License as published by
> -# the Free Software Foundation; either version 2 of the License, or
> -# (at your option) any later version.
> -#
> -# This program is distributed in the hope that it will be useful,
> -# but WITHOUT ANY WARRANTY; without even the implied warranty of
> -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> -# GNU General Public License for more details.
> -#
> -# You should have received a copy of the GNU General Public License
> -# along with this program.  If not, see <http://www.gnu.org/licenses/>.
> -#
> -# Authors:
> -#   Will Woods <wwoods at redhat.com>
> -
> -import itertools
> -import os
> -
> -from optparse import OptionParser, OptionConflictError
> -
> -from pyanaconda.flags import BootArgs
> -
> -import logging
> -log = logging.getLogger("anaconda")
> -
> -class AnacondaOptionParser(OptionParser):
> -    """
> -    Subclass of OptionParser that also examines boot arguments.
> -
> -    If the "bootarg_prefix" keyword argument is set, it's assumed that all
> -    bootargs will start with that prefix.
> -
> -    "require_prefix" is a bool:
> -        False: accept the argument with or without the prefix.
> -        True: ignore the argument without the prefix. (default)
> -    """
> -    def __init__(self, *args, **kwargs):
> -        self._boot_arg = dict()
> -        self.deprecated_bootargs = []
> -        self.bootarg_prefix = kwargs.pop("bootarg_prefix","")
> -        self.require_prefix = kwargs.pop("require_prefix",True)
> -        OptionParser.__init__(self, *args, **kwargs)
> -
> -    def add_option(self, *args, **kwargs):
> -        """
> -        Add a new option - like OptionParser.add_option.
> -
> -        The long options will be added to the list of boot args, unless
> -        the keyword argument 'bootarg' is set to False.
> -
> -        Positional arguments that don't start with '-' are considered extra
> -        boot args to look for.
> -
> -        NOTE: conflict_handler is currently ignored for boot args - they
> will
> -        always raise OptionConflictError if they conflict.
> -        """
> -        # TODO: add kwargs to make an option commandline-only or
> boot-arg-only
> -        flags = [a for a in args if a.startswith('-')]
> -        bootargs = [a for a in args if not a.startswith('-')]
> -        do_bootarg = kwargs.pop("bootarg", True)
> -        option = OptionParser.add_option(self, *flags, **kwargs)
> -        bootargs += (flag[2:] for flag in option._long_opts)
> -        if do_bootarg:
> -            for b in bootargs:
> -                if b in self._boot_arg:
> -                    raise OptionConflictError(
> -                          "conflicting bootopt string: %s" % b, option)
> -                else:
> -                    self._boot_arg[b] = option
> -        return option
> -
> -    def _get_bootarg_option(self, arg):
> -        """Find the correct Option for a given bootarg (if one exists)"""
> -        if self.bootarg_prefix and arg.startswith(self.bootarg_prefix):
> -            prefixed_option = True
> -            arg = arg[len(self.bootarg_prefix):]
> -        else:
> -            prefixed_option = False
> -        option = self._boot_arg.get(arg)
> -
> -        if self.require_prefix and not prefixed_option:
> -            return None
> -        if option and self.bootarg_prefix and not prefixed_option:
> -            self.deprecated_bootargs.append(arg)
> -        return option
> -
> -    def parse_boot_cmdline(self, cmdline, values=None):
> -        """
> -        Parse the boot cmdline and set appropriate values according to
> -        the options set by add_option.
> -
> -        cmdline can be given as a string (to be parsed by BootArgs), or a
> -        dict (or any object with .iteritems()) of {bootarg:value} pairs.
> -
> -        If cmdline is None, the cmdline data will be whatever BootArgs reads
> -        by default (/proc/cmdline, /run/initramfs/etc/cmdline,
> /etc/cmdline).
> -
> -        If an option requires a value but the boot arg doesn't provide one,
> -        we'll quietly not set anything.
> -        """
> -        if cmdline is None or type(cmdline) is str:
> -            bootargs = BootArgs(cmdline)
> -        else:
> -            bootargs = cmdline
> -        self.deprecated_bootargs = []
> -        for arg, val in bootargs.iteritems():
> -            option = self._get_bootarg_option(arg)
> -            if option is None:
> -                continue
> -            if option.takes_value() and val is None:
> -                continue # TODO: emit a warning or something there?
> -            if option.action == "store_true" and val in ("0", "no", "off"):
> -                # special case: "mpath=0" would otherwise set mpath to True
> -                setattr(values, option.dest, False)
> -                continue
> -            option.process(arg, val, values, self)
> -        return values
> -
> -    # pylint: disable=arguments-differ
> -    def parse_args(self, args=None, values=None, cmdline=None):
> -        """
> -        Like OptionParser.parse_args(), but also parses the boot cmdline.
> -        (see parse_boot_cmdline for details on that process.)
> -        Commandline arguments will override boot arguments.
> -        """
> -        if values is None:
> -            values = self.get_default_values()
> -        v = self.parse_boot_cmdline(cmdline, values)
> -        return OptionParser.parse_args(self, args, v)
> -
> -def name_path_pairs(image_specs):
> -    """Processes and verifies image file specifications. Generates pairs
> -       of names and paths.
> -
> -       :param image_specs: a list of image specifications
> -       :type image_specs: list of str
> -
> -       Each image spec in image_specs has format <path>[:<name>] where
> -       <path> is the path to a local file and <name> is an optional
> -       name used to identify the disk in UI. <name> may not contain colons
> -       or slashes.
> -
> -       If no name given in specification, synthesizes name from basename
> -       of path. Since two distinct paths may have the same basename, handles
> -       name collisions by synthesizing a different name for the colliding
> -       name.
> -
> -       Raises an exception if:
> -         * A path is empty
> -         * A path specifies a non-existant file
> -         * A path specifies a directory
> -         * Duplicate paths are specified
> -         * A name contains a "/"
> -    """
> -    image_specs = (spec.rsplit(":", 1) for spec in image_specs)
> -    path_name_pairs = ((image_spec[0], image_spec[1].strip() if
> len(image_spec) == 2 else None) for image_spec in image_specs)
> -
> -    paths_seen = []
> -    names_seen = []
> -    for (path, name) in path_name_pairs:
> -        if path == "":
> -            raise ValueError("empty path specified for image file")
> -        path = os.path.abspath(path)
> -        if not os.path.exists(path):
> -            raise ValueError("non-existant path %s specified for image file"
> % path)
> -        if os.path.isdir(path):
> -            raise ValueError("directory path %s specified for image file" %
> path)
> -        if path in paths_seen:
> -            raise ValueError("path %s specified twice for image file" %
> path)
> -        paths_seen.append(path)
> -
> -        if name and "/" in name:
> -            raise ValueError("improperly formatted image file name %s,
> includes slashes" % name)
> -
> -        if not name:
> -            name = os.path.splitext(os.path.basename(path))[0]
> -
> -        if name in names_seen:
> -            names = ("%s_%d" % (name, n) for n in itertools.count())
> -            name = itertools.dropwhile(lambda n: n in names_seen,
> names).next()
> -        names_seen.append(name)
> -
> -        yield name, path
> -
> -class HelpTextParser(object):
> -    """Class to parse help text from file and make it available to option
> -       parser.
> -    """
> -
> -    def __init__(self, path):
> -        """ Initializer
> -            :param path: The absolute path to the help text file
> -        """
> -        if not os.path.isabs(path):
> -            raise ValueError("path %s is not an absolute path" % path)
> -        self._path = path
> -
> -        self._help_text = None
> -
> -    def read(self, lines):
> -        """Reads option, help text pairs from a text file.
> -
> -           Each pair is separated from the next by an empty line.
> -           The option comes first, followed by any number of lines of help
> text.
> -
> -           :param lines: a sequence of lines of text
> -        """
> -        if not lines:
> -            return
> -        expect_option = True
> -        option = None
> -        text = []
> -        for line in (line.strip() for line in lines):
> -            if line == "":
> -                expect_option = True
> -            elif expect_option:
> -                if option:
> -                    yield option, " ".join(text)
> -                option = line
> -                text = []
> -                expect_option = False
> -            else:
> -                text.append(line)
> -        yield option, " ".join(text)
> -
> -    def help_text(self, option):
> -        """
> -        Returns the help text corresponding to the given command-line
> option.
> -        If no help text is available, returns the empty string.
> -
> -        :param str option: The name of the option
> -
> -        :rtype: str
> -        """
> -        if self._help_text is None:
> -            self._help_text = {}
> -            try:
> -                with open(self._path) as lines:
> -                    for parsed_option, parsed_text in self.read(lines):
> -                        self._help_text[parsed_option] = parsed_text
> -            except StandardError:
> -                log.error("error reading help text file %s", self._path)
> -
> -        return self._help_text.get(option, "")
> --
> 1.9.0
> 
> _______________________________________________
> anaconda-patches mailing list
> anaconda-patches at lists.fedorahosted.org
> https://lists.fedorahosted.org/mailman/listinfo/anaconda-patches
> 

Trivial typo in the commit message, "developer" is wrong.

- mulhern


More information about the anaconda-patches mailing list