diff --git a/.pylintrc b/.pylintrc index 8cb7a35fd..8498bea12 100644 --- a/.pylintrc +++ b/.pylintrc @@ -2,6 +2,7 @@ [MASTER] ignore=ez_setup.py +extension-pkg-whitelist=PyQt5,sip [MESSAGES CONTROL] disable=no-self-use, @@ -23,7 +24,8 @@ disable=no-self-use, too-many-instance-attributes, unnecessary-lambda, blacklisted-name, - too-many-lines + too-many-lines, + logging-format-interpolation [BASIC] module-rgx=(__)?[a-z][a-z0-9_]*(__)?$ diff --git a/.run_checks b/.run_checks index 070f920f1..808d5882f 100644 --- a/.run_checks +++ b/.run_checks @@ -13,7 +13,8 @@ exclude=test_.* [pylint] args=--output-format=colorized,--reports=no,--rcfile=.pylintrc plugins=config,crlf,modeline,settrace,openencoding -exclude=resources.py +# excluding command.py is a WORKAROUND for https://bitbucket.org/logilab/pylint/issue/395/horrible-performance-related-to-inspect +exclude=resources.py,command.py [flake8] args=--config=.flake8 diff --git a/README.asciidoc b/README.asciidoc index 3cca31379..8bfee7fbb 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -130,8 +130,8 @@ Contributors, sorted by the number of commits in descending order: // QUTE_AUTHORS_START * Florian Bruhin * Claude -* John ShaggyTwoDope Jenkins * Peter Vilim +* John ShaggyTwoDope Jenkins * rikn00 * Martin Zimmermann * Joel Torstensson @@ -141,10 +141,12 @@ Contributors, sorted by the number of commits in descending order: * Regina Hug * Mathias Fussenegger * Larry Hynes +* Thorsten Wißmann * Thiago Barroso Perrotta * Matthias Lisin * Helen Sherwood-Taylor * HalosGhost +* Eivind Uggedal * Andreas Fischer // QUTE_AUTHORS_END diff --git a/doc/INSTALL.asciidoc b/doc/INSTALL.asciidoc index c39714ddd..f75b09e26 100644 --- a/doc/INSTALL.asciidoc +++ b/doc/INSTALL.asciidoc @@ -80,6 +80,16 @@ in your `PYTHON_TARGETS` (`/etc/portage/make.conf`) and rebuild your system # emerge -av qutebrowser ---- +On Void Linux +------------- + +qutebrowser is available in the official repositories and can be installed +with: + +---- +# xbps-install qutebrowser +---- + On Windows ---------- diff --git a/qutebrowser/app.py b/qutebrowser/app.py index 8a07e5385..dd158e7cc 100644 --- a/qutebrowser/app.py +++ b/qutebrowser/app.py @@ -130,7 +130,7 @@ class Application(QApplication): utils.actute_warning() try: self._init_modules() - except OSError as e: + except (OSError, UnicodeDecodeError) as e: msgbox = QMessageBox( QMessageBox.Critical, "Error while initializing!", "Error while initializing: {}".format(e)) diff --git a/qutebrowser/browser/commands.py b/qutebrowser/browser/commands.py index 9fb88f599..9e7b50f86 100644 --- a/qutebrowser/browser/commands.py +++ b/qutebrowser/browser/commands.py @@ -813,10 +813,9 @@ class CommandDispatcher: log.procs.debug("Executing: {}, userscript={}".format( args, userscript)) if userscript: - if len(args) > 1: - self.run_userscript(args[0], args[1:]) - else: - self.run_userscript(args[0]) + cmd = args[0] + args = [] if not args else args[1:] + self.run_userscript(cmd, *args) else: try: subprocess.Popen(args) diff --git a/qutebrowser/completion/models/completion.py b/qutebrowser/completion/models/completion.py index e2940de92..09263970a 100644 --- a/qutebrowser/completion/models/completion.py +++ b/qutebrowser/completion/models/completion.py @@ -116,8 +116,10 @@ class SettingValueCompletionModel(base.BaseCompletionModel): value = '""' self.cur_item, _descitem, _miscitem = self.new_item(cur_cat, value, "Current value") - self.new_item(cur_cat, configdata.DATA[section][option].default(), - "Default value") + default_value = configdata.DATA[section][option].default() + if not default_value: + default_value = '""' + self.new_item(cur_cat, default_value, "Default value") if hasattr(configdata.DATA[section], 'valtype'): # Same type for all values (ValueList) vals = configdata.DATA[section].valtype.complete() diff --git a/qutebrowser/config/config.py b/qutebrowser/config/config.py index 03a7564ce..b12c33dcf 100644 --- a/qutebrowser/config/config.py +++ b/qutebrowser/config/config.py @@ -123,7 +123,7 @@ def init(args): try: app = objreg.get('app') config_obj = ConfigManager(confdir, 'qutebrowser.conf', app) - except (configexc.Error, configparser.Error) as e: + except (configexc.Error, configparser.Error, UnicodeDecodeError) as e: log.init.exception(e) errstr = "Error while reading config:" try: @@ -141,7 +141,7 @@ def init(args): objreg.register('config', config_obj) try: key_config = keyconf.KeyConfigParser(confdir, 'keys.conf') - except keyconf.KeyConfigError as e: + except (keyconf.KeyConfigError, UnicodeDecodeError) as e: log.init.exception(e) errstr = "Error while reading key config:\n" if e.lineno is not None: diff --git a/qutebrowser/misc/crashdialog.py b/qutebrowser/misc/crashdialog.py index da533cd41..62753b57c 100644 --- a/qutebrowser/misc/crashdialog.py +++ b/qutebrowser/misc/crashdialog.py @@ -81,6 +81,22 @@ class _CrashDialog(QDialog): self._paste_client = pastebin.PastebinClient(self) self._init_text() + contact = QLabel("I'd like to be able to follow up with you, to keep " + "you posted on the status of this crash and get more " + "information if I need it - how can I contact you?") + self._vbox.addWidget(contact) + self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False) + try: + state = objreg.get('state-config') + try: + self._contact.setPlainText(state['general']['contact-info']) + except KeyError: + self._contact.setPlaceholderText("Mail or IRC nickname") + except Exception: + log.misc.exception("Failed to get contact information!") + self._contact.setPlaceholderText("Mail or IRC nickname") + self._vbox.addWidget(self._contact, 2) + info = QLabel("What were you doing when this crash/bug happened?") self._vbox.addWidget(info) self._info = QTextEdit(tabChangesFocus=True, acceptRichText=False) @@ -88,11 +104,6 @@ class _CrashDialog(QDialog): "- Switched tabs\n" "- etc...") self._vbox.addWidget(self._info, 5) - contact = QLabel("How can I contact you if I need more info?") - self._vbox.addWidget(contact) - self._contact = QTextEdit(tabChangesFocus=True, acceptRichText=False) - self._contact.setPlaceholderText("Github username, mail or IRC") - self._vbox.addWidget(self._contact, 2) self._vbox.addSpacing(15) self._debug_log = QTextEdit(tabChangesFocus=True, acceptRichText=False, @@ -229,6 +240,11 @@ class _CrashDialog(QDialog): @pyqtSlot() def finish(self): """Accept/reject the dialog when reporting is done.""" + try: + state = objreg.get('state-config') + state['general']['contact-info'] = self._contact.toPlainText() + except Exception: + log.misc.exception("Failed to save contact information!") if self._resolution: self.accept() else: diff --git a/qutebrowser/misc/earlyinit.py b/qutebrowser/misc/earlyinit.py index f22637871..eda1e3fee 100644 --- a/qutebrowser/misc/earlyinit.py +++ b/qutebrowser/misc/earlyinit.py @@ -234,14 +234,14 @@ def check_libraries(): 'jinja2': _missing_str("jinja2", debian="apt-get install python3-jinja2", - arch="Install python-jinja from the AUR", + arch="Install python-jinja from community", windows="Install from http://www.lfd.uci.edu/" "~gohlke/pythonlibs/#jinja2 or via pip.", pip="jinja2"), 'pygments': _missing_str("pygments", debian="apt-get install python3-pygments", - arch="Install python-jinja from the AUR", + arch="Install python-pygments from community", windows="Install from http://www.lfd.uci.edu/" "~gohlke/pythonlibs/#pygments or via pip.", pip="pygments"), diff --git a/qutebrowser/test/config/test_config.py b/qutebrowser/test/config/test_config.py index 7555df655..e27c49063 100644 --- a/qutebrowser/test/config/test_config.py +++ b/qutebrowser/test/config/test_config.py @@ -58,6 +58,9 @@ class ConfigParserTests(unittest.TestCase): def test_transformed_option_old(self): """Test a transformed option with the old name.""" + # WORKAROUND for unknown PyQt bug + # Instance of 'str' has no 'name' member + # pylint: disable=no-member self.cp.read_dict({'colors': {'tab.fg.odd': 'pink'}}) self.cfg._from_cp(self.cp) self.assertEqual(self.cfg.get('colors', 'tabs.fg.odd').name(), @@ -65,6 +68,9 @@ class ConfigParserTests(unittest.TestCase): def test_transformed_option_new(self): """Test a transformed section with the new name.""" + # WORKAROUND for unknown PyQt bug + # Instance of 'str' has no 'name' member + # pylint: disable=no-member self.cp.read_dict({'colors': {'tabs.fg.odd': 'pink'}}) self.cfg._from_cp(self.cp) self.assertEqual(self.cfg.get('colors', 'tabs.fg.odd').name(), diff --git a/scripts/init_venv.py b/scripts/init_venv.py index ea0ef2861..49e548933 100644 --- a/scripts/init_venv.py +++ b/scripts/init_venv.py @@ -70,8 +70,8 @@ def get_dev_packages(short=False): Args: short: Remove the version specification. """ - packages = ['colorlog', 'flake8', 'astroid==1.2.1', 'pylint==1.3.1', - 'pep257', 'colorama', 'beautifulsoup4'] + packages = ['colorlog', 'flake8', 'astroid', 'pylint', 'pep257', + 'colorama', 'beautifulsoup4'] if short: packages = [re.split(r'[<>=]', p)[0] for p in packages] return packages @@ -91,11 +91,15 @@ def venv_python(*args, output=False): """Call the virtualenv's python with the given arguments.""" subdir = 'Scripts' if os.name == 'nt' else 'bin' executable = os.path.join(g_path, subdir, os.path.basename(sys.executable)) + env = dict(os.environ) + if sys.platform == 'darwin' and '__PYVENV_LAUNCHER__' in env: + # WORKAROUND for https://github.com/pypa/pip/issues/2031 + del env['__PYVENV_LAUNCHER__'] if output: return subprocess.check_output([executable] + list(args), - universal_newlines=True) + universal_newlines=True, env=env) else: - subprocess.check_call([executable] + list(args)) + subprocess.check_call([executable] + list(args), env=env) def test_toolchain(): diff --git a/scripts/run_checks.py b/scripts/run_checks.py index a577468c4..d648f1abd 100755 --- a/scripts/run_checks.py +++ b/scripts/run_checks.py @@ -78,20 +78,32 @@ def _adjusted_pythonpath(name): del os.environ['PYTHONPATH'] -def run(name, target=None): +def run(name, target=None, print_version=False): """Run a checker via distutils with optional args. Arguments: name: Name of the checker/binary target: The package to check + print_version: Whether to print the checker version. """ # pylint: disable=too-many-branches args = _get_args(name) if target is not None: args.append(target) with _adjusted_pythonpath(name): + if os.name == 'nt': + exename = name + '.exe' + else: + exename = name + # for virtualenvs + executable = os.path.join(os.path.dirname(sys.executable), exename) + if not os.path.exists(executable): + # in $PATH + executable = name + if print_version: + subprocess.call([executable, '--version']) try: - status = subprocess.call([name] + args) + status = subprocess.call([executable] + args) except OSError: traceback.print_exc() status = None @@ -99,9 +111,15 @@ def run(name, target=None): return status -def check_pep257(target): - """Run pep257 checker with args passed.""" +def check_pep257(target, print_version=False): + """Run pep257 checker with args passed. + + We use this rather than run() because on some systems (e.g. Windows) no + pep257 binary is available. + """ # pylint: disable=assignment-from-no-return,no-member + if print_version: + print(pep257.__version__) args = _get_args('pep257') sys.argv = ['pep257', target] if args is not None: @@ -232,7 +250,7 @@ def _get_args(checker): return args -def _get_checkers(): +def _get_checkers(args): """Get a dict of checkers we need to execute.""" # "Static" checkers checkers = collections.OrderedDict([ @@ -241,17 +259,18 @@ def _get_checkers(): ('git', check_git), ])), ('setup', collections.OrderedDict([ - ('pyroma', functools.partial(run, 'pyroma')), - ('check-manifest', functools.partial(run, 'check-manifest')), + ('pyroma', functools.partial(run, 'pyroma', args.version)), + ('check-manifest', functools.partial(run, 'check-manifest', + args.version)), ])), ]) # "Dynamic" checkers which exist once for each target. for target in config.get('DEFAULT', 'targets').split(','): checkers[target] = collections.OrderedDict([ - ('pep257', functools.partial(check_pep257, target)), - ('flake8', functools.partial(run, 'flake8', target)), + ('pep257', functools.partial(check_pep257, target, args.version)), + ('flake8', functools.partial(run, 'flake8', target, args.version)), ('vcs', functools.partial(check_vcs_conflict, target)), - ('pylint', functools.partial(run, 'pylint', target)), + ('pylint', functools.partial(run, 'pylint', target, args.version)), ]) return checkers @@ -275,6 +294,8 @@ def _parse_args(): parser.add_argument('-q', '--quiet', help="Don't print unnecessary headers.", action='store_true') + parser.add_argument('-V', '--version', + help="Print checker versions.", action='store_true') parser.add_argument('checkers', help="Checkers to run (or 'all')", default='all', nargs='?') return parser.parse_args() @@ -290,7 +311,7 @@ def main(): exit_status_bool = {} args = _parse_args() - checkers = _get_checkers() + checkers = _get_checkers(args) groups = ['global'] groups += config.get('DEFAULT', 'targets').split(',') diff --git a/scripts/src2asciidoc.py b/scripts/src2asciidoc.py index 8f9b0eaf5..a459ad8ff 100755 --- a/scripts/src2asciidoc.py +++ b/scripts/src2asciidoc.py @@ -389,7 +389,7 @@ def regenerate_manpage(filename): options = '\n'.join(groups) # epilog if parser.epilog is not None: - options.append(parser.epilog) + options += parser.epilog _format_block(filename, 'options', options)