diff -r 8734e881c400 Lib/idlelib/Checker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/idlelib/Checker.py Tue Jul 01 22:55:07 2014 +0530 @@ -0,0 +1,298 @@ +import os +from subprocess import Popen, PIPE +from tempfile import NamedTemporaryFile +import tkinter as tk +import tkinter.messagebox as tkMessageBox +from idlelib.OutputWindow import OutputWindow +from idlelib.configHandler import idleConf + +def get_checkers(): + return idleConf.GetSectionList('user', 'checker') + +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['command'] = idleConf.GetOption('checker', checker, 'command') + config['additional'] = idleConf.GetOption('checker', checker, + 'additional') + return config + + +class Checker: + + menudefs = [ + ('run', [None, + ('Config Checkers', '<>') + ] + ) + ] + + 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() + addButton = tk.Button(dialog, text='Add checker', + command=self.add_checker) + editButton = tk.Button(dialog, text='Edit Checker', + command=self.edit_checker) + removeButton = tk.Button(dialog, text='Remove Checker', + command=self.remove_checker) + titleLabel.pack() + self.listbox.pack() + addButton.pack(side=tk.LEFT) + editButton.pack(side=tk.LEFT) + removeButton.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 add_checker(self): + ConfigCheckerDialog(self.editwin, self) + + 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 remove_checker(self): + checker = self._selected_checker() + if checker: + idleConf.userCfg['checker'].remove_option(checker, 'enabled') + idleConf.userCfg['checker'].remove_option(checker, 'command') + idleConf.userCfg['checker'].remove_option(checker, 'additional') + idleConf.userCfg['checker'].Save() + else: + tkMessageBox.showerror(title='No Checker Selected', + message='Selectan existing checker to remove', + parent=self.dialog) + self.update_listbox() + self.update_menu() + + 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=None): + """Run the 3rd party checker 'checker' and display result in a new + outputwindow""" + orig_file = self.editwin.io.filename + if not orig_file: + return + saved = self.editwin.get_saved() + if saved: + _file = orig_file + else: + _file = NamedTemporaryFile(suffix='.tmp', + delete=False).name + text = self.editwin.text.get('1.0', 'end') + with open(_file, 'w') as f: + f.write(text) + if checker: + additional_args = get_checker_config(checker)['additional'] + else: + checker = self.name.get() + additional_args = self.additional.get() + args = [checker, _file, additional_args] + args = [os.path.expanduser(arg) for arg in args if arg] + try: + proc = Popen(args, stdout=PIPE, stderr=PIPE) + proc.wait() + output, error = map(lambda b:b.decode('utf-8'), proc.communicate()) + except Exception as e: + output = '' + error = str(e) + if not saved: + os.unlink(_file) + output, error = self.process_text(_file, output, error) + 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') + + def process_text(self, temp_file, output, error): + orig_file = self.editwin.io.filename + orig_dirname = os.path.dirname(orig_file) + orig_basename = os.path.basename(orig_file) + orig_modulename = os.path.splitext(orig_basename) + + temp_dirname = os.path.dirname(temp_file) + temp_basename = os.path.basename(temp_file) + temp_modulename = os.path.splitext(temp_basename) + + output = output.replace(temp_modulename[0], orig_modulename[0]) + output = output.replace(temp_modulename[1], orig_modulename[1]) + output = output.replace(temp_basename, orig_basename) + output = output.replace(temp_dirname, orig_dirname) + output = output.replace(temp_file, orig_file) + + error = error.replace(temp_modulename[0], orig_modulename[0]) + error = error.replace(temp_modulename[1], orig_modulename[1]) + error = error.replace(temp_basename, orig_basename) + error = error.replace(temp_dirname, orig_dirname) + error = error.replace(temp_file, temp_file) + + return output, error + +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.name = tk.StringVar(parent) + self.command = tk.StringVar(parent) + self.enabled = tk.StringVar(parent) + self.additional = tk.StringVar(parent) + if checker: + config = get_checker_config(checker) + self.name.set(config['name']) + self.command.set(config['command']) + self.additional.set(config['additional']) + self.enabled.set(config['enabled']) + self.create_widgets() + + def create_widgets(self): + parent = self.parent + self.configure(borderwidth=5) + if self.checker: + title = 'Edit {} checker'.format(self.checker) + else: + title = 'Config new 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 + nameLabel = tk.Label(optionsFrame, text='Name of Checker') + commandLabel = tk.Label(optionsFrame, text='Command') + additionalLabel = tk.Label(optionsFrame, text='Additional Args') + + self.nameEntry = tk.Entry(optionsFrame, textvariable=self.name) + self.commandEntry = tk.Entry(optionsFrame, textvariable=self.command) + 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 + nameLabel.pack() + self.nameEntry.pack() + commandLabel.pack() + self.commandEntry.pack() + additionalLabel.pack() + self.additionalEntry.pack() + enabledCheckbutton.pack() + # buttonFrame packing + okButton.pack() + cancelButton.pack() + + def name_ok(self): + name = self.name.get() + ok = True + if name == '': + tkMessageBox.showerror(title='Name Error', + message='No Name Specified', parent=self) + ok = False + return ok + + def command_ok(self): + command = self.command.get() + ok = True + if command == '': + tkMessageBox.showerror(title='Command Error', + message='No Command Specified', parent=self) + ok = False + return ok + + def additional_ok(self): + ok = True + return ok + + def ok(self): + _ok = self.name_ok() and self.command_ok() and self.additional_ok() + if _ok: + name = self.name.get() + idleConf.userCfg['checker'].SetOption(name, 'enabled', + self.enabled.get()) + idleConf.userCfg['checker'].SetOption(name, 'command', + self.command.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 Tue Jul 01 22:55:07 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 Tue Jul 01 22:55:07 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] diff -r 8734e881c400 Lib/idlelib/idle_test/mock_tk.py --- a/Lib/idlelib/idle_test/mock_tk.py Sun Jun 29 00:46:45 2014 +0200 +++ b/Lib/idlelib/idle_test/mock_tk.py Tue Jul 01 22:55:07 2014 +0530 @@ -296,3 +296,12 @@ def bind(sequence=None, func=None, add=None): "Bind to this widget at event sequence a call to function func." pass + +class Listbox: + """Mock for tk.Listbox""" + def __init__(self, *args, **kwargs): + self.listitems = [] + self.selection = None + + def curselection(self): + return self.selection diff -r 8734e881c400 Lib/idlelib/idle_test/test_checker.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Lib/idlelib/idle_test/test_checker.py Tue Jul 01 22:55:07 2014 +0530 @@ -0,0 +1,116 @@ +import unittest +from idlelib.idle_test.mock_tk import Var, Mbox, Listbox +import idlelib.Checker as checker + +checker_dialog = checker.Checker +config_checker_dialog = checker.ConfigCheckerDialog + +class Dummy_checker: + # Mock for testing methods of Checker + edit_checker = checker_dialog.edit_checker + _selected_checker = checker_dialog._selected_checker + listbox = Listbox() + +class Dummy_config_checker_dialog: + # Mock for testing methods of ConfigCheckerDialog + name_ok = config_checker_dialog.name_ok + command_ok = config_checker_dialog.command_ok + additional_ok = config_checker_dialog.additional_ok + ok = config_checker_dialog.ok + cancel = config_checker_dialog.cancel + + name = Var() + command = Var() + additional = Var() + destroyed = False + def destroy(cls): + cls.destroyed = True + +# methods call Mbox.showerror if values are not ok +orig_mbox = checker.tkMessageBox +showerror = Mbox.showerror + +class CheckerUtilityTest(unittest.TestCase): + def test_get_checkers(self): + self.assertIsInstance(checker.get_checkers(), list) + + def test_get_enabled_checkers(self): + checkers_list = checker.get_checkers() + enabled_checkers = checker.get_enabled_checkers() + self.assertIsInstance(enabled_checkers, list) + self.assertTrue(set(enabled_checkers) <= set(checkers_list)) + + def test_checker_config(self): + get = checker.get_checker_config + with self.assertRaises(ValueError) as ve: + get('') + self.assertIn('empty', str(ve.exception)) + enabled_checkers = checker.get_enabled_checkers() + if enabled_checkers: + c = enabled_checkers[0] + cfg_list = list(get(c)) + self.assertIn('enabled', cfg_list) + self.assertIn('name', cfg_list) + self.assertIn('command', cfg_list) + self.assertIn('additional', cfg_list) + + +class CheckerTest(unittest.TestCase): + dialog = Dummy_checker() + + @classmethod + def setUpClass(cls): + cls.dialog.dialog = cls.dialog + checker.tkMessageBox = Mbox + + @classmethod + def tearDownClass(cls): + checker.tkMessageBox = orig_mbox + + def test_blank_edit(self): + self.assertIsNone(self.dialog.listbox.selection) + showerror.title = '' + showerror.message = '' + self.dialog.edit_checker() + self.assertEqual(showerror.title, 'No Checker Selected') + self.assertIn('existing', showerror.message) + +class ConfigCheckerDialogTest(unittest.TestCase): + dialog = Dummy_config_checker_dialog() + + @classmethod + def setUpClass(cls): + checker.tkMessageBox = Mbox + + @classmethod + def tearDownClass(cls): + checker.tkMessageBox = orig_mbox + + def test_blank_name(self): + self.dialog.name.set('') + self.assertFalse(self.dialog.name_ok()) + self.assertEqual(showerror.title, 'Name Error') + self.assertIn('No', showerror.message) + + def test_good_name(self): + self.dialog.name.set('pyflakes') + self.assertTrue(self.dialog.name_ok()) + + def test_blank_command(self): + self.dialog.command.set('') + self.assertFalse(self.dialog.command_ok()) + self.assertEqual(showerror.title, 'Command Error') + self.assertIn('No', showerror.message) + + def test_good_command(self): + self.dialog.command.set('/bin/pyflakes') + self.assertTrue(self.dialog.command_ok()) + + def test_good_additional(self): + self.dialog.additional.set('') + self.assertTrue(self.dialog.additional_ok()) + self.dialog.additional.set('--help') + self.assertTrue(self.dialog.additional_ok()) + +if __name__ == '__main__': + unittest.main(verbosity=2)