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()