Skip to content

Commit a106d25

Browse files
committed
read configuration from a toml file
1 parent aec9b85 commit a106d25

File tree

16 files changed

+317
-104
lines changed

16 files changed

+317
-104
lines changed

.builds/arch.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,16 @@ image: archlinux
22
sources:
33
- https://git.sr.ht/~pvsr/qbpm
44
- https://aur.archlinux.org/python-xdg-base-dirs.git
5+
- https://aur.archlinux.org/python-dacite.git
56
packages:
67
- python-pytest
78
tasks:
89
- xdg-base-dirs: |
910
cd python-xdg-base-dirs
1011
makepkg -si --noconfirm
12+
- dacite: |
13+
cd python-dacite
14+
makepkg -si --noconfirm
1115
- makepkg: |
1216
cd qbpm/contrib
1317
sed -i 's|^source.*|source=("git+file:///home/build/qbpm")|' PKGBUILD

CHANGELOG.md

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,20 @@
1-
# next
1+
# 2.0
2+
## config
3+
qbpm now reads configuration options from a `$XDG_CONFIG_HOME/qbpm/config.toml`!
4+
- to install the default config file:
5+
- run `qbpm config path` and confirm that it prints out a path
6+
- run `qbpm config default > "$(qbpm config path)"`
7+
- supported configuration options:
8+
- `config_py_template`: control the contents of `config.py` in new profiles
9+
- `profile_directory` and `qutebrowser_config_directory`
10+
- equivalent `--profile-dir` to `--qutebrowser-config-dir`
11+
- `generate_desktop_file` and `desktop_file_directory`
12+
- whether to generate an XDG desktop entry for the profile and where to put it
13+
- `menu`: equivalent to `--menu` for `qbpm choose`
14+
- `menu_prompt`: prompt shown in most menus
15+
- see default config file for more detailed documentation
16+
17+
## other
218
- `contrib/qbpm.desktop`: add `MimeType` and `Keywords`, fix incorrect formatting of `Categories`
319
- allow help text to be slightly wider to avoid awkward line breaks
420
- macOS: fix detection of qutebrowser binary in /Applications

contrib/PKGBUILD

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ url="https://github.com/pvsr/qbpm"
88
license=('GPL-3.0-or-later')
99
sha512sums=('SKIP')
1010
arch=('any')
11-
depends=('python' 'python-click' 'python-xdg-base-dirs')
11+
depends=('python' 'python-click' 'python-xdg-base-dirs' 'python-dacite')
1212
makedepends=('git' 'python-build' 'python-installer' 'python-wheel' 'python-flit-core' 'scdoc')
1313
provides=('qbpm')
1414
source=("git+https://github.com/pvsr/qbpm")

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ classifiers = [
1515
"Typing :: Typed",
1616
]
1717
requires-python = ">= 3.11"
18-
dependencies = ["click", "xdg-base-dirs"]
18+
dependencies = [
19+
"click",
20+
"xdg-base-dirs",
21+
"dacite",
22+
]
1923

2024
[project.urls]
2125
homepage = "https://github.com/pvsr/qbpm"

qbpm.1.scd

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ qbpm - qutebrowser profile manager
66

77
# SYNOPSIS
88

9-
*qbpm* [--profile-dir=<path>|-P <path>] <command> [<args>]
9+
*qbpm* [--profile-dir=<path>|-P <path>] [--config-file|-c <path>] <command> [<args>]
1010

1111
# DESCRIPTION
1212

@@ -30,6 +30,8 @@ appropriate \--basedir, or more conveniently using the qbpm launch and qbpm choo
3030
Use _path_ as the profile directory instead of the default location. Takes
3131
precedence over the QBPM_PROFILE_DIR environment variable.
3232

33+
*-c, --config-file* <path>
34+
Read configuration for qbpm from _path_. Defaults to ~/.config/qbpm/config.toml.
3335

3436
# COMMANDS
3537

@@ -49,9 +51,9 @@ appropriate \--basedir, or more conveniently using the qbpm launch and qbpm choo
4951
Source config files from the provided directory instead of the global
5052
qutebrowser config location.
5153

52-
*--no-desktop-file*
53-
Do not generate an XDG desktop entry for the profile. Always true on
54-
non-linux systems. See https://wiki.archlinux.org/title/Desktop_entries
54+
*--desktop-file/--no-desktop-file*
55+
Whether to generate an XDG desktop entry for the profile. Only relevant
56+
on linux systems. See https://wiki.archlinux.org/title/Desktop_entries
5557
for information on desktop entries.
5658

5759
*--overwrite*

src/qbpm/choose.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99

1010
def choose_profile(
11-
profile_dir: Path, menu: str | None, foreground: bool, qb_args: tuple[str, ...]
11+
profile_dir: Path,
12+
menu: str | list[str],
13+
prompt: str,
14+
foreground: bool,
15+
qb_args: tuple[str, ...],
1216
) -> bool:
1317
dmenu = find_menu(menu)
1418
if not dmenu:
@@ -19,7 +23,7 @@ def choose_profile(
1923
error("no profiles")
2024
return False
2125
profiles = [*real_profiles, "qutebrowser"]
22-
command = dmenu.command(sorted(profiles), "qutebrowser", " ".join(qb_args))
26+
command = dmenu.command(sorted(profiles), prompt, " ".join(qb_args))
2327
selection_cmd = subprocess.run(
2428
command,
2529
text=True,

src/qbpm/config.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import os
2+
import platform
3+
import sys
4+
import tomllib
5+
from dataclasses import dataclass, field, fields
6+
from pathlib import Path
7+
8+
import dacite
9+
10+
from . import paths
11+
from .log import error, or_phrase
12+
13+
DEFAULT_CONFIG_FILE = Path(__file__).parent / "config.toml"
14+
15+
16+
@dataclass(kw_only=True)
17+
class Config:
18+
config_py_template: str | None = None
19+
qutebrowser_config_directory: Path | None = None
20+
profile_directory: Path = field(default_factory=paths.default_profile_dir)
21+
generate_desktop_file: bool = platform.system() == "Linux"
22+
desktop_file_directory: Path = field(
23+
default_factory=paths.default_qbpm_application_dir
24+
)
25+
menu: str | list[str] = field(default_factory=list)
26+
menu_prompt: str = "qutebrowser"
27+
28+
@classmethod
29+
def load(cls, config_file: Path | None) -> "Config":
30+
config_file = config_file or DEFAULT_CONFIG_FILE
31+
try:
32+
data = tomllib.loads(config_file.read_text(encoding="utf-8"))
33+
if extra := data.keys() - {field.name for field in fields(Config)}:
34+
raise RuntimeError(f'unknown config value: "{next(iter(extra))}"')
35+
return dacite.from_dict(
36+
data_class=Config,
37+
data=data,
38+
config=dacite.Config(
39+
type_hooks={Path: lambda val: Path(val).expanduser()}
40+
),
41+
)
42+
except Exception as e:
43+
error(f"loading {config_file} failed with error '{e}'")
44+
sys.exit(1)
45+
46+
47+
def find_config(config_path: Path | None) -> Config:
48+
if not config_path:
49+
default = paths.default_qbpm_config_dir() / "config.toml"
50+
if default.is_file():
51+
config_path = default
52+
elif config_path == Path(os.devnull):
53+
config_path = None
54+
elif not config_path.is_file():
55+
error(f"{config_path} is not a file")
56+
sys.exit(1)
57+
return Config.load(config_path)
58+
59+
60+
def find_qutebrowser_config_dir(qb_config_dir: Path | None) -> Path | None:
61+
dirs = (
62+
[qb_config_dir, qb_config_dir / "config"]
63+
if qb_config_dir
64+
else list(paths.qutebrowser_config_dirs())
65+
)
66+
for config_dir in dirs:
67+
if (config_dir / "config.py").exists():
68+
return config_dir.absolute()
69+
error(f"couldn't find config.py in {or_phrase(dirs)}")
70+
return None

src/qbpm/config.toml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# template that new config.py files are generated from
2+
# supported placeholders: {profile_name}, {source_config_py}
3+
config_py_template = """
4+
config.source(r'{source_config_py}')
5+
6+
c.window.title_format += ' ({profile_name})'
7+
8+
config.load_autoconfig()
9+
"""
10+
11+
# location to store qutebrowser profiles
12+
# profile_directory = "~/.local/share/qutebrowser-profiles"
13+
14+
# location of the qutebrowser config to inherit from
15+
# qutebrowser_config_directory = "~/.config/qutebrowser"
16+
17+
# when creating a profile also generate an XDG desktop entry that launches the profile
18+
# defaults to true on linux
19+
generate_desktop_file = true
20+
# desktop_file_directory = "~/.local/share/applications/qbpm"
21+
22+
# profile selection menu for `qbpm choose`
23+
# when not set, qbpm will try to find a menu program on your $PATH
24+
# run `qbpm choose --help` for a list of known menu programs
25+
# if menu is a known menu, dmenu-mode flags are set automatically
26+
# menu = "fuzzel" # gets turned into "fuzzel --dmenu", /path/to/fuzzel also works
27+
# otherwise menu must be a dmenu-compatible commandline
28+
# supported placeholders: {prompt}, {qb_args}
29+
# menu = "~/bin/my-dmenu"
30+
# menu = "fuzzel --dmenu --prompt '{prompt}> ' --lines 20 --width 50"
31+
# optionally menu can be written as a list to simplify quoting
32+
# menu = ["fuzzel", "--dmenu", "--prompt", "{prompt}> ", "--lines", "20", "--width", "50"]
33+
34+
# value of {prompt} in menu commands
35+
# supported placeholders: {qb_args}
36+
# defaults to "qutebrowser"
37+
# menu_prompt = "qbpm"
38+
# menu_prompt = "profiles"
39+
# menu_prompt = "qutebrowser {qb_args}"

src/qbpm/desktop.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from pathlib import Path
33

44
from . import Profile
5-
from .paths import default_qbpm_application_dir
65

76
MIME_TYPES = [
87
"text/html",
@@ -19,8 +18,7 @@
1918
]
2019

2120

22-
# TODO expose application_dir through config
23-
def create_desktop_file(profile: Profile, application_dir: Path | None = None) -> None:
21+
def create_desktop_file(profile: Profile, application_dir: Path) -> None:
2422
text = textwrap.dedent(f"""\
2523
[Desktop Entry]
2624
Name={profile.name} (qutebrowser profile)
@@ -44,5 +42,5 @@ def create_desktop_file(profile: Profile, application_dir: Path | None = None) -
4442
Name=Preferences
4543
Exec={" ".join([*profile.cmdline(), '"qute://settings"'])}
4644
""")
47-
application_dir = application_dir or default_qbpm_application_dir()
45+
application_dir.mkdir(parents=True, exist_ok=True)
4846
(application_dir / f"{profile.name}.desktop").write_text(text)

0 commit comments

Comments
 (0)