[Idle-dev] IDLE extension to integrate 3rd party checkers and analyzers

Saimadhav Heblikar saimadhavheblikar at gmail.com
Thu Jul 3 19:27:57 CEST 2014


Hi,

I combined your(Terry Reedy) for checking editor buffer, with my
earlier proposal of using PyPI to distribute the adapter files.

The result is:
User has to pip install a checker file(assumes the user has dependency
for the time being, dependency can be forced using setuptools,
currently using distutils, which does not check for dependencies)
The checker gets auto-detected in IDLE.(though this does not enable
the checker within IDLE)
The user can choose to enable it and set additional args(additional
args part not yet implemented, but it is a matter of passing on the
args in the adapter files)
The user can run the checker from "Run" menu as necessary.

I am attaching the diff file.
To install the pyflakes IDLE api,
pip install -i https://testpypi.python.org/pypi IDLEPyflakes

In essence, the user has to just pip install the file. It is disabled
by default for security reasons(?? Is this necessary? Should we enable
it by default?)

Please let me know if I should proceed with this "experiment".


-- 
Regards
Saimadhav Heblikar
-------------- next part --------------
diff -r 8734e881c400 Lib/idlelib/Checker.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Lib/idlelib/Checker.py	Thu Jul 03 22:55:56 2014 +0530
@@ -0,0 +1,188 @@
+import os
+from pkgutil import iter_modules
+from distutils.sysconfig import get_python_lib
+from importlib import import_module
+import tkinter as tk
+import tkinter.messagebox as tkMessageBox
+from idlelib.OutputWindow import OutputWindow
+from idlelib.configHandler import idleConf
+
+def get_checkers():
+    idlecheckers_path = os.path.join(get_python_lib(), 'idlecheckers')
+    checkers = [c[1][12:] for c in list(iter_modules([idlecheckers_path])) if c[1].startswith('idlechecker_')]
+    return checkers
+
+def get_enabled_checkers():
+    return [c for c in get_checkers() if idleConf.GetOption('checker', c,
+        'enabled', type='bool')]
+
+def get_checker_config(checker):
+    if checker == '':
+        raise ValueError('Checker name cannot be empty')
+    config = {}
+    config['enabled'] = idleConf.GetOption('checker', checker, 'enabled',
+            type='bool')
+    config['name'] = checker
+    config['additional'] = idleConf.GetOption('checker', checker,
+            'additional')
+    return config
+
+
+class Checker:
+
+    menudefs = [
+            ('run', [None,
+                    ('Config Checkers', '<<config-checker>>')
+                    ]
+            )
+    ]
+
+    def __init__(self, editwin):
+        self.editwin = editwin
+        self.parent = self.editwin.top
+        self.update_menu()
+
+    def config_checker_event(self, event):
+        dialog = self.dialog = tk.Toplevel(self.parent)
+        dialog.transient(self.parent)
+        dialog.wm_title('Config Checkers')
+        dialog.geometry('+%d+%d' % (self.parent.winfo_rootx() + 30,
+                                    self.parent.winfo_rooty() + 30))
+        titleLabel = tk.Label(dialog, text='Configure Checkers')
+        self.listbox = tk.Listbox(dialog, selectmode=tk.SINGLE)
+        self.update_listbox()
+        editButton = tk.Button(dialog, text='Edit Checker',
+                              command=self.edit_checker)
+        titleLabel.pack()
+        self.listbox.pack()
+        editButton.pack(side=tk.LEFT)
+
+    def update_listbox(self):
+        self.listbox.delete(0, tk.END)
+        checkers = get_checkers()
+        for c in checkers:
+            self.listbox.insert(tk.END, c)
+
+    def _selected_checker(self):
+        index = self.listbox.curselection()
+        if index:
+            checker = self.listbox.get(index)
+            return checker
+        else:
+            return None
+
+    def edit_checker(self):
+        checker = self._selected_checker()
+        if checker:
+            ConfigCheckerDialog(self.editwin, self, checker)
+        else:
+            tkMessageBox.showerror(title='No Checker Selected',
+                                message='Select an existing checker to edit',
+                                parent=self.dialog)
+
+    def update_menu(self):
+        menu = self.editwin.menudict.get('run')
+        start = menu.index('Config Checkers') + 1
+        end = menu.index(tk.END)
+        if start < end:
+            menu.delete(start, end)
+        if start == end:
+            menu.delete(start)
+        for checker in get_enabled_checkers():
+            label = 'Run {}'.format(checker)
+            menu.add_command(label=label,
+                    command=lambda checker=checker:self.run_checker(checker))
+
+    def run_checker(self, checker):
+        """Run the 3rd party checker 'checker' and display result in a new
+        outputwindow"""
+        text = self.editwin.text.get('1.0', 'end')
+        filename = self.editwin.io.filename or 'Untitled'
+        additional_args = get_checker_config(checker)
+        module = import_module('idlecheckers.idlechecker_'+checker)
+        output, error = module.process(text, filename, additional_args)
+        self.show_result(output, error)
+
+    def show_result(self, output, error):
+        """Utility method to display 'output' and 'error' in a new
+        OutputWindow"""
+        theme = idleConf.CurrentTheme()
+        stdout = idleConf.GetHighlight(theme, 'stdout')
+        stderr = idleConf.GetHighlight(theme, 'stderr')
+        tagdefs = {'stdout':stdout, 'stderr':stderr}
+        outputwindow = OutputWindow(self.editwin.flist)
+        for tag, cnf in tagdefs.items():
+            outputwindow.text.tag_configure(tag, **cnf)
+        outputwindow.write(error, 'stderr')
+        outputwindow.write(output, 'stdout')
+
+
+class ConfigCheckerDialog(tk.Toplevel):
+
+    def __init__(self, editwin, _checker, checker=None):
+        tk.Toplevel.__init__(self, editwin.top)
+        self.editwin = editwin
+        self._checker = _checker
+        self.checker = checker
+        self.parent = parent = self.editwin.top
+        self.enabled = tk.StringVar(parent)
+        self.additional = tk.StringVar(parent)
+        if checker:
+            config = get_checker_config(checker)
+            self.additional.set(config['additional'] or '')
+            self.enabled.set(config['enabled'])
+        self.create_widgets()
+
+    def create_widgets(self):
+        parent = self.parent
+        self.configure(borderwidth=5)
+        title = 'Edit {} checker'.format(self.checker)
+        self.wm_title(title)
+        self.geometry('+%d+%d' % (parent.winfo_rootx() + 30,
+                                    parent.winfo_rooty() + 30))
+        self.transient(parent)
+        self.focus_set()
+        # frames creation
+        optionsFrame = tk.Frame(self)
+        buttonFrame = tk.Frame(self)
+
+        # optionsFrame
+        additionalLabel = tk.Label(optionsFrame, text='Additional Args')
+
+        self.additionalEntry = tk.Entry(optionsFrame,
+               textvariable=self.additional)
+        enabledCheckbutton = tk.Checkbutton(optionsFrame,
+                variable=self.enabled, text='Enable Checker?')
+
+        # buttonFrame
+        okButton = tk.Button(buttonFrame, text='Ok', command=self.ok)
+        cancelButton = tk.Button(buttonFrame, text='Cancel',
+                                 command=self.cancel)
+
+        # frames packing
+        optionsFrame.pack()
+        buttonFrame.pack()
+        # optionsFrame packing
+        additionalLabel.pack()
+        self.additionalEntry.pack()
+        enabledCheckbutton.pack()
+        # buttonFrame packing
+        okButton.pack()
+        cancelButton.pack()
+
+    def ok(self):
+        name = self.checker
+        idleConf.userCfg['checker'].SetOption(name, 'enabled',
+                self.enabled.get())
+        idleConf.userCfg['checker'].SetOption(name, 'additional',
+                self.additional.get())
+        idleConf.userCfg['checker'].Save()
+        self.close()
+
+    def cancel(self):
+        self.close()
+
+    def close(self):
+        self._checker.update_listbox()
+        self._checker.update_menu()
+        self.destroy()
diff -r 8734e881c400 Lib/idlelib/config-extensions.def
--- a/Lib/idlelib/config-extensions.def	Sun Jun 29 00:46:45 2014 +0200
+++ b/Lib/idlelib/config-extensions.def	Thu Jul 03 22:55:56 2014 +0530
@@ -94,3 +94,9 @@
 enable_shell=0
 enable_editor=1
 
+[Checker]
+enable=1
+enable_shell=0
+enable_editor=1
+[Checker_bindings]
+config-checker=
diff -r 8734e881c400 Lib/idlelib/configHandler.py
--- a/Lib/idlelib/configHandler.py	Sun Jun 29 00:46:45 2014 +0200
+++ b/Lib/idlelib/configHandler.py	Thu Jul 03 22:55:56 2014 +0530
@@ -162,6 +162,7 @@
         (user home dir)/.idlerc/config-extensions.cfg
         (user home dir)/.idlerc/config-highlight.cfg
         (user home dir)/.idlerc/config-keys.cfg
+        (user home dir)/.idlerc/config-checker.cfg
     """
     def __init__(self):
         self.defaultCfg={}
@@ -182,7 +183,7 @@
         else: # we were exec'ed (for testing only)
             idleDir=os.path.abspath(sys.path[0])
         userDir=self.GetUserCfgDir()
-        configTypes=('main','extensions','highlight','keys')
+        configTypes=('main','extensions','highlight','keys', 'checker')
         defCfgFiles={}
         usrCfgFiles={}
         for cfgType in configTypes: #build config file names
@@ -282,9 +283,11 @@
         Get a list of sections from either the user or default config for
         the given config type.
         configSet must be either 'user' or 'default'
-        configType must be one of ('main','extensions','highlight','keys')
+        configType must be one of ('main','extensions','highlight','keys',
+        'checker')
         """
-        if not (configType in ('main','extensions','highlight','keys')):
+        if not (configType in ('main','extensions','highlight','keys',
+        'checker')):
             raise InvalidConfigType('Invalid configType specified')
         if configSet == 'user':
             cfgParser=self.userCfg[configType]


More information about the IDLE-dev mailing list