summaryrefslogtreecommitdiffstats
path: root/WebKitTools/Scripts/webkitpy/style/optparser.py
diff options
context:
space:
mode:
Diffstat (limited to 'WebKitTools/Scripts/webkitpy/style/optparser.py')
-rw-r--r--WebKitTools/Scripts/webkitpy/style/optparser.py399
1 files changed, 216 insertions, 183 deletions
diff --git a/WebKitTools/Scripts/webkitpy/style/optparser.py b/WebKitTools/Scripts/webkitpy/style/optparser.py
index 4137c8b..576c16a 100644
--- a/WebKitTools/Scripts/webkitpy/style/optparser.py
+++ b/WebKitTools/Scripts/webkitpy/style/optparser.py
@@ -22,84 +22,83 @@
"""Supports the parsing of command-line options for check-webkit-style."""
-import getopt
+import logging
+from optparse import OptionParser
import os.path
import sys
from filter import validate_filter_rules
# This module should not import anything from checker.py.
+_log = logging.getLogger(__name__)
-def _create_usage(default_options):
- """Return the usage string to display for command help.
+_USAGE = """usage: %prog [--help] [options] [path1] [path2] ...
- Args:
- default_options: A DefaultCommandOptionValues instance.
+Overview:
+ Check coding style according to WebKit style guidelines:
- """
- usage = """
-Syntax: %(program_name)s [--verbose=#] [--git-commit=<SingleCommit>] [--output=vs7]
- [--filter=-x,+y,...] [file] ...
-
- The style guidelines this tries to follow are here:
- http://webkit.org/coding/coding-style.html
+ http://webkit.org/coding/coding-style.html
- Every style error is given a confidence score from 1-5, with 5 meaning
- we are certain of the problem, and 1 meaning it could be a legitimate
- construct. This can miss some errors and does not substitute for
- code review.
+ Path arguments can be files and directories. If neither a git commit nor
+ paths are passed, then all changes in your source control working directory
+ are checked.
- To prevent specific lines from being linted, add a '// NOLINT' comment to the
- end of the line.
+Style errors:
+ This script assigns to every style error a confidence score from 1-5 and
+ a category name. A confidence score of 5 means the error is certainly
+ a problem, and 1 means it could be fine.
- Linted extensions are .cpp, .c and .h. Other file types are ignored.
+ Category names appear in error messages in brackets, for example
+ [whitespace/indent]. See the options section below for an option that
+ displays all available categories and which are reported by default.
- The file parameter is optional and accepts multiple files. Leaving
- out the file parameter applies the check to all files considered changed
- by your source control management system.
+Filters:
+ Use filters to configure what errors to report. Filters are specified using
+ a comma-separated list of boolean filter rules. The script reports errors
+ in a category if the category passes the filter, as described below.
- Flags:
+ All categories start out passing. Boolean filter rules are then evaluated
+ from left to right, with later rules taking precedence. For example, the
+ rule "+foo" passes any category that starts with "foo", and "-foo" fails
+ any such category. The filter input "-whitespace,+whitespace/braces" fails
+ the category "whitespace/tab" and passes "whitespace/braces".
- verbose=#
- A number 1-5 that restricts output to errors with a confidence
- score at or above this value. In particular, the value 1 displays
- all errors. The default is %(default_verbosity)s.
+ Examples: --filter=-whitespace,+whitespace/braces
+ --filter=-whitespace,-runtime/printf,+runtime/printf_format
+ --filter=-,+build/include_what_you_use
- git-commit=<SingleCommit>
- Checks the style of everything from the given commit to the local tree.
+Paths:
+ Certain style-checking behavior depends on the paths relative to
+ the WebKit source root of the files being checked. For example,
+ certain types of errors may be handled differently for files in
+ WebKit/gtk/webkit/ (e.g. by suppressing "readability/naming" errors
+ for files in this directory).
- output=vs7
- The output format, which may be one of
- emacs : to ease emacs parsing
- vs7 : compatible with Visual Studio
- Defaults to "%(default_output_format)s". Other formats are unsupported.
+ Consequently, if the path relative to the source root cannot be
+ determined for a file being checked, then style checking may not
+ work correctly for that file. This can occur, for example, if no
+ WebKit checkout can be found, or if the source root can be detected,
+ but one of the files being checked lies outside the source tree.
- filter=-x,+y,...
- A comma-separated list of boolean filter rules used to filter
- which categories of style guidelines to check. The script checks
- a category if the category passes the filter rules, as follows.
+ If a WebKit checkout can be detected and all files being checked
+ are in the source tree, then all paths will automatically be
+ converted to paths relative to the source root prior to checking.
+ This is also useful for display purposes.
- Any webkit category starts out passing. All filter rules are then
- evaluated left to right, with later rules taking precedence. For
- example, the rule "+foo" passes any category that starts with "foo",
- and "-foo" fails any such category. The filter input "-whitespace,
- +whitespace/braces" fails the category "whitespace/tab" and passes
- "whitespace/braces".
+ Currently, this command can detect the source root only if the
+ command is run from within a WebKit checkout (i.e. if the current
+ working directory is below the root of a checkout). In particular,
+ it is not recommended to run this script from a directory outside
+ a checkout.
- Examples: --filter=-whitespace,+whitespace/braces
- --filter=-whitespace,-runtime/printf,+runtime/printf_format
- --filter=-,+build/include_what_you_use
+ Running this script from a top-level WebKit source directory and
+ checking only files in the source tree will ensure that all style
+ checking behaves correctly -- whether or not a checkout can be
+ detected. This is because all file paths will already be relative
+ to the source root and so will not need to be converted."""
- Category names appear in error messages in brackets, for example
- [whitespace/indent]. To see a list of all categories available to
- %(program_name)s, along with which are enabled by default, pass
- the empty filter as follows:
- --filter=
-""" % {'program_name': os.path.basename(sys.argv[0]),
- 'default_verbosity': default_options.verbosity,
- 'default_output_format': default_options.output_format}
-
- return usage
+_EPILOG = ("This script can miss errors and does not substitute for "
+ "code review.")
# This class should not have knowledge of the flag key names.
@@ -109,26 +108,22 @@ class DefaultCommandOptionValues(object):
Attributes:
output_format: A string that is the default output format.
- verbosity: An integer that is the default verbosity level.
+ min_confidence: An integer that is the default minimum confidence level.
"""
- def __init__(self, output_format, verbosity):
+ def __init__(self, min_confidence, output_format):
+ self.min_confidence = min_confidence
self.output_format = output_format
- self.verbosity = verbosity
-# FIXME: Eliminate support for "extra_flag_values".
-#
# This class should not have knowledge of the flag key names.
class CommandOptionValues(object):
"""Stores the option values passed by the user via the command line.
Attributes:
- extra_flag_values: A string-string dictionary of all flag key-value
- pairs that are not otherwise represented by this
- class. The default is the empty dictionary.
+ is_verbose: A boolean value of whether verbose logging is enabled.
filter_rules: The list of filter rules provided by the user.
These rules are appended to the base rules and
@@ -138,54 +133,52 @@ class CommandOptionValues(object):
git_commit: A string representing the git commit to check.
The default is None.
+ min_confidence: An integer between 1 and 5 inclusive that is the
+ minimum confidence level of style errors to report.
+ The default is 1, which reports all errors.
+
output_format: A string that is the output format. The supported
output formats are "emacs" which emacs can parse
and "vs7" which Microsoft Visual Studio 7 can parse.
- verbosity: An integer between 1-5 inclusive that restricts output
- to errors with a confidence score at or above this value.
- The default is 1, which reports all errors.
-
"""
def __init__(self,
- extra_flag_values=None,
filter_rules=None,
git_commit=None,
- output_format="emacs",
- verbosity=1):
- if extra_flag_values is None:
- extra_flag_values = {}
+ is_verbose=False,
+ min_confidence=1,
+ output_format="emacs"):
if filter_rules is None:
filter_rules = []
+ if (min_confidence < 1) or (min_confidence > 5):
+ raise ValueError('Invalid "min_confidence" parameter: value '
+ "must be an integer between 1 and 5 inclusive. "
+ 'Value given: "%s".' % min_confidence)
+
if output_format not in ("emacs", "vs7"):
raise ValueError('Invalid "output_format" parameter: '
'value must be "emacs" or "vs7". '
'Value given: "%s".' % output_format)
- if (verbosity < 1) or (verbosity > 5):
- raise ValueError('Invalid "verbosity" parameter: '
- "value must be an integer between 1-5 inclusive. "
- 'Value given: "%s".' % verbosity)
-
- self.extra_flag_values = extra_flag_values
self.filter_rules = filter_rules
self.git_commit = git_commit
+ self.is_verbose = is_verbose
+ self.min_confidence = min_confidence
self.output_format = output_format
- self.verbosity = verbosity
# Useful for unit testing.
def __eq__(self, other):
"""Return whether this instance is equal to another."""
- if self.extra_flag_values != other.extra_flag_values:
- return False
if self.filter_rules != other.filter_rules:
return False
if self.git_commit != other.git_commit:
return False
- if self.output_format != other.output_format:
+ if self.is_verbose != other.is_verbose:
return False
- if self.verbosity != other.verbosity:
+ if self.min_confidence != other.min_confidence:
+ return False
+ if self.output_format != other.output_format:
return False
return True
@@ -212,10 +205,9 @@ class ArgumentPrinter(object):
options: A CommandOptionValues instance.
"""
- flags = options.extra_flag_values.copy()
-
+ flags = {}
+ flags['min-confidence'] = options.min_confidence
flags['output'] = options.output_format
- flags['verbose'] = options.verbosity
# Only include the filter flag if user-provided rules are present.
filter_rules = options.filter_rules
if filter_rules:
@@ -231,7 +223,6 @@ class ArgumentPrinter(object):
return flag_string.strip()
-# FIXME: Replace the use of getopt.getopt() with optparse.OptionParser.
class ArgumentParser(object):
# FIXME: Move the documentation of the attributes to the __init__
@@ -256,8 +247,8 @@ class ArgumentParser(object):
all_categories,
default_options,
base_filter_rules=None,
- create_usage=None,
- stderr_write=None):
+ mock_stderr=None,
+ usage=None):
"""Create an ArgumentParser instance.
Args:
@@ -279,31 +270,96 @@ class ArgumentParser(object):
"""
if base_filter_rules is None:
base_filter_rules = []
- if create_usage is None:
- create_usage = _create_usage
- if stderr_write is None:
- stderr_write = sys.stderr.write
+ stderr = sys.stderr if mock_stderr is None else mock_stderr
+ if usage is None:
+ usage = _USAGE
self._all_categories = all_categories
self._base_filter_rules = base_filter_rules
+
# FIXME: Rename these to reflect that they are internal.
- self.create_usage = create_usage
self.default_options = default_options
- self.stderr_write = stderr_write
-
- def _exit_with_usage(self, error_message=''):
- """Exit and print a usage string with an optional error message.
-
- Args:
- error_message: A string that is an error message to print.
-
- """
- usage = self.create_usage(self.default_options)
- self.stderr_write(usage)
+ self.stderr_write = stderr.write
+
+ self._parser = self._create_option_parser(stderr=stderr,
+ usage=usage,
+ default_min_confidence=self.default_options.min_confidence,
+ default_output_format=self.default_options.output_format)
+
+ def _create_option_parser(self, stderr, usage,
+ default_min_confidence, default_output_format):
+ # Since the epilog string is short, it is not necessary to replace
+ # the epilog string with a mock epilog string when testing.
+ # For this reason, we use _EPILOG directly rather than passing it
+ # as an argument like we do for the usage string.
+ parser = OptionParser(usage=usage, epilog=_EPILOG)
+
+ filter_help = ('set a filter to control what categories of style '
+ 'errors to report. Specify a filter using a comma-'
+ 'delimited list of boolean filter rules, for example '
+ '"--filter -whitespace,+whitespace/braces". To display '
+ 'all categories and which are enabled by default, pass '
+ """no value (e.g. '-f ""' or '--filter=').""")
+ parser.add_option("-f", "--filter-rules", metavar="RULES",
+ dest="filter_value", help=filter_help)
+
+ git_help = "check all changes after the given git commit."
+ parser.add_option("-g", "--git-commit", "--git-diff", "--git-since",
+ metavar="COMMIT", dest="git_since", help=git_help,)
+
+ min_confidence_help = ("set the minimum confidence of style errors "
+ "to report. Can be an integer 1-5, with 1 "
+ "displaying all errors. Defaults to %default.")
+ parser.add_option("-m", "--min-confidence", metavar="INT",
+ type="int", dest="min_confidence",
+ default=default_min_confidence,
+ help=min_confidence_help)
+
+ output_format_help = ('set the output format, which can be "emacs" '
+ 'or "vs7" (for Visual Studio). '
+ 'Defaults to "%default".')
+ parser.add_option("-o", "--output-format", metavar="FORMAT",
+ choices=["emacs", "vs7"],
+ dest="output_format", default=default_output_format,
+ help=output_format_help)
+
+ verbose_help = "enable verbose logging."
+ parser.add_option("-v", "--verbose", dest="is_verbose", default=False,
+ action="store_true", help=verbose_help)
+
+ # Override OptionParser's error() method so that option help will
+ # also display when an error occurs. Normally, just the usage
+ # string displays and not option help.
+ parser.error = self._parse_error
+
+ # Override OptionParser's print_help() method so that help output
+ # does not render to the screen while running unit tests.
+ print_help = parser.print_help
+ parser.print_help = lambda: print_help(file=stderr)
+
+ return parser
+
+ def _parse_error(self, error_message):
+ """Print the help string and an error message, and exit."""
+ # The method format_help() includes both the usage string and
+ # the flag options.
+ help = self._parser.format_help()
+ # Separate help from the error message with a single blank line.
+ self.stderr_write(help + "\n")
if error_message:
- sys.exit('\nFATAL ERROR: ' + error_message)
- else:
- sys.exit(1)
+ _log.error(error_message)
+
+ # Since we are using this method to replace/override the Python
+ # module optparse's OptionParser.error() method, we match its
+ # behavior and exit with status code 2.
+ #
+ # As additional background, Python documentation says--
+ #
+ # "Unix programs generally use 2 for command line syntax errors
+ # and 1 for all other kind of errors."
+ #
+ # (from http://docs.python.org/library/sys.html#sys.exit )
+ sys.exit(2)
def _exit_with_categories(self):
"""Exit and print the style categories and default filter rules."""
@@ -335,90 +391,67 @@ class ArgumentParser(object):
filters.append(filter)
return filters
- def parse(self, args, extra_flags=None):
+ def parse(self, args):
"""Parse the command line arguments to check-webkit-style.
Args:
args: A list of command-line arguments as returned by sys.argv[1:].
- extra_flags: A list of flags whose values we want to extract, but
- are not supported by the CommandOptionValues class.
- An example flag "new_flag=". This defaults to the
- empty list.
Returns:
- A tuple of (filenames, options)
+ A tuple of (paths, options)
- filenames: The list of filenames to check.
+ paths: The list of paths to check.
options: A CommandOptionValues instance.
"""
- if extra_flags is None:
- extra_flags = []
-
- output_format = self.default_options.output_format
- verbosity = self.default_options.verbosity
+ (options, paths) = self._parser.parse_args(args=args)
+
+ filter_value = options.filter_value
+ git_commit = options.git_since
+ is_verbose = options.is_verbose
+ min_confidence = options.min_confidence
+ output_format = options.output_format
+
+ if filter_value is not None and not filter_value:
+ # Then the user explicitly passed no filter, for
+ # example "-f ''" or "--filter=".
+ self._exit_with_categories()
+
+ # Validate user-provided values.
+
+ if paths and git_commit:
+ self._parse_error('You cannot provide both paths and a git '
+ 'commit at the same time.')
+
+ # FIXME: Add unit tests.
+ if git_commit and '..' in git_commit:
+ # FIXME: If the range is a "...", the code should find the common
+ # ancestor and start there. See git diff --help for how
+ # "..." usually works.
+ self._parse_error('invalid --git-commit option: option does '
+ 'not support ranges "..": %s' % git_commit)
+
+ min_confidence = int(min_confidence)
+ if (min_confidence < 1) or (min_confidence > 5):
+ self._parse_error('option --min-confidence: invalid integer: '
+ '%s: value must be between 1 and 5'
+ % min_confidence)
+
+ if filter_value:
+ filter_rules = self._parse_filter_flag(filter_value)
+ else:
+ filter_rules = []
- # The flags already supported by the CommandOptionValues class.
- flags = ['help', 'output=', 'verbose=', 'filter=', 'git-commit=']
+ try:
+ validate_filter_rules(filter_rules, self._all_categories)
+ except ValueError, err:
+ self._parse_error(err)
- for extra_flag in extra_flags:
- if extra_flag in flags:
- raise ValueError('Flag \'%(extra_flag)s is duplicated '
- 'or already supported.' %
- {'extra_flag': extra_flag})
- flags.append(extra_flag)
+ options = CommandOptionValues(filter_rules=filter_rules,
+ git_commit=git_commit,
+ is_verbose=is_verbose,
+ min_confidence=min_confidence,
+ output_format=output_format)
- try:
- (opts, filenames) = getopt.getopt(args, '', flags)
- except getopt.GetoptError:
- # FIXME: Settle on an error handling approach: come up
- # with a consistent guideline as to when and whether
- # a ValueError should be raised versus calling
- # sys.exit when needing to interrupt execution.
- self._exit_with_usage('Invalid arguments.')
-
- extra_flag_values = {}
- git_commit = None
- filter_rules = []
-
- for (opt, val) in opts:
- if opt == '--help':
- self._exit_with_usage()
- elif opt == '--output':
- output_format = val
- elif opt == '--verbose':
- verbosity = val
- elif opt == '--git-commit':
- git_commit = val
- elif opt == '--filter':
- if not val:
- self._exit_with_categories()
- filter_rules = self._parse_filter_flag(val)
- else:
- extra_flag_values[opt] = val
-
- # Check validity of resulting values.
- if filenames and (git_commit != None):
- self._exit_with_usage('It is not possible to check files and a '
- 'specific commit at the same time.')
-
- if output_format not in ('emacs', 'vs7'):
- raise ValueError('Invalid --output value "%s": The only '
- 'allowed output formats are emacs and vs7.' %
- output_format)
-
- validate_filter_rules(filter_rules, self._all_categories)
-
- verbosity = int(verbosity)
- if (verbosity < 1) or (verbosity > 5):
- raise ValueError('Invalid --verbose value %s: value must '
- 'be between 1-5.' % verbosity)
-
- options = CommandOptionValues(extra_flag_values=extra_flag_values,
- filter_rules=filter_rules,
- git_commit=git_commit,
- output_format=output_format,
- verbosity=verbosity)
-
- return (filenames, options)
+ return (paths, options)