Repository: qutebrowser/qutebrowser

Issue:
Also read greasemonkey/userscript files from config directory?
Some people [disagree](https://www.reddit.com/r/qutebrowser/comments/hvjhcf/question_why_are_userscripts_and_greasemonkey/) with where userscripts and greasemonkey files belong - I won't spend any time on discussing what the right place would be (we've been there before, and people have differing opinions).

However, since qutebrowser only *reads* those files (and doesn't write to them), I suppose it'd be possible to load them from both places and (maybe) make everyone happy.


Discussion:
At least for userscripts, this should be a small change: Add the directory to `_lookup_path` in `qutebrowser/commands/userscripts.py` (probably to the end of the list, to not affect existing setups) and perhaps add a test to `tests/unit/commands/test_userscripts.py`.

Resolving patch:
diff --git a/qutebrowser/browser/greasemonkey.py b/qutebrowser/browser/greasemonkey.py
--- a/qutebrowser/browser/greasemonkey.py
+++ b/qutebrowser/browser/greasemonkey.py
@@ -41,9 +41,12 @@
 gm_manager = cast('GreasemonkeyManager', None)
 
 
-def _scripts_dir():
+def _scripts_dirs():
     """Get the directory of the scripts."""
-    return os.path.join(standarddir.data(), 'greasemonkey')
+    return [
+        os.path.join(standarddir.data(), 'greasemonkey'),
+        os.path.join(standarddir.config(), 'greasemonkey'),
+    ]
 
 
 class GreasemonkeyScript:
@@ -277,18 +280,19 @@ def load_scripts(self, *, force=False):
         self._run_end = []
         self._run_idle = []
 
-        scripts_dir = os.path.abspath(_scripts_dir())
-        log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
-        for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
-            if not os.path.isfile(script_filename):
-                continue
-            script_path = os.path.join(scripts_dir, script_filename)
-            with open(script_path, encoding='utf-8-sig') as script_file:
-                script = GreasemonkeyScript.parse(script_file.read(),
-                                                  script_filename)
-                if not script.name:
-                    script.name = script_filename
-                self.add_script(script, force)
+        for scripts_dir in _scripts_dirs():
+            scripts_dir = os.path.abspath(scripts_dir)
+            log.greasemonkey.debug("Reading scripts from: {}".format(scripts_dir))
+            for script_filename in glob.glob(os.path.join(scripts_dir, '*.js')):
+                if not os.path.isfile(script_filename):
+                    continue
+                script_path = os.path.join(scripts_dir, script_filename)
+                with open(script_path, encoding='utf-8-sig') as script_file:
+                    script = GreasemonkeyScript.parse(script_file.read(),
+                                                      script_filename)
+                    if not script.name:
+                        script.name = script_filename
+                    self.add_script(script, force)
         self.scripts_reloaded.emit()
 
     def add_script(self, script, force=False):
@@ -325,7 +329,7 @@ def _add_script(self, script):
         log.greasemonkey.debug("Loaded script: {}".format(script.name))
 
     def _required_url_to_file_path(self, url):
-        requires_dir = os.path.join(_scripts_dir(), 'requires')
+        requires_dir = os.path.join(_scripts_dirs()[0], 'requires')
         if not os.path.exists(requires_dir):
             os.mkdir(requires_dir)
         return os.path.join(requires_dir, utils.sanitize_filename(url))
@@ -426,7 +430,7 @@ def greasemonkey_reload(force=False):
     """Re-read Greasemonkey scripts from disk.
 
     The scripts are read from a 'greasemonkey' subdirectory in
-    qutebrowser's data directory (see `:version`).
+    qutebrowser's data or config directories (see `:version`).
 
     Args:
         force: For any scripts that have required dependencies,
@@ -440,7 +444,8 @@ def init():
     global gm_manager
     gm_manager = GreasemonkeyManager()
 
-    try:
-        os.mkdir(_scripts_dir())
-    except FileExistsError:
-        pass
+    for scripts_dir in _scripts_dirs():
+        try:
+            os.mkdir(scripts_dir)
+        except FileExistsError:
+            pass
diff --git a/qutebrowser/commands/userscripts.py b/qutebrowser/commands/userscripts.py
--- a/qutebrowser/commands/userscripts.py
+++ b/qutebrowser/commands/userscripts.py
@@ -395,6 +395,7 @@ def _lookup_path(cmd):
     directories = [
         os.path.join(standarddir.data(), "userscripts"),
         os.path.join(standarddir.data(system=True), "userscripts"),
+        os.path.join(standarddir.config(), "userscripts"),
     ]
     for directory in directories:
         cmd_path = os.path.join(directory, cmd)


Test patch:
diff --git a/tests/helpers/fixtures.py b/tests/helpers/fixtures.py
--- a/tests/helpers/fixtures.py
+++ b/tests/helpers/fixtures.py
@@ -166,7 +166,7 @@ def fake_web_tab(stubs, tab_registry, mode_manager, qapp):
 
 
 @pytest.fixture
-def greasemonkey_manager(monkeypatch, data_tmpdir):
+def greasemonkey_manager(monkeypatch, data_tmpdir, config_tmpdir):
     gm_manager = greasemonkey.GreasemonkeyManager()
     monkeypatch.setattr(greasemonkey, 'gm_manager', gm_manager)
 
diff --git a/tests/unit/javascript/test_greasemonkey.py b/tests/unit/javascript/test_greasemonkey.py
--- a/tests/unit/javascript/test_greasemonkey.py
+++ b/tests/unit/javascript/test_greasemonkey.py
@@ -41,12 +41,15 @@
 console.log("Script is running.");
 """
 
-pytestmark = pytest.mark.usefixtures('data_tmpdir')
+pytestmark = [
+    pytest.mark.usefixtures('data_tmpdir'),
+    pytest.mark.usefixtures('config_tmpdir')
+]
 
 
 def _save_script(script_text, filename):
     # pylint: disable=no-member
-    file_path = py.path.local(greasemonkey._scripts_dir()) / filename
+    file_path = py.path.local(greasemonkey._scripts_dirs()[0]) / filename
     # pylint: enable=no-member
     file_path.write_text(script_text, encoding='utf-8', ensure=True)