[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