Bug 1281899 - [mozlint] Create cli module and move logic from tools/lint/mach_commands.py there, r?smacleod draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 24 Jun 2016 14:06:22 -0400
changeset 384336 32d6e8ec7587066c61e2ac10a2143dc02010c1f3
parent 384060 dbb31bcad5a1f60a35b5600ea1578d9b9fa55237
child 384337 3d5b16c87f0948f843961adf0488374a602d4cc3
push id22245
push userahalberstadt@mozilla.com
push dateWed, 06 Jul 2016 03:00:35 +0000
reviewerssmacleod
bugs1281899
milestone50.0a1
Bug 1281899 - [mozlint] Create cli module and move logic from tools/lint/mach_commands.py there, r?smacleod There is currently no built-in user interface to mozlint. The only existing interface is the external cli provided by |mach lint|. However, in the future mozlint may need to be used in a context where mach isn't readily available (i.e version-control-tools). This patch basically just moves the cli logic out of mach_commands.py, and into mozlint core. That way it can be re-used in other places without needing to be re-implemented. The |mach lint setup| subcommand was removed because apparently subcommands don't work with the parser attribute. Nothing was using it yet anyway, so I removed it for now. It may get re-added in some form in the future. MozReview-Commit-ID: aOGp2Yrncs
python/mozlint/mozlint/cli.py
python/mozlint/mozlint/types.py
tools/lint/mach_commands.py
new file mode 100644
--- /dev/null
+++ b/python/mozlint/mozlint/cli.py
@@ -0,0 +1,91 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from __future__ import print_function, unicode_literals
+
+import os
+import subprocess
+import sys
+from argparse import ArgumentParser
+
+
+SEARCH_PATHS = []
+
+
+class MozlintParser(ArgumentParser):
+    arguments = [
+        [['paths'],
+         {'nargs': '*',
+          'default': None,
+          'help': "Paths to file or directories to lint, like "
+                  "'browser/components/loop' or 'mobile/android'. "
+                  "Defaults to the current directory if not given.",
+          }],
+        [['-l', '--linter'],
+         {'dest': 'linters',
+          'default': [],
+          'action': 'append',
+          'help': "Linters to run, e.g 'eslint'. By default all linters "
+                  "are run for all the appropriate files.",
+          }],
+        [['-f', '--format'],
+         {'dest': 'fmt',
+          'default': 'stylish',
+          'help': "Formatter to use. Defaults to 'stylish'.",
+          }],
+        [['-n', '--no-filter'],
+         {'dest': 'use_filters',
+          'default': True,
+          'action': 'store_false',
+          'help': "Ignore all filtering. This is useful for quickly "
+                  "testing a directory that otherwise wouldn't be run, "
+                  "without needing to modify the config file.",
+          }],
+    ]
+
+    def __init__(self, **kwargs):
+        ArgumentParser.__init__(self, usage=self.__doc__, **kwargs)
+
+        for cli, args in self.arguments:
+            self.add_argument(*cli, **args)
+
+
+def find_linters(self, linters=None):
+    lints = []
+    for search_path in SEARCH_PATHS:
+        if not os.path.isdir(search_path):
+            continue
+
+        files = os.listdir(search_path)
+        for f in files:
+            name, ext = os.path.splitext(f)
+            if ext != '.lint':
+                continue
+
+            if linters and name not in linters:
+                continue
+
+            lints.append(os.path.join(search_path, f))
+    return lints
+
+
+def run(paths, linters, fmt, **lintargs):
+    from mozlint import LintRoller, formatters
+    paths = paths or ['.']
+
+    lint = LintRoller(**lintargs)
+    lint.read(find_linters(linters))
+
+    # run all linters
+    results = lint.roll(paths)
+
+    formatter = formatters.get(fmt)
+    print(formatter(results))
+    return 1 if results else 0
+
+
+if __name__ == '__main__':
+    parser = MozlintParser()
+    args = vars(parser.parse_args())
+    sys.exit(run(**args))
--- a/python/mozlint/mozlint/types.py
+++ b/python/mozlint/mozlint/types.py
@@ -27,17 +27,17 @@ class BaseType(object):
         """
         exclude = lintargs.get('exclude', [])
         exclude.extend(linter.get('exclude', []))
 
         if lintargs.get('use_filters', True):
             paths, exclude = filterpaths(paths, linter.get('include'), exclude)
 
         if not paths:
-            print("{}: No files to lint for specified paths!".format(linter['name']))
+            print("{}: no files to lint in specified paths".format(linter['name']))
             return
 
         lintargs['exclude'] = exclude
         if self.batch:
             return self._lint(paths, linter, **lintargs)
 
         errors = []
         try:
--- a/tools/lint/mach_commands.py
+++ b/tools/lint/mach_commands.py
@@ -18,17 +18,16 @@ from mozbuild.base import (
     MachCommandBase,
 )
 
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
-    SubCommand,
 )
 
 
 here = os.path.abspath(os.path.dirname(__file__))
 
 
 ESLINT_PACKAGES = [
     "eslint@2.9.0",
@@ -58,92 +57,34 @@ NPM_NOT_FOUND_MESSAGE = '''
 Node Package Manager (npm) is either not installed or installed to a
 non-standard path. Please install npm from https://nodejs.org (it comes as an
 option in the node installation) and try again.
 
 Valid installation paths:
 '''.strip()
 
 
+def setup_argument_parser():
+    from mozlint import cli
+    return cli.MozlintParser()
+
+
 @CommandProvider
 class MachCommands(MachCommandBase):
 
     @Command(
         'lint', category='devenv',
-        description='Run linters.')
-    @CommandArgument(
-        'paths', nargs='*', default=None,
-        help="Paths to file or directories to lint, like "
-             "'browser/components/loop' or 'mobile/android'. "
-             "Defaults to the current directory if not given.")
-    @CommandArgument(
-        '-l', '--linter', dest='linters', default=None, action='append',
-        help="Linters to run, e.g 'eslint'. By default all linters are run "
-             "for all the appropriate files.")
-    @CommandArgument(
-        '-f', '--format', dest='fmt', default='stylish',
-        help="Formatter to use. Defaults to 'stylish'.")
-    @CommandArgument(
-        '-n', '--no-filter', dest='use_filters', default=True, action='store_false',
-        help="Ignore all filtering. This is useful for quickly testing a "
-             "directory that otherwise wouldn't be run, without needing to "
-             "modify the config file.")
-    def lint(self, paths, linters=None, fmt='stylish', **lintargs):
+        description='Run linters.',
+        parser=setup_argument_parser)
+    def lint(self, paths, linters, fmt, **lintargs):
         """Run linters."""
-        from mozlint import LintRoller, formatters
-
-        paths = paths or ['.']
-
-        lint_files = self.find_linters(linters)
-
+        from mozlint import cli
         lintargs['exclude'] = ['obj*']
-        lint = LintRoller(**lintargs)
-        lint.read(lint_files)
-
-        # run all linters
-        results = lint.roll(paths)
-
-        status = 0
-        if results:
-            status = 1
-
-        formatter = formatters.get(fmt)
-        print(formatter(results))
-        return status
-
-    @SubCommand('lint', 'setup',
-                "Setup required libraries for specified lints.")
-    @CommandArgument(
-        '-l', '--linter', dest='linters', default=None, action='append',
-        help="Linters to run, e.g 'eslint'. By default all linters are run "
-             "for all the appropriate files.")
-    def lint_setup(self, linters=None, **lintargs):
-        from mozlint import LintRoller
-
-        lint_files = self.find_linters(linters)
-        lint = LintRoller(lintargs=lintargs)
-        lint.read(lint_files)
-
-        for l in lint.linters:
-            if 'setup' in l:
-                l['setup']()
-
-    def find_linters(self, linters=None):
-        lints = []
-        files = os.listdir(here)
-        for f in files:
-            name, ext = os.path.splitext(f)
-            if ext != '.lint':
-                continue
-
-            if linters and name not in linters:
-                continue
-
-            lints.append(os.path.join(here, f))
-        return lints
+        cli.SEARCH_PATHS.append(here)
+        return cli.run(paths, linters, fmt, **lintargs)
 
     @Command('eslint', category='devenv',
              description='Run eslint or help configure eslint for optimal development.')
     @CommandArgument('-s', '--setup', default=False, action='store_true',
                      help='configure eslint for optimal development.')
     @CommandArgument('-e', '--ext', default='[.js,.jsm,.jsx,.xml,.html]',
                      help='Filename extensions to lint, default: "[.js,.jsm,.jsx,.xml,.html]".')
     @CommandArgument('-b', '--binary', default=None,