qutebrowser profile manager
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

make profile_dir part of Profile

+63 -82
-5
qpm/config.py
··· 1 - from pathlib import Path 2 - 3 - from xdg import BaseDirectory # type: ignore 4 - 5 - profiles_dir = Path(BaseDirectory.xdg_data_home) / "qutebrowser-profiles"
+19 -28
qpm/main.py
··· 1 1 import argparse 2 - import sys 2 + from argparse import Namespace 3 3 from pathlib import Path 4 - from typing import Callable, Optional 4 + from typing import Callable, Optional, Set 5 5 6 - from qpm import config, operations, profiles 6 + from qpm import operations, profiles 7 7 from qpm.profiles import Profile 8 - from qpm.utils import error 9 8 10 9 11 10 def main() -> None: 12 - parser = argparse.ArgumentParser(description="Qutebrowser profile manager") 13 - parser.set_defaults(operation=lambda args: parser.print_help()) 11 + parser = argparse.ArgumentParser(description="qutebrowser profile manager") 12 + parser.set_defaults(operation=parser.print_help) 14 13 parser.add_argument( 15 14 "-P", 16 15 "--profile-dir", ··· 21 20 22 21 subparsers = parser.add_subparsers() 23 22 new = subparsers.add_parser("new", help="create a new profile") 24 - new.set_defaults( 25 - operation=lambda args: wrap_op(args.profile_name, profiles.new_profile) 26 - ) 23 + new.set_defaults(operation=wrap_op(profiles.new_profile, set())) 27 24 new.add_argument("profile_name", metavar="name", help="name of the new profile") 28 25 creator_args(new) 29 26 30 27 session = subparsers.add_parser( 31 28 "from-session", help="create a new profile from a qutebrowser session" 32 29 ) 33 - session.set_defaults( 34 - operation=lambda args: operations.from_session(args.session, args.profile_name), 35 - ) 30 + session.set_defaults(operation=lambda args: operations.from_session(**vars(args))) 36 31 session.add_argument( 37 32 "session", help="session to create a new profile from", 38 33 ) ··· 48 43 "launch", aliases=["run"], help="launch qutebrowser with the given profile" 49 44 ) 50 45 launch.set_defaults( 51 - operation=lambda args: wrap_op( 52 - args.profile_name, 53 - lambda profile: operations.launch( 54 - profile, args.strict, args.foreground, args.qb_args or [] 55 - ), 56 - ) 46 + operation=wrap_op(operations.launch, {"strict", "foreground", "qb_args"}) 57 47 ) 58 48 launch.add_argument( 59 49 "profile_name", ··· 77 67 raw_args = parser.parse_known_args() 78 68 args = raw_args[0] 79 69 args.qb_args = raw_args[1] 80 - if args.profile_dir: 81 - if not args.profile_dir.is_dir(): 82 - error(f"{args.profile_dir} is not a directory") 83 - sys.exit(1) 84 - config.profiles_dir = args.profile_dir 85 70 args.operation(args) 86 71 87 72 ··· 96 81 parser.set_defaults( 97 82 strict=True, foreground=False, 98 83 ) 99 - 100 - 101 - def wrap_op(profile_name: str, op: Callable[[Profile], bool]) -> Optional[Profile]: 102 - profile = Profile(profile_name) 103 - return profile if op(profile) else None 104 84 105 85 106 86 class ThenLaunchAction(argparse.Action): ··· 124 104 if profile: 125 105 return operations.launch(profile, args.strict, args.foreground, []) 126 106 return False 107 + 108 + 109 + def wrap_op( 110 + op: Callable[..., bool], wanted: Set[str] 111 + ) -> Callable[[Namespace], Optional[Profile]]: 112 + def f(args) -> Optional[Profile]: 113 + profile = Profile(args.profile_name, args.profile_dir) 114 + kwargs = {k: v for (k, v) in vars(args).items() if k in wanted} 115 + return profile if op(profile=profile, **kwargs) else None 116 + 117 + return f 127 118 128 119 129 120 if __name__ == "__main__":
+5 -5
qpm/operations.py
··· 3 3 import subprocess 4 4 from typing import Iterable, Optional 5 5 6 - from qpm import config, profiles 6 + from qpm import profiles 7 7 from qpm.profiles import Profile 8 8 from qpm.utils import error 9 9 ··· 16 16 error(f"{session} is not a file") 17 17 return None 18 18 19 - profile = Profile(profile_name or session_name) 19 + profile = Profile(profile_name or session_name, None) 20 20 if not profiles.new_profile(profile): 21 21 return None 22 22 ··· 28 28 29 29 30 30 def launch( 31 - profile: Profile, strict: bool, foreground: bool, args: Iterable[str] 31 + profile: Profile, strict: bool, foreground: bool, qb_args: Iterable[str] 32 32 ) -> bool: 33 33 if not profiles.ensure_profile_exists(profile, not strict): 34 34 return False 35 35 36 36 if foreground: 37 - os.execlp("qutebrowser", "qutebrowser", "-B", str(profile.root), *args) 37 + os.execlp("qutebrowser", "qutebrowser", "-B", str(profile.root), *qb_args) 38 38 else: 39 39 p = subprocess.Popen( 40 - ["qutebrowser", "-B", str(profile.root), *args], 40 + ["qutebrowser", "-B", str(profile.root), *qb_args], 41 41 stdout=subprocess.DEVNULL, 42 42 stderr=subprocess.PIPE, 43 43 )
+26 -20
qpm/profiles.py
··· 1 1 import platform 2 2 import sys 3 3 from pathlib import Path 4 + from typing import Optional 4 5 5 6 from xdg import BaseDirectory # type: ignore 6 7 7 - from qpm import config 8 8 from qpm.utils import error 9 9 10 10 11 11 class Profile: 12 12 name: str 13 + profile_dir: Path 13 14 root: Path 14 15 15 - def __init__(self, name: str) -> None: 16 + def __init__(self, name: str, profile_dir: Optional[Path]) -> None: 16 17 self.name = name 17 - self.root = config.profiles_dir / name 18 + self.profile_dir = profile_dir or Path( 19 + BaseDirectory.save_data_path("qutebrowser-profiles") 20 + ) 21 + self.root = self.profile_dir / name 22 + 23 + def check(self) -> Optional["Profile"]: 24 + if not self.profile_dir.resolve().is_dir(): 25 + error("{self.profile_dir} is not a directory") 26 + return None 27 + if self.profile_dir.resolve() not in self.root.resolve().parents: 28 + error("will not create profile outside of profile dir. consider using -P") 29 + return None 30 + if self.root.exists(): 31 + error(f"{self.root} already exists") 32 + return None 33 + for parent in self.root.parents: 34 + if parent == self.profile_dir: 35 + break 36 + if parent.exists(): 37 + error(f"{parent} already exists") 38 + return None 39 + return self 18 40 19 41 20 42 main_config_dir = Path(BaseDirectory.xdg_data_home) / "qutebrowser" ··· 28 50 sys.exit(1) 29 51 30 52 31 - def check_profile(profile_root: Path) -> bool: 32 - if config.profiles_dir.resolve() not in profile_root.resolve().parents: 33 - error("will not create profile outside of profile dir. consider using -P") 34 - return False 35 - if profile_root.exists(): 36 - error(f"{profile_root} already exists") 37 - return False 38 - for parent in profile_root.parents: 39 - if parent == config.profiles_dir: 40 - break 41 - if parent.exists(): 42 - error(f"{parent} already exists") 43 - return False 44 - return True 45 - 46 - 47 53 def create_profile(profile: Profile) -> bool: 48 - if not check_profile(profile.root): 54 + if not profile.check(): 49 55 return False 50 56 51 57 config_dir = profile.root / "config"
+13 -24
tests/test_profiles.py
··· 1 1 from pathlib import Path 2 2 from typing import Optional 3 3 4 - from qpm import config, profiles 4 + from qpm import profiles 5 5 from qpm.profiles import Profile 6 6 7 7 ··· 24 24 25 25 26 26 def test_set_profile(tmp_path: Path): 27 - config.profiles_dir = tmp_path 28 - assert Profile("test").root == tmp_path / "test" 27 + assert Profile("test", tmp_path).root == tmp_path / "test" 29 28 30 29 31 30 def test_create_profile(tmp_path: Path): 32 - config.profiles_dir = tmp_path 33 - profile = Profile("test") 31 + profile = Profile("test", tmp_path) 34 32 assert profiles.create_profile(profile) 35 33 assert list(tmp_path.iterdir()) == [profile.root] 36 34 check_empty_profile(profile) 37 35 38 36 39 37 def test_create_profile_conflict(tmp_path: Path): 40 - config.profiles_dir = tmp_path 41 38 (tmp_path / "test").touch() 42 - profile = Profile("test") 39 + profile = Profile("test", tmp_path) 43 40 assert not profiles.create_profile(profile) 44 41 45 42 46 43 def test_create_profile_parent(tmp_path: Path): 47 - config.profiles_dir = tmp_path / "profiles" 48 - profile = Profile("../test") 44 + profile = Profile("../test", tmp_path / "profiles") 49 45 assert not profiles.create_profile(profile) 50 46 assert not (tmp_path / "test").exists() 51 47 52 48 53 49 def test_create_profile_nested_conflict(tmp_path: Path): 54 - config.profiles_dir = tmp_path 55 - assert profiles.create_profile(Profile("test")) 56 - assert not profiles.create_profile(Profile("test/a")) 50 + assert profiles.create_profile(Profile("test", tmp_path)) 51 + assert not profiles.create_profile(Profile("test/a", tmp_path)) 57 52 58 53 59 54 def test_create_config(tmp_path: Path): 60 - config.profiles_dir = tmp_path 61 - profile = Profile("test") 55 + profile = Profile("test", tmp_path) 62 56 config_dir = profile.root / "config" 63 57 config_dir.mkdir(parents=True) 64 58 profiles.create_config(profile) ··· 66 60 67 61 68 62 def test_ensure_profile_exists_exists(tmp_path: Path): 69 - config.profiles_dir = tmp_path 70 - profile = Profile("test") 63 + profile = Profile("test", tmp_path) 71 64 profile.root.mkdir() 72 65 assert profiles.ensure_profile_exists(profile, False) 73 66 assert profiles.ensure_profile_exists(profile, True) ··· 75 68 76 69 77 70 def test_ensure_profile_exists_does_not_exist(tmp_path: Path): 78 - config.profiles_dir = tmp_path 79 - assert not profiles.ensure_profile_exists(Profile("test"), False) 71 + assert not profiles.ensure_profile_exists(Profile("test", tmp_path), False) 80 72 check_is_empty(tmp_path) 81 73 82 74 83 75 def test_ensure_profile_exists_not_dir(tmp_path: Path): 84 - config.profiles_dir = tmp_path 85 - profile = Profile("test") 76 + profile = Profile("test", tmp_path) 86 77 profile.root.touch() 87 78 assert not profiles.ensure_profile_exists(profile, False) 88 79 assert not profiles.ensure_profile_exists(profile, True) 89 80 90 81 91 82 def test_ensure_profile_exists_create(tmp_path: Path): 92 - config.profiles_dir = tmp_path 93 - profile = Profile("test") 83 + profile = Profile("test", tmp_path) 94 84 assert profiles.ensure_profile_exists(profile, True) 95 85 check_new_profile(profile) 96 86 97 87 98 88 def test_new_profile(tmp_path: Path): 99 - config.profiles_dir = tmp_path 100 - profile = Profile("test") 89 + profile = Profile("test", tmp_path) 101 90 assert profiles.new_profile(profile) 102 91 check_new_profile(profile)