Merge lp:~adiroiban/pocket-lint/plugin-system into lp:pocket-lint

Proposed by Adi Roiban
Status: Needs review
Proposed branch: lp:~adiroiban/pocket-lint/plugin-system
Merge into: lp:pocket-lint
Diff against target: 308 lines (+155/-58)
2 files modified
pocketlint/contrib/pep257_plugin.py (+97/-0)
pocketlint/formatcheck.py (+58/-58)
To merge this branch: bzr merge lp:~adiroiban/pocket-lint/plugin-system
Reviewer Review Type Date Requested Status
Curtis Hovey Pending
Review via email: mp+215554@code.launchpad.net

Description of the change

Description
-----------

I want to have google-js-linter, pep257 , some custom regex checkers, css/LESS/SASS checker, HTML5 etc... then there is jshint/jslint ... maybe someone want pylint :)

I think that is hard to ship all this checkers with pocket lint.

One option is to let pocket-link define a framework for checkers and then users can add custom checkers as plugins.

Each plugin has its own configuration and can also use global configuration (once we have a well defined configuration object)

Changes
-------

As a drive-by fix I have updated PocektLintOption to not have the dirty hack for filling values from command line options.

PocketLintOption now have the addPlugin method... there is no method to remove plugins :)

I have defined a base Pluging which can register itself for a list of languages. We might want a checker for all files... ex line lenght like checker or tab checker or traling spaces checker

check is the entry paint and a helper for calling the plugin for multiple files.

check_all is the main method which should be defined by plugins.

I am also thinking at check_line to remove the need for plugins to split the file if they only want to do line checks.

I added `enabled` attribute, but I am not sure if it make sense... From an 3rd party user, if plugin is not enable, just don't add it. I am thinking that maybe we want to use the plugin system for own checker... like jshint.

Please let me know what do you think.\

Does it make sense? Do you find it useful? Is the API ok?

Thanks!

To post a comment you must log in.

Unmerged revisions

453. By Adi Roiban <email address hidden>

Initial code for plugin system.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'pocketlint/contrib/pep257_plugin.py'
--- pocketlint/contrib/pep257_plugin.py 1970-01-01 00:00:00 +0000
+++ pocketlint/contrib/pep257_plugin.py 2014-04-12 19:27:30 +0000
@@ -0,0 +1,97 @@
1import os
2
3try:
4 import pep257
5 # Shut up the linter.
6 pep257
7except ImportError:
8 pep257 = None
9from pocketlint.formatcheck import Language
10
11
12class Plugin(object):
13 """
14 Base class for implementing pocket-lint plugins.
15
16 You will need to define `check_all` if your linter needs whole content
17 or `check_line` if you will check each line.
18
19 Use `message` to add a message to reporter.
20 """
21 #: List of languages which can be checked using this plugin.
22 languages = [Language.TEXT]
23 #: Whether this plugin is enabled.
24 enabled = True
25 #: Path to current file that is checked.
26 file_path = None
27 #: Global options used by pocket-lint
28 global_options = None
29
30 def check(self, language, file_path, text, reporter, options):
31 """
32 Check code using this plugin.
33 """
34 if not self.enabled:
35 return
36
37 if language not in self.languages:
38 return
39
40 self._reporter = reporter
41 self.global_options = options
42 self.file_path = file_path
43
44 self.check_all(text)
45
46 for line_no, line in enumerate(text.splitlines()):
47 self.check_line(line, line_no + 1)
48
49 def check_all(self, text):
50 """
51 Check whole file content.
52 """
53 pass
54
55 def check_line(self, line, line_no):
56 """
57 Check single line.
58 """
59
60 def message(self, line_no, message, icon=None):
61 """
62 Add a message to reporter.
63 """
64 self._reporter(
65 line_no,
66 message,
67 icon=icon,
68 base_dir=os.path.dirname(self.file_path),
69 file_name=self.file_path,
70 )
71
72
73class PEP257Plugin(Plugin):
74 """
75 A plugin to check Python docstrings for PEP257.
76 """
77
78 languages = [Language.PYTHON]
79
80 _default_options = {
81 'ignore': ['D203', 'D204', 'D401'],
82 }
83
84 def __init__(self, options=None):
85 if not options:
86 options = self._default_options.copy()
87 self._options = options
88 self.enabled = pep257 != None
89 self._checker = pep257.PEP257Checker()
90
91 def check_all(self, text):
92 """Pass all content to pep257 module."""
93 results = self._checker.check_source(text, self.file_path)
94 for error in results:
95 if error.code in self._options['ignore']:
96 continue
97 self.message(error.line, error.message, icon='error')
098
=== modified file 'pocketlint/formatcheck.py'
--- pocketlint/formatcheck.py 2014-03-09 21:04:28 +0000
+++ pocketlint/formatcheck.py 2014-04-12 19:27:30 +0000
@@ -83,14 +83,6 @@
83except ImportError:83except ImportError:
84 closure_linter = None84 closure_linter = None
8585
86try:
87 import pep257
88 # Shut up the linter.
89 pep257
90except ImportError:
91 pep257 = None
92
93
94IS_PY3 = True if sys.version_info >= (3,) else False86IS_PY3 = True if sys.version_info >= (3,) else False
9587
9688
@@ -262,10 +254,14 @@
262254
263255
264class PocketLintOptions(object):256class PocketLintOptions(object):
265 """Default options used by pocketlint"""257 """Default options used by pocketlint."""
266258
267 def __init__(self, command_options=None):259 def __init__(self):
268 self.max_line_length = 0260 self.max_line_length = 0
261 self.verbose = False
262 # Docstring options.
263 self.do_format = False
264 self.is_interactive = False
269265
270 self.jslint = {266 self.jslint = {
271 'enabled': True,267 'enabled': True,
@@ -287,19 +283,16 @@
287283
288 self.regex_line = []284 self.regex_line = []
289285
290 if command_options:286 self._plugins = set()
291 self._updateFromCommandLineOptions(command_options)287
292288 @property
293 def _updateFromCommandLineOptions(self, options):289 def plugins(self):
294 """290 """Return a copy of current plugins."""
295 Update with options received from command line.291 return self._plugins.copy()
296 """292
297 # Update maximum line length.293 def addPlugin(self, plugin):
298 self.max_line_length = options.max_line_length294 """Add `plugin`."""
299 self.pep8['max_line_length'] = options.max_line_length - 1295 self._plugins.add(plugin)
300 self.pep8['hang_closing'] = options.hang_closing
301 if hasattr(options, 'regex_line'):
302 self.regex_line = options.regex_line
303296
304297
305class BaseChecker(object):298class BaseChecker(object):
@@ -318,12 +311,9 @@
318 self.text = u(text)311 self.text = u(text)
319 self.set_reporter(reporter=reporter)312 self.set_reporter(reporter=reporter)
320313
321 if options is None:314 if not options:
322 self.options = PocketLintOptions()315 options = PocketLintOptions()
323 elif not isinstance(options, PocketLintOptions):316 self.options = options
324 self.options = PocketLintOptions(command_options=options)
325 else:
326 self.options = options
327317
328 def set_reporter(self, reporter=None):318 def set_reporter(self, reporter=None):
329 """Set the reporter for messages."""319 """Set the reporter for messages."""
@@ -397,6 +387,19 @@
397 self.file_path, self.text, self._reporter, self.options)387 self.file_path, self.text, self._reporter, self.options)
398 checker.check()388 checker.check()
399389
390 self.check_plugins()
391
392 def check_plugins(self):
393 """Checked code with registered plugins."""
394 for plugin in self.options.plugins:
395 plugin.check(
396 self.language,
397 self.file_path,
398 self.text,
399 self._reporter,
400 self.options,
401 )
402
400403
401class AnyTextMixin:404class AnyTextMixin:
402 """Common checks for many checkers."""405 """Common checks for many checkers."""
@@ -512,9 +515,9 @@
512class FastParser(object):515class FastParser(object):
513 """A simple and pure-python parser that checks well-formedness.516 """A simple and pure-python parser that checks well-formedness.
514517
515 This parser works in py 2 and 3. It handles entities and ignores518 This parser works in py 2 and 3. It handles entities and ignores
516 namespaces. This parser works with python ElementTree.519 namespaces. This parser works with python ElementTree.
517 """520 """
518521
519 def __init__(self, html=0, target=None, encoding=None):522 def __init__(self, html=0, target=None, encoding=None):
520 parser = expat.ParserCreate(encoding, None)523 parser = expat.ParserCreate(encoding, None)
@@ -702,7 +705,6 @@
702 self.check_text()705 self.check_text()
703 self.check_flakes()706 self.check_flakes()
704 self.check_pep8()707 self.check_pep8()
705 self.check_pep257()
706 self.check_windows_endlines()708 self.check_windows_endlines()
707709
708 def check_flakes(self):710 def check_flakes(self):
@@ -744,24 +746,6 @@
744 message = "%s: %s" % (message, location[3].strip())746 message = "%s: %s" % (message, location[3].strip())
745 self.message(location[1], message, icon='error')747 self.message(location[1], message, icon='error')
746748
747 def check_pep257(self):
748 """PEP 257 docstring style checker."""
749 if not pep257:
750 # PEP257 is not available.
751 return
752
753 ignore_list = getattr(self.options, 'pep257_ignore', [])
754
755 results = pep257.check_source(self.text, self.file_path)
756
757 for error in results:
758 # PEP257 message contains the short error as first line from
759 # the long docstring explanation.
760 error_message = error.explanation.splitlines()[0]
761 if error_message in ignore_list:
762 continue
763 self.message(error.line, error_message, icon='error')
764
765 def check_text(self):749 def check_text(self):
766 """Call each line_method for each line in text."""750 """Call each line_method for each line in text."""
767 for line_no, line in enumerate(self.text.splitlines()):751 for line_no, line in enumerate(self.text.splitlines()):
@@ -1160,6 +1144,7 @@
11601144
1161 def check_text(self):1145 def check_text(self):
1162 """Call each line_method for each line in text."""1146 """Call each line_method for each line in text."""
1147
1163 for line_no, line in enumerate(self.text.splitlines()):1148 for line_no, line in enumerate(self.text.splitlines()):
1164 line_no += 11149 line_no += 1
1165 self.check_length(line_no, line)1150 self.check_length(line_no, line)
@@ -1168,8 +1153,8 @@
1168 self.check_regex_line(line_no, line)1153 self.check_regex_line(line_no, line)
11691154
11701155
1171def get_option_parser():1156def parse_command_line(args):
1172 """Return the option parser for this program."""1157 """Return a tuple with (options, source) for the command line request."""
1173 usage = "usage: %prog [options] file1 file2"1158 usage = "usage: %prog [options] file1 file2"
1174 parser = OptionParser(usage=usage)1159 parser = OptionParser(usage=usage)
1175 parser.add_option(1160 parser.add_option(
@@ -1197,7 +1182,18 @@
1197 is_interactive=False,1182 is_interactive=False,
1198 max_line_length=DEFAULT_MAX_LENGTH,1183 max_line_length=DEFAULT_MAX_LENGTH,
1199 )1184 )
1200 return parser1185
1186 (command_options, sources) = parser.parse_args(args=args)
1187
1188 # Create options based on parsed command line.
1189 options = PocketLintOptions()
1190 options.verbose = command_options.verbose
1191 options.do_format = command_options.do_format
1192 options.is_interactive = command_options.is_interactive
1193 options.max_line_length = command_options.max_line_length
1194 options.pep8['hang_closing'] = command_options.hang_closing
1195
1196 return (options, sources)
12011197
12021198
1203def check_sources(sources, options, reporter=None):1199def check_sources(sources, options, reporter=None):
@@ -1224,11 +1220,15 @@
1224 """Run the command line operations."""1220 """Run the command line operations."""
1225 if argv is None:1221 if argv is None:
1226 argv = sys.argv1222 argv = sys.argv
1227 parser = get_option_parser()1223 (options, sources) = parse_command_line(args=argv[1:])
1228 (options, sources) = parser.parse_args(args=argv[1:])1224
1229 # Handle standard args.
1230 if len(sources) == 0:1225 if len(sources) == 0:
1231 parser.error("Expected file paths.")1226 sys.stderr.write("Expected file paths.\n")
1227 return 1
1228
1229 from pocketlint.contrib.pep257_plugin import PEP257Plugin
1230
1231 options.addPlugin(PEP257Plugin())
1232 reporter = Reporter(Reporter.CONSOLE)1232 reporter = Reporter(Reporter.CONSOLE)
1233 reporter.error_only = not options.verbose1233 reporter.error_only = not options.verbose
1234 return check_sources(sources, options, reporter)1234 return check_sources(sources, options, reporter)

Subscribers

People subscribed via source and target branches

to all changes: