diff --git a/CHANGES.rst b/CHANGES.rst index 5d58bb4dbd2cd50ee3c1226d85c7deff4ccba98e..a5742f8278005514ea2a2d5e492c77cd090cdb4d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,7 +8,14 @@ Major overhaul of the whole project. We introduce more modern approaches in project layout (like `pyproject`), use new linters and other tools while we still support all Python versions from 2.7 up to current 3.12. +We now follow the XDG base directory specification, that tells where config +(and other) files for an application can be found. You now can also use +`${XDG_CONFIG_HOME}/diceware/diceware.ini`, or, if the given var is emtpy or +unset, `${HOME}/.config/diceware/diceware.ini`. The traditional location +`${HOME}/.diceware.ini` is still supported. + - Officially support Python 3.10 to 3.12. +- Fixed #86: Follow `XDG`_ base directory specification. - Use `ruff` as linter, drop `flake8`. - Renew `tox` configuration. - Switch to `pyproject`-based project layout, away from using `setup.py`. diff --git a/README.rst b/README.rst index 47168d812394e57f1e8f7b3c5424c1f2e868a7f3..b5621048cecc17173847f2b127a88d9194dbc031 100644 --- a/README.rst +++ b/README.rst @@ -212,9 +212,14 @@ In custom wordlists we take each line for a valid word and ignore empty lines (i.e. lines containing whitespace characters only). Oh, and we handle even PGP-signed wordlists. -You can set customized default values in a configuration file -``.diceware.ini`` (note the leading dot) placed in your home -directory. This file could look like this:: +You can set customized default values in a configuration file ``.diceware.ini`` +(note the leading dot) placed in your home directory. Since version 1.0 you can +also use ``${XDG_CONFIG_HOME}/diceware/diceware.ini`` or +``${HOME}/.config/diceware/diceware.ini`` (if ``${XDG_CONFIG_HOME}`` is +undefined, see XDG_ for details). + + +This file could look like this:: [diceware] num = 7 @@ -432,6 +437,7 @@ People that helped spotting bugs, providing solutions, etc.: - `Simon Fondrie-Teitler <https://github.com/simonft>`_ contributed a machine-readable copyright file, with improvements from `@anarcat`_ - `Doug Muth <https://github.com/dmuth>`_ fixed formatting in docs. + - `@kmille`_ suggested support for XDG config file locations. Many thanks to all of them! @@ -480,6 +486,7 @@ details. .. _`fork me on github`: http://github.com/ulif/diceware/ .. _`@heartsucker`: https://github.com/heartsucker/ .. _`Joseph Bonneau`: https://www.eff.org/about/staff/joseph-bonneau +.. _`@kmille`: https://github.com/kmille .. _`NaturalLanguagePasswords`: https://github.com/NaturalLanguagePasswords .. _`prefix code`: https://en.wikipedia.org/wiki/Prefix_code .. _`random.SystemRandom`: https://docs.python.org/3.4/library/random.html#random.SystemRandom @@ -488,3 +495,4 @@ details. .. _py.test: https://pytest.org/ .. _tox: https://tox.testrun.org/ .. _Sphinx: https://sphinx-doc.org/ +.. _`XDG`: https://specifications.freedesktop.org/basedir-spec/latest/ diff --git a/diceware/config.py b/diceware/config.py index fb5dd7977966740ddc95597c7bd36aa6aa2edd60..1d994ed25d9ff74f2bbdcb375f6a82c8a5371342 100644 --- a/diceware/config.py +++ b/diceware/config.py @@ -42,11 +42,37 @@ RE_WLIST_NAME = re.compile(r'(?![\w\-]+).') def valid_locations(): """The list of valid paths we look up for config files. + + We search for config files in the following locations (in that order): + 1a) dirs in colon-separated var $XDG_CONFIG_DIRS + 1b) /etc/xdg/diceware/diceware.ini # if $XDG_CONFIG_DIRS is undefined + 2a) $XDG_CONFIG_HOME/diceware/diceware.ini # if $XDG_CONFIG_HOME + # is defined + 2b) $HOME/.config/diceware/diceware.ini # if $HOME is defined + # but not $XDG_CONFIG_HOME + Finally we look also for + 3) ~/.diceware.ini + + Later read configs override prior ones. Therefore an existing + `~/.diceware.ini` contains values that cannot be overridden, except on + commandline. + """ - user_home = os.path.expanduser("~") result = [] + user_home = os.path.expanduser("~") if user_home != "~": - result = [os.path.join(user_home, ".diceware.ini"), ] + result.append(os.path.join(user_home, ".diceware.ini")) + xdg_dirs = os.getenv("XDG_CONFIG_DIRS", os.path.normcase("/etc/xdg")) + if os.getenv("XDG_CONFIG_HOME") or os.getenv("HOME"): + xdg_dirs = ( + os.getenv("XDG_CONFIG_HOME", os.path.join(os.getenv("HOME", ""), ".config")) + + ":" + + xdg_dirs + ) + result.extend( + [os.path.join(x, "diceware", "diceware.ini") for x in xdg_dirs.split(":")] + ) + result.reverse() return result diff --git a/tests/conftest.py b/tests/conftest.py index 06b0bb563d52675b0f15d5172ebc6d3dec3f6065..d9c7fb60608668c8f18b94aca9d1f74d25ccb5d5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,8 +78,13 @@ def change_home(monkeypatch, tmpdir): If the user running tests has an own .diceware.ini in his home, then this will influence tests. Therefore we set the user home to some empty dir while tests are running. + + The same applies for XDG-based config files, that might be set on the host + running and point to real config files not related to testing. """ monkeypatch.setenv("HOME", str(tmpdir)) + monkeypatch.delenv("XDG_CONFIG_DIRS", raising=False) + monkeypatch.delenv("XDG_CONFIG_HOME", raising=False) return tmpdir diff --git a/tests/test_config.py b/tests/test_config.py index 7e301962f14f0222a577c3766af92fb275c67f5e..a62aa791d8667ea7962c8583d92184a92dc80aef 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,15 +13,41 @@ class TestConfigModule(object): assert OPTIONS_DEFAULTS is not None def test_valid_locations(self, home_dir): - # we look for config files in user home and local dir - assert valid_locations() == [ - str(home_dir / ".diceware.ini") - ] + # we look for config files in "~/.diceware.ini" + locations = valid_locations() + assert locations == [ + "/etc/xdg/diceware/diceware.ini", + str(home_dir / ".config" / "diceware" / "diceware.ini"), + str(home_dir / ".diceware.ini"), + ] def test_valid_locations_wo_user_home(self, monkeypatch): - # w/o a valid home we get an empty list + # w/o a valid home we get at least a system-wide default location monkeypatch.setattr("os.path.expanduser", lambda x: x) - assert valid_locations() == [] + monkeypatch.delenv("HOME") + locations = valid_locations() + assert locations == ["/etc/xdg/diceware/diceware.ini"] + + def test_valid_locations_considers_xdg_config_home(self, monkeypatch, home_dir): + # we consider the $XDG_CONFIG_HOME env var + monkeypatch.setenv("XDG_CONFIG_HOME", "/foo") + locations = valid_locations() + assert locations == [ + "/etc/xdg/diceware/diceware.ini", + "/foo/diceware/diceware.ini", + str(home_dir / ".diceware.ini"), + ] + + def test_valid_locations_considers_xdg_config_dirs(self, monkeypatch, home_dir): + # we consider the $XDG_CONFIG_DIRS env var + monkeypatch.setenv("XDG_CONFIG_DIRS", "/foo:/bar") + locations = valid_locations() + assert locations == [ + "/bar/diceware/diceware.ini", + "/foo/diceware/diceware.ini", + str(home_dir / ".config" / "diceware" / "diceware.ini"), + str(home_dir / ".diceware.ini"), + ] def test_get_configparser(self, tmpdir): # we can parse simple configs