qutebrowser profile manager
at 0.2 164 lines 5.4 kB view raw
1import argparse 2from os import environ 3from pathlib import Path 4from typing import Any, Callable, Optional 5 6from qpm import operations, profiles 7from qpm.profiles import Profile 8 9 10def main(mock_args=None) -> None: 11 parser = argparse.ArgumentParser(description="qutebrowser profile manager") 12 parser.set_defaults(operation=lambda args: parser.print_help()) 13 parser.add_argument( 14 "-P", 15 "--profile-dir", 16 metavar="directory", 17 type=Path, 18 help="directory in which profiles are stored", 19 ) 20 parser.add_argument( 21 "--set-app-id", 22 action="store_true", 23 help="set wayland app_id to this profile's name. requires a recent qutebrowser version that supports --desktop-file-name (unreleased as of writing)", 24 ) 25 26 subparsers = parser.add_subparsers() 27 new = subparsers.add_parser("new", help="create a new profile") 28 new.add_argument("profile_name", metavar="profile", help="name of the new profile") 29 new.add_argument("home_page", metavar="url", nargs="?", help="profile's home page") 30 new.set_defaults( 31 operation=lambda args: profiles.new_profile( 32 build_profile(args), 33 args.home_page, 34 args.desktop_file, 35 ) 36 ) 37 creator_args(new) 38 39 session = subparsers.add_parser( 40 "from-session", help="create a new profile from a qutebrowser session" 41 ) 42 session.add_argument( 43 "session", 44 help="path to session file or name of session. " 45 "e.g. ~/.local/share/qutebrowser/sessions/example.yml or example", 46 ) 47 session.add_argument( 48 "profile_name", 49 metavar="profile", 50 nargs="?", 51 help="name of the new profile. if unset the session name will be used", 52 ) 53 session.set_defaults( 54 operation=lambda args: operations.from_session( 55 args.session, args.profile_name, args.profile_dir, args.desktop_file 56 ) 57 ) 58 creator_args(session) 59 60 desktop = subparsers.add_parser( 61 "desktop", help="create a desktop file for an existing profile" 62 ) 63 desktop.add_argument( 64 "profile_name", metavar="profile", help="profile to create a desktop file for" 65 ) 66 desktop.set_defaults(operation=lambda args: operations.desktop(build_profile(args))) 67 68 launch = subparsers.add_parser( 69 "launch", aliases=["run"], help="launch qutebrowser with the given profile" 70 ) 71 launch.add_argument( 72 "profile_name", 73 metavar="profile", 74 help="profile to launch. it will be created if it does not exist, unless -s is set", 75 ) 76 launch.add_argument( 77 "-n", 78 "--new", 79 action="store_false", 80 dest="strict", 81 help="create the profile if it doesn't exist", 82 ) 83 launch.add_argument( 84 "-f", 85 "--foreground", 86 action="store_true", 87 help="launch qutebrowser in the foreground and print its stdout and stderr to the console", 88 ) 89 launch.set_defaults( 90 operation=lambda args: operations.launch( 91 build_profile(args), args.strict, args.foreground, args.qb_args 92 ) 93 ) 94 95 list_ = subparsers.add_parser("list", help="list existing profiles") 96 list_.set_defaults(operation=lambda args: operations.list_()) 97 98 edit = subparsers.add_parser( 99 "edit", help="edit a profile's config.py using $EDITOR" 100 ) 101 edit.add_argument("profile_name", metavar="profile", help="profile to edit") 102 edit.set_defaults(operation=lambda args: operations.edit(build_profile(args))) 103 104 raw_args = parser.parse_known_args(mock_args) 105 args = raw_args[0] 106 args.qb_args = raw_args[1] 107 if not args.profile_dir and (env_dir := environ.get("QPM_PROFILE_DIR")): 108 args.profile_dir = Path(env_dir) 109 args.operation(args) 110 111 112def creator_args(parser: argparse.ArgumentParser) -> None: 113 parser.add_argument( 114 "-l", 115 "--launch", 116 action=ThenLaunchAction, 117 dest="operation", 118 help="launch the profile after creating", 119 ) 120 parser.add_argument( 121 "-f", 122 "--foreground", 123 action="store_true", 124 help="if --launch is set, launch qutebrowser in the foreground", 125 ) 126 parser.add_argument( 127 "--no-desktop-file", 128 dest="desktop_file", 129 action="store_false", 130 help="do not generate a desktop file for the profile", 131 ) 132 parser.set_defaults(strict=True) 133 134 135class ThenLaunchAction(argparse.Action): 136 def __init__(self, option_strings, dest, nargs=0, **kwargs): 137 super(ThenLaunchAction, self).__init__( 138 option_strings, dest, nargs=nargs, **kwargs 139 ) 140 141 def __call__(self, parser, namespace, values, option_string=None): 142 if operation := getattr(namespace, self.dest): 143 setattr(namespace, self.dest, lambda args: then_launch(args, operation)) 144 145 146def then_launch( 147 args: argparse.Namespace, 148 operation: Callable[[argparse.Namespace], Optional[Any]], 149) -> bool: 150 if result := operation(args): 151 if isinstance(result, Profile): 152 profile = result 153 else: 154 profile = build_profile(args) 155 return operations.launch(profile, False, args.foreground, []) 156 return False 157 158 159def build_profile(args: argparse.Namespace) -> Profile: 160 return Profile(args.profile_name, args.profile_dir, args.set_app_id) 161 162 163if __name__ == "__main__": 164 main()