diff --git a/CHANGELOG.md b/CHANGELOG.md index cb1b000f..f19b73b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Added * The *rollback ratio* is now displayed in the "global" header (#385). +* Configuration profiles can now be defined at + `${XDG_CONFIG_HOME:~/.config}/pg_activity/.conf` or + `/etc/pg_activity/.conf` as selected from the command line through + `--profile `. ### Changed diff --git a/README.md b/README.md index 4f7d8fcd..a39fb8c8 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,11 @@ ex: pg_activity [options] [connection string] + Configuration: + --profile PROFILE Configuration profile matching a PROFILE.conf file in + ${XDG_CONFIG_HOME:~/.config}/pg_activity/ or + /etc/pg_activity/. + Options: --blocksize BLOCKSIZE Filesystem blocksize (default: 4096). @@ -140,7 +145,7 @@ ex: ## Configuration -`pg_activity` may be configured through configuration file, in [INI format][], +`pg_activity` may be configured through a configuration file, in [INI format][], read from `${XDG_CONFIG_HOME:~/.config}/pg_activity.conf` or `/etc/pg_activity.conf` in that order. Command-line options may override configuration file settings. @@ -153,6 +158,13 @@ hidden = yes width = 9 ``` +Alternatively, the user might define *configuration profiles* in the form of +files located at `${XDG_CONFIG_HOME:~/.config}/pg_activity/.conf` or +`/etc/pg_activity/.conf`; these can then be used through the +`--profile ` command-line option. The format of these files is the +same as the main configuration file. + + [INI format]: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure ## Notes diff --git a/docs/man/pg_activity.1 b/docs/man/pg_activity.1 index 43b7ed86..2a27b2c1 100644 --- a/docs/man/pg_activity.1 +++ b/docs/man/pg_activity.1 @@ -347,6 +347,13 @@ required by another session. It shows following information: .PD .SH "COMMAND-LINE OPTIONS" .IX Header "COMMAND-LINE OPTIONS" +.SS "\s-1CONFIGURATION\s0" +.IX Subsection "CONFIGURATION" +.IP "\fB\-\-profile=PROFILE\fR" 2 +.IX Item "--profile=PROFILE" +.Vb 1 +\& Configuration profile matching a PROFILE.conf file in ${XDG_CONFIG_HOME:~/.config}/pg_activity/ or /etc/pg_activity/. +.Ve .SS "\s-1OPTIONS\s0" .IX Subsection "OPTIONS" .IP "\fB\-\-blocksize=BLOCKSIZE\fR" 2 diff --git a/docs/man/pg_activity.pod b/docs/man/pg_activity.pod index 99572cfb..d9f1d2b9 100644 --- a/docs/man/pg_activity.pod +++ b/docs/man/pg_activity.pod @@ -230,6 +230,16 @@ required by another session. It shows following information: =head1 COMMAND-LINE OPTIONS +=head2 CONFIGURATION + +=over 2 + +=item B<--profile=PROFILE> + + Configuration profile matching a PROFILE.conf file in ${XDG_CONFIG_HOME:~/.config}/pg_activity/ or /etc/pg_activity/. + +=back + =head2 OPTIONS =over 2 diff --git a/pgactivity/cli.py b/pgactivity/cli.py index 48a09b9b..ec11311e 100755 --- a/pgactivity/cli.py +++ b/pgactivity/cli.py @@ -59,6 +59,17 @@ def get_parser() -> ArgumentParser: add_help=False, ) + group = parser.add_argument_group( + "Configuration", + ) + group.add_argument( + "--profile", + help=( + "Configuration profile matching a PROFILE.conf file in " + "${XDG_CONFIG_HOME:~/.config}/pg_activity/ or /etc/pg_activity/." + ), + ) + group = parser.add_argument_group( "Options", ) @@ -387,8 +398,8 @@ def main() -> None: args.notempfile = True try: - cfg = Configuration.lookup() - except ConfigurationError as e: + cfg = Configuration.lookup(args.profile) + except (ConfigurationError, FileNotFoundError) as e: parser.error(str(e)) try: diff --git a/pgactivity/config.py b/pgactivity/config.py index 280b93fa..5542bfc1 100644 --- a/pgactivity/config.py +++ b/pgactivity/config.py @@ -261,14 +261,25 @@ def parse(cls: Type[_T], f: IO[str], name: str) -> _T: @classmethod def lookup( cls: Type[_T], + profile: Optional[str], *, user_config_home: Path = USER_CONFIG_HOME, etc: Path = ETC, ) -> Optional[_T]: - for base in (user_config_home, etc): - fpath = base / "pg_activity.conf" + if profile is None: + for base in (user_config_home, etc): + fpath = base / "pg_activity.conf" + if fpath.exists(): + with fpath.open() as f: + return cls.parse(f, str(fpath)) + return None + + assert profile # per argument validation + fname = f"{profile}.conf" + bases = (user_config_home / "pg_activity", etc / "pg_activity") + for base in bases: + fpath = base / fname if fpath.exists(): with fpath.open() as f: - value = cls.parse(f, str(fpath)) - return value - return None + return cls.parse(f, str(fpath)) + raise FileNotFoundError(f"profile {profile!r} not found") diff --git a/tests/test_config.py b/tests/test_config.py index 93909b88..96c3de27 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -2,6 +2,7 @@ from typing import Any, Dict import attr +import pytest from pgactivity.config import Configuration, Flag, UISection @@ -78,9 +79,19 @@ def test_lookup(tmp_path: Path) -> None: def asdict(cfg: Configuration) -> Dict[str, Any]: return {k: attr.asdict(v) for k, v in cfg.items()} - cfg = Configuration.lookup(user_config_home=tmp_path) + cfg = Configuration.lookup(None, user_config_home=tmp_path) assert cfg is None (tmp_path / "pg_activity.conf").write_text("\n".join(["[client]", "width=5"])) - cfg = Configuration.lookup(user_config_home=tmp_path) + cfg = Configuration.lookup(None, user_config_home=tmp_path) assert cfg is not None and asdict(cfg) == {"client": {"hidden": False, "width": 5}} + + (tmp_path / "pg_activity").mkdir() + (tmp_path / "pg_activity" / "x.conf").write_text( + "\n".join(["[database]", "hidden= on", "width = 3 "]) + ) + cfg = Configuration.lookup("x", user_config_home=tmp_path) + assert cfg is not None and asdict(cfg) == {"database": {"hidden": True, "width": 3}} + + with pytest.raises(FileNotFoundError): + Configuration.lookup("y", user_config_home=tmp_path)