From a106d25c46ff4756a765b90b8d06516e86dacf2a Mon Sep 17 00:00:00 2001 From: Peter Rice Date: Sun, 8 Jun 2025 03:15:41 -0400 Subject: [PATCH] read configuration from a toml file --- .builds/arch.yml | 4 ++ CHANGELOG.md | 18 ++++++- contrib/PKGBUILD | 2 +- pyproject.toml | 6 ++- qbpm.1.scd | 10 ++-- src/qbpm/choose.py | 8 ++- src/qbpm/config.py | 70 ++++++++++++++++++++++++ src/qbpm/config.toml | 39 ++++++++++++++ src/qbpm/desktop.py | 6 +-- src/qbpm/main.py | 119 ++++++++++++++++++++++++++++++++--------- src/qbpm/menus.py | 54 +++++++++++-------- src/qbpm/operations.py | 10 ++-- src/qbpm/paths.py | 12 ++--- src/qbpm/profiles.py | 49 ++++++++--------- tests/test_choose.py | 2 +- tests/test_profiles.py | 12 +++-- 16 files changed, 317 insertions(+), 104 deletions(-) create mode 100644 src/qbpm/config.py create mode 100644 src/qbpm/config.toml diff --git a/.builds/arch.yml b/.builds/arch.yml index 67dcc47..750118d 100644 --- a/.builds/arch.yml +++ b/.builds/arch.yml @@ -2,12 +2,16 @@ image: archlinux sources: - https://git.sr.ht/~pvsr/qbpm - https://aur.archlinux.org/python-xdg-base-dirs.git + - https://aur.archlinux.org/python-dacite.git packages: - python-pytest tasks: - xdg-base-dirs: | cd python-xdg-base-dirs makepkg -si --noconfirm + - dacite: | + cd python-dacite + makepkg -si --noconfirm - makepkg: | cd qbpm/contrib sed -i 's|^source.*|source=("git+file:///home/build/qbpm")|' PKGBUILD diff --git a/CHANGELOG.md b/CHANGELOG.md index 844cdfc..524b7b2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,20 @@ -# next +# 2.0 +## config +qbpm now reads configuration options from a `$XDG_CONFIG_HOME/qbpm/config.toml`! + - to install the default config file: + - run `qbpm config path` and confirm that it prints out a path + - run `qbpm config default > "$(qbpm config path)"` + - supported configuration options: + - `config_py_template`: control the contents of `config.py` in new profiles + - `profile_directory` and `qutebrowser_config_directory` + - equivalent `--profile-dir` to `--qutebrowser-config-dir` + - `generate_desktop_file` and `desktop_file_directory` + - whether to generate an XDG desktop entry for the profile and where to put it + - `menu`: equivalent to `--menu` for `qbpm choose` + - `menu_prompt`: prompt shown in most menus + - see default config file for more detailed documentation + +## other - `contrib/qbpm.desktop`: add `MimeType` and `Keywords`, fix incorrect formatting of `Categories` - allow help text to be slightly wider to avoid awkward line breaks - macOS: fix detection of qutebrowser binary in /Applications diff --git a/contrib/PKGBUILD b/contrib/PKGBUILD index 352320a..21a8b51 100644 --- a/contrib/PKGBUILD +++ b/contrib/PKGBUILD @@ -8,7 +8,7 @@ url="https://github.com/pvsr/qbpm" license=('GPL-3.0-or-later') sha512sums=('SKIP') arch=('any') -depends=('python' 'python-click' 'python-xdg-base-dirs') +depends=('python' 'python-click' 'python-xdg-base-dirs' 'python-dacite') makedepends=('git' 'python-build' 'python-installer' 'python-wheel' 'python-flit-core' 'scdoc') provides=('qbpm') source=("git+https://github.com/pvsr/qbpm") diff --git a/pyproject.toml b/pyproject.toml index 7a035f6..f7fc216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,11 @@ classifiers = [ "Typing :: Typed", ] requires-python = ">= 3.11" -dependencies = ["click", "xdg-base-dirs"] +dependencies = [ + "click", + "xdg-base-dirs", + "dacite", +] [project.urls] homepage = "https://github.com/pvsr/qbpm" diff --git a/qbpm.1.scd b/qbpm.1.scd index 0a8eb6f..b2e2f52 100644 --- a/qbpm.1.scd +++ b/qbpm.1.scd @@ -6,7 +6,7 @@ qbpm - qutebrowser profile manager # SYNOPSIS -*qbpm* [--profile-dir=|-P ] [] +*qbpm* [--profile-dir=|-P ] [--config-file|-c ] [] # DESCRIPTION @@ -30,6 +30,8 @@ appropriate \--basedir, or more conveniently using the qbpm launch and qbpm choo Use _path_ as the profile directory instead of the default location. Takes precedence over the QBPM_PROFILE_DIR environment variable. +*-c, --config-file* + Read configuration for qbpm from _path_. Defaults to ~/.config/qbpm/config.toml. # COMMANDS @@ -49,9 +51,9 @@ appropriate \--basedir, or more conveniently using the qbpm launch and qbpm choo Source config files from the provided directory instead of the global qutebrowser config location. - *--no-desktop-file* - Do not generate an XDG desktop entry for the profile. Always true on - non-linux systems. See https://wiki.archlinux.org/title/Desktop_entries + *--desktop-file/--no-desktop-file* + Whether to generate an XDG desktop entry for the profile. Only relevant + on linux systems. See https://wiki.archlinux.org/title/Desktop_entries for information on desktop entries. *--overwrite* diff --git a/src/qbpm/choose.py b/src/qbpm/choose.py index 1f0938a..d545a98 100644 --- a/src/qbpm/choose.py +++ b/src/qbpm/choose.py @@ -8,7 +8,11 @@ from .menus import find_menu def choose_profile( - profile_dir: Path, menu: str | None, foreground: bool, qb_args: tuple[str, ...] + profile_dir: Path, + menu: str | list[str], + prompt: str, + foreground: bool, + qb_args: tuple[str, ...], ) -> bool: dmenu = find_menu(menu) if not dmenu: @@ -19,7 +23,7 @@ def choose_profile( error("no profiles") return False profiles = [*real_profiles, "qutebrowser"] - command = dmenu.command(sorted(profiles), "qutebrowser", " ".join(qb_args)) + command = dmenu.command(sorted(profiles), prompt, " ".join(qb_args)) selection_cmd = subprocess.run( command, text=True, diff --git a/src/qbpm/config.py b/src/qbpm/config.py new file mode 100644 index 0000000..d746c9c --- /dev/null +++ b/src/qbpm/config.py @@ -0,0 +1,70 @@ +import os +import platform +import sys +import tomllib +from dataclasses import dataclass, field, fields +from pathlib import Path + +import dacite + +from . import paths +from .log import error, or_phrase + +DEFAULT_CONFIG_FILE = Path(__file__).parent / "config.toml" + + +@dataclass(kw_only=True) +class Config: + config_py_template: str | None = None + qutebrowser_config_directory: Path | None = None + profile_directory: Path = field(default_factory=paths.default_profile_dir) + generate_desktop_file: bool = platform.system() == "Linux" + desktop_file_directory: Path = field( + default_factory=paths.default_qbpm_application_dir + ) + menu: str | list[str] = field(default_factory=list) + menu_prompt: str = "qutebrowser" + + @classmethod + def load(cls, config_file: Path | None) -> "Config": + config_file = config_file or DEFAULT_CONFIG_FILE + try: + data = tomllib.loads(config_file.read_text(encoding="utf-8")) + if extra := data.keys() - {field.name for field in fields(Config)}: + raise RuntimeError(f'unknown config value: "{next(iter(extra))}"') + return dacite.from_dict( + data_class=Config, + data=data, + config=dacite.Config( + type_hooks={Path: lambda val: Path(val).expanduser()} + ), + ) + except Exception as e: + error(f"loading {config_file} failed with error '{e}'") + sys.exit(1) + + +def find_config(config_path: Path | None) -> Config: + if not config_path: + default = paths.default_qbpm_config_dir() / "config.toml" + if default.is_file(): + config_path = default + elif config_path == Path(os.devnull): + config_path = None + elif not config_path.is_file(): + error(f"{config_path} is not a file") + sys.exit(1) + return Config.load(config_path) + + +def find_qutebrowser_config_dir(qb_config_dir: Path | None) -> Path | None: + dirs = ( + [qb_config_dir, qb_config_dir / "config"] + if qb_config_dir + else list(paths.qutebrowser_config_dirs()) + ) + for config_dir in dirs: + if (config_dir / "config.py").exists(): + return config_dir.absolute() + error(f"couldn't find config.py in {or_phrase(dirs)}") + return None diff --git a/src/qbpm/config.toml b/src/qbpm/config.toml new file mode 100644 index 0000000..a6c6ea7 --- /dev/null +++ b/src/qbpm/config.toml @@ -0,0 +1,39 @@ +# template that new config.py files are generated from +# supported placeholders: {profile_name}, {source_config_py} +config_py_template = """ +config.source(r'{source_config_py}') + +c.window.title_format += ' ({profile_name})' + +config.load_autoconfig() +""" + +# location to store qutebrowser profiles +# profile_directory = "~/.local/share/qutebrowser-profiles" + +# location of the qutebrowser config to inherit from +# qutebrowser_config_directory = "~/.config/qutebrowser" + +# when creating a profile also generate an XDG desktop entry that launches the profile +# defaults to true on linux +generate_desktop_file = true +# desktop_file_directory = "~/.local/share/applications/qbpm" + +# profile selection menu for `qbpm choose` +# when not set, qbpm will try to find a menu program on your $PATH +# run `qbpm choose --help` for a list of known menu programs +# if menu is a known menu, dmenu-mode flags are set automatically +# menu = "fuzzel" # gets turned into "fuzzel --dmenu", /path/to/fuzzel also works +# otherwise menu must be a dmenu-compatible commandline +# supported placeholders: {prompt}, {qb_args} +# menu = "~/bin/my-dmenu" +# menu = "fuzzel --dmenu --prompt '{prompt}> ' --lines 20 --width 50" +# optionally menu can be written as a list to simplify quoting +# menu = ["fuzzel", "--dmenu", "--prompt", "{prompt}> ", "--lines", "20", "--width", "50"] + +# value of {prompt} in menu commands +# supported placeholders: {qb_args} +# defaults to "qutebrowser" +# menu_prompt = "qbpm" +# menu_prompt = "profiles" +# menu_prompt = "qutebrowser {qb_args}" diff --git a/src/qbpm/desktop.py b/src/qbpm/desktop.py index 529ac76..c6a57ee 100644 --- a/src/qbpm/desktop.py +++ b/src/qbpm/desktop.py @@ -2,7 +2,6 @@ import textwrap from pathlib import Path from . import Profile -from .paths import default_qbpm_application_dir MIME_TYPES = [ "text/html", @@ -19,8 +18,7 @@ MIME_TYPES = [ ] -# TODO expose application_dir through config -def create_desktop_file(profile: Profile, application_dir: Path | None = None) -> None: +def create_desktop_file(profile: Profile, application_dir: Path) -> None: text = textwrap.dedent(f"""\ [Desktop Entry] Name={profile.name} (qutebrowser profile) @@ -44,5 +42,5 @@ def create_desktop_file(profile: Profile, application_dir: Path | None = None) - Name=Preferences Exec={" ".join([*profile.cmdline(), '"qute://settings"'])} """) - application_dir = application_dir or default_qbpm_application_dir() + application_dir.mkdir(parents=True, exist_ok=True) (application_dir / f"{profile.name}.desktop").write_text(text) diff --git a/src/qbpm/main.py b/src/qbpm/main.py index cafae53..fdadc5b 100644 --- a/src/qbpm/main.py +++ b/src/qbpm/main.py @@ -10,17 +10,25 @@ import click from . import Profile, operations, profiles from .choose import choose_profile +from .config import DEFAULT_CONFIG_FILE, Config, find_config from .launch import launch_qutebrowser from .log import error, or_phrase from .menus import supported_menus -from .paths import default_profile_dir, qutebrowser_data_dir +from .paths import default_qbpm_config_dir, qutebrowser_data_dir CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"], "max_content_width": 91} @dataclass class Context: - profile_dir: Path + cli_profile_dir: Path | None + cli_config_file: Path | None + + def load_config(self) -> Config: + config = find_config(self.cli_config_file) + if self.cli_profile_dir: + config.profile_directory = self.cli_profile_dir + return config @dataclass @@ -28,7 +36,7 @@ class CreatorOptions: qb_config_dir: Path | None launch: bool foreground: bool - desktop_file: bool + desktop_file: bool | None overwrite: bool @@ -41,7 +49,7 @@ def creator_options(orig: Callable[..., T]) -> Callable[..., T]: qb_config_dir: Path | None, launch: bool, foreground: bool, - desktop_file: bool, + desktop_file: bool | None, overwrite: bool, *args: Any, # noqa: ANN401 **kwargs: Any, # noqa: ANN401 @@ -71,12 +79,9 @@ def creator_options(orig: Callable[..., T]) -> Callable[..., T]: help="If --launch is set, run qutebrowser in the foreground.", ), click.option( - "--no-desktop-file", - "desktop_file", - default=True, - is_flag=True, - flag_value=False, - help="Do not generate an XDG desktop entry for the profile.", + "--desktop-file/--no-desktop-file", + default=None, + help="Generate an XDG desktop entry for the profile.", ), click.option( "--overwrite", @@ -106,6 +111,12 @@ class LowerCaseFormatter(logging.Formatter): default=None, help="Location to store qutebrowser profiles.", ) +@click.option( + "-c", + "--config-file", + type=click.Path(dir_okay=False, writable=True, path_type=Path), + help="Location of qbpm config file.", +) @click.option( "-l", "--log-level", @@ -113,13 +124,18 @@ class LowerCaseFormatter(logging.Formatter): type=click.Choice(["debug", "info", "error"], case_sensitive=False), ) @click.pass_context -def main(ctx: click.Context, profile_dir: Path | None, log_level: str) -> None: +def main( + ctx: click.Context, + profile_dir: Path | None, + config_file: Path | None, + log_level: str, +) -> None: root_logger = logging.getLogger() root_logger.setLevel(log_level.upper()) handler = logging.StreamHandler() handler.setFormatter(LowerCaseFormatter("{levelname}: {message}", style="{")) root_logger.addHandler(handler) - ctx.obj = Context(profile_dir or default_profile_dir()) + ctx.obj = Context(profile_dir, config_file) @main.command() @@ -134,13 +150,18 @@ def new( c_opts: CreatorOptions, ) -> None: """Create a new profile.""" - profile = Profile(profile_name, **vars(context)) + config = context.load_config() + profile = Profile(profile_name, config.profile_directory) + if c_opts.qb_config_dir: + config.qutebrowser_config_directory = c_opts.qb_config_dir.absolute() + if c_opts.desktop_file is not None: + config.generate_desktop_file = c_opts.desktop_file + exit_with( profiles.new_profile( profile, - c_opts.qb_config_dir, + config, home_page, - c_opts.desktop_file, c_opts.overwrite, ) and ((not c_opts.launch) or launch_qutebrowser(profile, c_opts.foreground)) @@ -163,13 +184,19 @@ def from_session( SESSION may be the name of a session in the global qutebrowser profile or a path to a session yaml file. """ - profile, session_path = session_info(session, profile_name, context) + config = context.load_config() + profile, session_path = session_info( + session, profile_name, config.profile_directory + ) + if c_opts.qb_config_dir: + config.qutebrowser_config_directory = c_opts.qb_config_dir.absolute() + if c_opts.desktop_file is not None: + config.generate_desktop_file = c_opts.desktop_file exit_with( operations.from_session( profile, session_path, - c_opts.qb_config_dir, - c_opts.desktop_file, + config, c_opts.overwrite, ) and ((not c_opts.launch) or launch_qutebrowser(profile, c_opts.foreground)) @@ -189,7 +216,7 @@ def launch_profile( """Launch qutebrowser with a specific profile. All QB_ARGS are passed on to qutebrowser.""" - profile = Profile(profile_name, **vars(context)) + profile = Profile(profile_name, context.load_config().profile_directory) if not profiles.check(profile): sys.exit(1) exit_with(launch_qutebrowser(profile, foreground, qb_args)) @@ -216,7 +243,16 @@ def choose( Support is built in for many X and Wayland launchers, as well as applescript dialogs. All QB_ARGS are passed on to qutebrowser. """ - exit_with(choose_profile(context.profile_dir, menu, foreground, qb_args)) + config = context.load_config() + exit_with( + choose_profile( + config.profile_directory, + menu or config.menu, + config.menu_prompt, + foreground, + qb_args, + ) + ) @main.command() @@ -224,7 +260,7 @@ def choose( @click.pass_obj def edit(context: Context, profile_name: str) -> None: """Edit a profile's config.py.""" - profile = Profile(profile_name, **vars(context)) + profile = Profile(profile_name, context.load_config().profile_directory) if not profiles.check(profile): sys.exit(1) click.edit(filename=str(profile.root / "config" / "config.py")) @@ -234,7 +270,7 @@ def edit(context: Context, profile_name: str) -> None: @click.pass_obj def list_(context: Context) -> None: """List existing profiles.""" - for profile in sorted(context.profile_dir.iterdir()): + for profile in sorted(context.load_config().profile_directory.iterdir()): print(profile.name) @@ -246,12 +282,40 @@ def desktop( profile_name: str, ) -> None: """Create an XDG desktop entry for an existing profile.""" - profile = Profile(profile_name, **vars(context)) - exit_with(operations.desktop(profile)) + config = context.load_config() + profile = Profile(profile_name, config.profile_directory) + exit_with(operations.desktop(profile, config.desktop_file_directory)) + + +@main.group(context_settings={"help_option_names": []}) +def config() -> None: + """Commands to create a qbpm config file. + + qbpm config default > "$(qbpm config path)" + """ + pass + + +@config.command() +@click.pass_obj +def path(context: Context) -> None: + """Print the location where qbpm will look for a config file.""" + if context.cli_config_file: + print(context.cli_config_file.absolute()) + else: + config_dir = default_qbpm_config_dir() + config_dir.mkdir(parents=True, exist_ok=True) + print(config_dir / "config.toml") + + +@config.command +def default() -> None: + """Print the default qbpm config file.""" + print(DEFAULT_CONFIG_FILE.read_text(), end="") def session_info( - session: str, profile_name: str | None, context: Context + session: str, profile_name: str | None, profile_dir: Path ) -> tuple[Profile, Path]: user_session_dir = qutebrowser_data_dir() / "sessions" session_paths = [] @@ -262,7 +326,10 @@ def session_info( if session_path: return ( - Profile(profile_name or session_path.stem, **vars(context)), + Profile( + profile_name or session_path.stem, + profile_dir, + ), session_path, ) tried = or_phrase([str(p.resolve()) for p in session_paths]) diff --git a/src/qbpm/menus.py b/src/qbpm/menus.py index 3f80cdb..c49f95e 100644 --- a/src/qbpm/menus.py +++ b/src/qbpm/menus.py @@ -21,6 +21,7 @@ class Dmenu: return which(self.name()) is not None def command(self, _profiles: list[str], prompt: str, qb_args: str) -> list[str]: + prompt = prompt.format(qb_args=qb_args) return [arg.format(prompt=prompt, qb_args=qb_args) for arg in self.menu_command] @@ -45,33 +46,40 @@ item 1 of profile""", ] -def find_menu(menu: str | None) -> Dmenu | ApplescriptMenu | None: +def find_menu(menu: str | list[str] | None) -> Dmenu | ApplescriptMenu | None: + if menu: + dmenu = custom_dmenu(menu) + if not dmenu.installed(): + error(f"{dmenu.name()} not found") + return None + return dmenu menus = list(supported_menus()) - if not menu: - found = next(filter(lambda m: m.installed(), menus), None) - if not found: - error( - "no menu program found, use --menu to provide a dmenu-compatible menu or install one of " - + or_phrase([m.name() for m in menus if isinstance(m, Dmenu)]) - ) - return found - dmenu = custom_dmenu(menu) - if not dmenu.installed(): - error(f"{dmenu.name()} not found") - return None - return dmenu - - -def custom_dmenu(command: str) -> Dmenu: - split = shlex.split(command) + found = next(filter(lambda m: m.installed(), menus), None) + if not found: + error( + "no menu program found, use --menu to provide a dmenu-compatible menu or install one of " + + or_phrase([m.name() for m in menus if isinstance(m, Dmenu)]) + ) + return found + + +def custom_dmenu(command: str | list[str]) -> Dmenu: + split = shlex.split(command) if isinstance(command, str) else command if len(split) == 1 or not split[1]: - name = Path(command).name + command_path = Path(split[0]) + name = command_path.name for menu in supported_menus(): if isinstance(menu, Dmenu) and menu.name() == name: return ( menu - if name == command - else replace(menu, menu_command=[command, *menu.menu_command[1::]]) + if name == split[0] + else replace( + menu, + menu_command=[ + str(command_path.expanduser()), + *menu.menu_command[1::], + ], + ) ) return Dmenu(split) @@ -83,8 +91,8 @@ def supported_menus() -> Iterator[Dmenu | ApplescriptMenu]: yield from [ # default window is too narrow for a long prompt Dmenu(["fuzzel", "--dmenu"]), - Dmenu(["walker", "--dmenu", "--placeholder", "{prompt} {qb_args}"]), - Dmenu(["wofi", "--dmenu", "--prompt", "{prompt} {qb_args}"]), + Dmenu(["walker", "--dmenu", "--placeholder", "{prompt}"]), + Dmenu(["wofi", "--dmenu", "--prompt", "{prompt}"]), Dmenu(["tofi", "--prompt-text", "{prompt}> "]), Dmenu(["wmenu", "-p", "{prompt}"]), Dmenu(["dmenu-wl", "--prompt", "{prompt}"]), diff --git a/src/qbpm/operations.py b/src/qbpm/operations.py index f5f90d6..3823b18 100644 --- a/src/qbpm/operations.py +++ b/src/qbpm/operations.py @@ -2,17 +2,17 @@ import shutil from pathlib import Path from . import Profile, profiles +from .config import Config from .desktop import create_desktop_file def from_session( profile: Profile, session_path: Path, - qb_config_dir: Path | None, - desktop_file: bool = True, + config: Config, overwrite: bool = False, ) -> bool: - if not profiles.new_profile(profile, qb_config_dir, None, desktop_file, overwrite): + if not profiles.new_profile(profile, config, None, overwrite): return False session_dir = profile.root / "data" / "sessions" @@ -22,8 +22,8 @@ def from_session( return True -def desktop(profile: Profile) -> bool: +def desktop(profile: Profile, application_dir: Path) -> bool: exists = profiles.check(profile) if exists: - create_desktop_file(profile) + create_desktop_file(profile, application_dir) return exists diff --git a/src/qbpm/paths.py b/src/qbpm/paths.py index 48addbc..14ab4d9 100644 --- a/src/qbpm/paths.py +++ b/src/qbpm/paths.py @@ -14,16 +14,16 @@ def qutebrowser_exe() -> str: return "qutebrowser" +def default_qbpm_config_dir() -> Path: + return xdg_config_home() / "qbpm" + + def default_qbpm_application_dir() -> Path: - path = xdg_data_home() / "applications" / "qbpm" - path.mkdir(parents=True, exist_ok=True) - return path + return xdg_data_home() / "applications" / "qbpm" def default_profile_dir() -> Path: - path = xdg_data_home() / "qutebrowser-profiles" - path.mkdir(parents=True, exist_ok=True) - return path + return xdg_data_home() / "qutebrowser-profiles" def qutebrowser_data_dir() -> Path: diff --git a/src/qbpm/profiles.py b/src/qbpm/profiles.py index d74acdb..c0c599f 100644 --- a/src/qbpm/profiles.py +++ b/src/qbpm/profiles.py @@ -1,11 +1,10 @@ from functools import partial from pathlib import Path -from sys import platform from . import Profile +from .config import Config, find_qutebrowser_config_dir from .desktop import create_desktop_file -from .log import error, or_phrase -from .paths import qutebrowser_config_dirs +from .log import error MIME_TYPES = [ "text/html", @@ -39,18 +38,22 @@ def create_profile(profile: Profile, overwrite: bool = False) -> bool: def create_config( profile: Profile, qb_config_dir: Path, + config_py_template: str, home_page: str | None = None, overwrite: bool = False, ) -> None: user_config = profile.root / "config" / "config.py" with user_config.open(mode="w" if overwrite else "x") as dest_config: out = partial(print, file=dest_config) - out("config.load_autoconfig()") - title_prefix = "{perc}{current_title}{title_sep}" - out(f"c.window.title_format = '{title_prefix} qutebrowser ({profile.name})'") + out( + config_py_template.format( + profile_name=profile.name, + source_config_py=qb_config_dir / "config.py", + ) + ) + # TODO move to template? if home_page: out(f"c.url.start_pages = ['{home_page}']") - out(f"config.source(r'{qb_config_dir / 'config.py'}')") def check(profile: Profile) -> bool: @@ -71,31 +74,25 @@ def check(profile: Profile) -> bool: def new_profile( profile: Profile, - qb_config_dir: Path | None, + config: Config, home_page: str | None = None, - desktop_file: bool | None = None, overwrite: bool = False, ) -> bool: + qb_config_dir = config.qutebrowser_config_directory + if qb_config_dir and not qb_config_dir.is_dir(): + error(f"{qb_config_dir} is not a directory") + return False qb_config_dir = find_qutebrowser_config_dir(qb_config_dir) if not qb_config_dir: return False + if not config.config_py_template: + error("no value for config_py_template in config.toml") + return False if create_profile(profile, overwrite): - create_config(profile, qb_config_dir, home_page, overwrite) - if desktop_file is True or (desktop_file is not False and platform == "linux"): - create_desktop_file(profile) + create_config( + profile, qb_config_dir, config.config_py_template, home_page, overwrite + ) + if config.generate_desktop_file: + create_desktop_file(profile, config.desktop_file_directory) return True return False - - -def find_qutebrowser_config_dir(qb_config_dir: Path | None) -> Path | None: - config_file = "config.py" - dirs = ( - [qb_config_dir, qb_config_dir / "config"] - if qb_config_dir - else list(qutebrowser_config_dirs()) - ) - for config_dir in dirs: - if (config_dir / config_file).exists(): - return config_dir.absolute() - error(f"could not find {config_file} in {or_phrase(dirs)}") - return None diff --git a/tests/test_choose.py b/tests/test_choose.py index 6b03aae..ec3a60c 100644 --- a/tests/test_choose.py +++ b/tests/test_choose.py @@ -29,7 +29,7 @@ def test_choose(tmp_path: Path): profile_dir.mkdir() (profile_dir / "p1").mkdir() (profile_dir / "p2").mkdir() - assert choose_profile(profile_dir, str(menu), False, ()) + assert choose_profile(profile_dir, str(menu), "", False, ()) assert log.read_text().startswith( f"""p1 p2 diff --git a/tests/test_profiles.py b/tests/test_profiles.py index 5ef1ef8..1e69997 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -1,6 +1,7 @@ from pathlib import Path from qbpm import profiles +from qbpm.config import Config from qbpm.profiles import Profile from . import no_homedir_fixture # noqa: F401 @@ -56,7 +57,7 @@ def test_create_config(tmp_path: Path): profile = Profile("test", tmp_path) config_dir = profile.root / "config" config_dir.mkdir(parents=True) - profiles.create_config(profile, tmp_path) + profiles.create_config(profile, tmp_path, "") assert list(config_dir.iterdir()) == [config_dir / "config.py"] @@ -65,8 +66,8 @@ def test_overwrite_config(tmp_path: Path): url = "http://example.com" config_dir = profile.root / "config" config_dir.mkdir(parents=True) - profiles.create_config(profile, tmp_path) - profiles.create_config(profile, tmp_path, url, True) + profiles.create_config(profile, tmp_path, "") + profiles.create_config(profile, tmp_path, "", url, True) assert list(config_dir.iterdir()) == [config_dir / "config.py"] with (config_dir / "config.py").open() as conf: for line in conf: @@ -78,5 +79,8 @@ def test_overwrite_config(tmp_path: Path): def test_new_profile(tmp_path: Path): (tmp_path / "config.py").touch() profile = Profile("test", tmp_path / "test") - assert profiles.new_profile(profile, tmp_path, desktop_file=False) + config = Config.load(None) + config.qutebrowser_config_directory = tmp_path + config.generate_desktop_file = False + assert profiles.new_profile(profile, config) check_new_profile(profile) -- 2.51.2