diff --git a/CHANGELOG.md b/CHANGELOG.md index 524b7b2..fd94c04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ qbpm now reads configuration options from a `$XDG_CONFIG_HOME/qbpm/config.toml`! - run `qbpm config default > "$(qbpm config path)"` - supported configuration options: - `config_py_template`: control the contents of `config.py` in new profiles + - `symlink_autoconfig`: symlink qutebrowser's `autoconfig.yml` in new profiles - `profile_directory` and `qutebrowser_config_directory` - equivalent `--profile-dir` to `--qutebrowser-config-dir` - `generate_desktop_file` and `desktop_file_directory` @@ -15,6 +16,7 @@ qbpm now reads configuration options from a `$XDG_CONFIG_HOME/qbpm/config.toml`! - see default config file for more detailed documentation ## other + - support for symlinking `autoconfig.yml` in addition to or instead of sourcing `config.py` - `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/src/qbpm/config.py b/src/qbpm/config.py index d746c9c..274404f 100644 --- a/src/qbpm/config.py +++ b/src/qbpm/config.py @@ -16,6 +16,7 @@ DEFAULT_CONFIG_FILE = Path(__file__).parent / "config.toml" @dataclass(kw_only=True) class Config: config_py_template: str | None = None + symlink_autoconfig: bool = False qutebrowser_config_directory: Path | None = None profile_directory: Path = field(default_factory=paths.default_profile_dir) generate_desktop_file: bool = platform.system() == "Linux" @@ -57,14 +58,21 @@ def find_config(config_path: Path | None) -> Config: return Config.load(config_path) -def find_qutebrowser_config_dir(qb_config_dir: Path | None) -> Path | None: +def find_qutebrowser_config_dir( + qb_config_dir: Path | None, autoconfig: bool = False +) -> 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(): + if (config_dir / "config.py").exists() or ( + autoconfig and (config_dir / "autoconfig.yml").exists() + ): return config_dir.absolute() - error(f"couldn't find config.py in {or_phrase(dirs)}") + if autoconfig: + error(f"couldn't find config.py or autoconfig.yml in {or_phrase(dirs)}") + else: + 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 index a6c6ea7..b0e3622 100644 --- a/src/qbpm/config.toml +++ b/src/qbpm/config.toml @@ -8,6 +8,9 @@ c.window.title_format += ' ({profile_name})' config.load_autoconfig() """ +# symlink autoconfig.yml in new profiles if the os supports it +# symlink_autoconfig = false + # location to store qutebrowser profiles # profile_directory = "~/.local/share/qutebrowser-profiles" diff --git a/src/qbpm/profiles.py b/src/qbpm/profiles.py index c0c599f..c75fcef 100644 --- a/src/qbpm/profiles.py +++ b/src/qbpm/profiles.py @@ -4,7 +4,7 @@ from pathlib import Path from . import Profile from .config import Config, find_qutebrowser_config_dir from .desktop import create_desktop_file -from .log import error +from .log import error, info MIME_TYPES = [ "text/html", @@ -31,7 +31,6 @@ def create_profile(profile: Profile, overwrite: bool = False) -> bool: config_dir = profile.root / "config" config_dir.mkdir(parents=True, exist_ok=overwrite) - print(profile.root) return True @@ -42,13 +41,16 @@ def create_config( home_page: str | None = None, overwrite: bool = False, ) -> None: + source = qb_config_dir / "config.py" + if not source.is_file(): + return 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_py_template.format( profile_name=profile.name, - source_config_py=qb_config_dir / "config.py", + source_config_py=source, ) ) # TODO move to template? @@ -56,6 +58,24 @@ def create_config( out(f"c.url.start_pages = ['{home_page}']") +def link_autoconfig( + profile: Profile, + qb_config_dir: Path, + overwrite: bool = False, +) -> None: + if not hasattr(Path, "symlink_to"): + return + source = qb_config_dir / "autoconfig.yml" + dest = profile.root / "config" / "autoconfig.yml" + if not source.is_file() or dest.resolve() == source.resolve(): + return + if overwrite and dest.exists(): + backup = Path(str(dest) + ".bak") + info(f"backing up existing autoconfig to {backup}") + dest.replace(backup) + dest.symlink_to(source) + + def check(profile: Profile) -> bool: if not profile.check_name(): return False @@ -82,7 +102,9 @@ def new_profile( 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) + qb_config_dir = find_qutebrowser_config_dir( + qb_config_dir, config.symlink_autoconfig + ) if not qb_config_dir: return False if not config.config_py_template: @@ -92,7 +114,10 @@ def new_profile( create_config( profile, qb_config_dir, config.config_py_template, home_page, overwrite ) + if config.symlink_autoconfig: + link_autoconfig(profile, qb_config_dir, overwrite) if config.generate_desktop_file: create_desktop_file(profile, config.desktop_file_directory) + print(profile.root) return True return False diff --git a/tests/test_config.py b/tests/test_config.py index 0335c87..b64420d 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -38,6 +38,7 @@ def test_full_config(tmp_path: Path): config_py_template = \""" config.load_autoconfig() \""" +symlink_autoconfig = true qutebrowser_config_directory = "~/.config/qutebrowser" profile_directory = "profile" generate_desktop_file = false @@ -47,6 +48,7 @@ menu_prompt = "qbpm" """) assert find_config(file) == Config( config_py_template="config.load_autoconfig()\n", + symlink_autoconfig=True, qutebrowser_config_directory=Path("~/.config/qutebrowser").expanduser(), profile_directory=Path("profile"), desktop_file_directory=Path("desktop"), @@ -65,6 +67,14 @@ def test_find_qb_config(tmp_path: Path): assert find_qutebrowser_config_dir(qb_dir / "config") == qb_conf_dir +def test_find_autoconfig(tmp_path: Path): + qb_dir = tmp_path / "qb" + qb_conf_dir = qb_dir / "config" + qb_conf_dir.mkdir(parents=True) + (qb_conf_dir / "autoconfig.yml").touch() + assert find_qutebrowser_config_dir(qb_dir, autoconfig=True) == qb_conf_dir + + def test_find_qb_config_default(tmp_path: Path): (tmp_path / "config.py").touch() assert find_qutebrowser_config_dir(None) == tmp_path diff --git a/tests/test_profiles.py b/tests/test_profiles.py index 14c8a7f..3b7c3c9 100644 --- a/tests/test_profiles.py +++ b/tests/test_profiles.py @@ -54,14 +54,18 @@ def test_create_profile_nested_conflict(tmp_path: Path): def test_create_config(tmp_path: Path): + (tmp_path / "config.py").touch() profile = Profile("test", tmp_path) config_dir = profile.root / "config" config_dir.mkdir(parents=True) - profiles.create_config(profile, tmp_path, "") - assert list(config_dir.iterdir()) == [config_dir / "config.py"] + profiles.create_config(profile, tmp_path, "{source_config_py}") + config = config_dir / "config.py" + assert list(config_dir.iterdir()) == [config] + assert str(tmp_path / "config.py") in config.read_text() def test_overwrite_config(tmp_path: Path): + (tmp_path / "config.py").touch() profile = Profile("test", tmp_path) url = "http://example.com" config_dir = profile.root / "config" @@ -76,6 +80,41 @@ def test_overwrite_config(tmp_path: Path): raise AssertionError() +def test_link_autoconfig(tmp_path: Path): + profile = Profile("test", tmp_path) + config_dir = profile.root / "config" + config_dir.mkdir(parents=True) + (tmp_path / "autoconfig.yml").touch() + profiles.link_autoconfig(profile, tmp_path, False) + config = config_dir / "autoconfig.yml" + assert list(config_dir.iterdir()) == [config] + assert config.resolve().parent == tmp_path + + +def test_autoconfig_present(tmp_path: Path): + profile = Profile("test", tmp_path) + config_dir = profile.root / "config" + config_dir.mkdir(parents=True) + (tmp_path / "autoconfig.yml").touch() + profiles.link_autoconfig(profile, tmp_path, False) + profiles.link_autoconfig(profile, tmp_path, False) + config = config_dir / "autoconfig.yml" + assert list(config_dir.iterdir()) == [config] + assert config.resolve().parent == tmp_path + + +def test_overwrite_autoconfig(tmp_path: Path): + profile = Profile("test", tmp_path) + config_dir = profile.root / "config" + config_dir.mkdir(parents=True) + (config_dir / "autoconfig.yml").touch() + (tmp_path / "autoconfig.yml").touch() + profiles.link_autoconfig(profile, tmp_path, True) + config = config_dir / "autoconfig.yml" + assert set(config_dir.iterdir()) == {config, config_dir / "autoconfig.yml.bak"} + assert config.resolve().parent == tmp_path + + def test_new_profile(tmp_path: Path): (tmp_path / "config.py").touch() profile = Profile("test", tmp_path / "test") @@ -86,16 +125,41 @@ def test_new_profile(tmp_path: Path): check_new_profile(profile) +def test_new_profile_autoconfig(tmp_path: Path): + (tmp_path / "autoconfig.yml").touch() + profile = Profile("test", tmp_path / "test") + config = Config.load(None) + config.qutebrowser_config_directory = tmp_path + config.generate_desktop_file = False + config.symlink_autoconfig = True + profiles.new_profile(profile, config) + config_dir = profile.root / "config" + assert set(config_dir.iterdir()) == {config_dir / "autoconfig.yml"} + + +def test_new_profile_both(tmp_path: Path): + (tmp_path / "config.py").touch() + (tmp_path / "autoconfig.yml").touch() + profile = Profile("test", tmp_path / "test") + config = Config.load(None) + config.qutebrowser_config_directory = tmp_path + config.generate_desktop_file = False + config.symlink_autoconfig = True + profiles.new_profile(profile, config) + assert len(set((profile.root / "config").iterdir())) == 2 # noqa: PLR2004 + + def test_config_template(tmp_path: Path): + (tmp_path / "config.py").touch() profile = Profile("test", tmp_path) + config_dir = profile.root / "config" + config_dir.mkdir(parents=True) template = "# Profile: {profile_name}\nconfig.source('{source_config_py}')" - profiles.create_profile(profile) - profiles.create_config(profile, tmp_path / "config", template) - + profiles.create_config(profile, tmp_path, template) config_content = (profile.root / "config" / "config.py").read_text() assert "# Profile: test" in config_content - assert f"config.source('{tmp_path / 'config' / 'config.py'}')" in config_content + assert f"config.source('{tmp_path / 'config.py'}')" in config_content def test_missing_qb_config(tmp_path: Path):