Skip to content

Commit 3d0a825

Browse files
committed
mostly functional click setup
1 parent bf1a896 commit 3d0a825

File tree

5 files changed

+129
-195
lines changed

5 files changed

+129
-195
lines changed

default.nix

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ with pythonPackages;
1111
doCheck = true;
1212
SETUPTOOLS_SCM_PRETEND_VERSION = version;
1313
nativeBuildInputs = [pkgs.scdoc setuptools-scm];
14-
propagatedBuildInputs = [pyxdg];
14+
propagatedBuildInputs = [pyxdg click];
1515
checkInputs = [pytest];
1616
postInstall = ''
1717
mkdir -p $out/share/fish/vendor_completions.d

flake.nix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
(pkgs.python3.withPackages (ps:
2121
with ps; [
2222
pyxdg
23+
click
2324
setuptools-scm
2425
pytest
2526
pylint

qbpm/main.py

Lines changed: 113 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,178 +1,125 @@
1-
import argparse
21
import inspect
32
from os import environ
43
from pathlib import Path
54
from typing import Any, Callable, Optional
65

6+
import click
77
from xdg import BaseDirectory
88

99
from . import __version__, operations, profiles
1010
from .profiles import Profile
11-
from .utils import SUPPORTED_MENUS, error
12-
13-
DEFAULT_PROFILE_DIR = Path(BaseDirectory.xdg_data_home) / "qutebrowser-profiles"
14-
15-
16-
def main(mock_args: Optional[list[str]] = None) -> None:
17-
parser = argparse.ArgumentParser(description="qutebrowser profile manager")
18-
parser.set_defaults(
19-
operation=lambda args: parser.print_help(), passthrough=False, launch=False
20-
)
21-
parser.add_argument(
22-
"-P",
23-
"--profile-dir",
24-
metavar="directory",
25-
type=Path,
26-
help="directory in which profiles are stored",
27-
)
28-
parser.add_argument(
29-
"--version",
30-
action="version",
31-
version=__version__,
32-
)
33-
34-
subparsers = parser.add_subparsers()
35-
new = subparsers.add_parser("new", help="create a new profile")
36-
new.add_argument("profile_name", metavar="profile", help="name of the new profile")
37-
new.add_argument("home_page", metavar="url", nargs="?", help="profile's home page")
38-
new.set_defaults(operation=build_op(profiles.new_profile))
39-
creator_args(new)
40-
41-
session = subparsers.add_parser(
42-
"from-session", help="create a new profile from a qutebrowser session"
43-
)
44-
session.add_argument(
45-
"session",
46-
help="path to session file or name of session. "
47-
"e.g. ~/.local/share/qutebrowser/sessions/example.yml or example",
48-
)
49-
session.add_argument(
50-
"profile_name",
51-
metavar="profile",
52-
nargs="?",
53-
help="name of the new profile. if unset the session name will be used",
54-
)
55-
session.set_defaults(operation=build_op(operations.from_session))
56-
creator_args(session)
57-
58-
desktop = subparsers.add_parser(
59-
"desktop", help="create a desktop file for an existing profile"
60-
)
61-
desktop.add_argument(
62-
"profile_name", metavar="profile", help="profile to create a desktop file for"
63-
)
64-
desktop.set_defaults(operation=build_op(operations.desktop))
65-
66-
launch = subparsers.add_parser(
67-
"launch", help="launch qutebrowser with the given profile"
68-
)
69-
launch.add_argument(
70-
"profile_name",
71-
metavar="profile",
72-
help="profile to launch. it will be created if it does not exist, unless -s is set",
73-
)
74-
launch.add_argument(
75-
"-n",
76-
"--new",
77-
action="store_false",
78-
dest="strict",
79-
help="create the profile if it doesn't exist",
80-
)
81-
launch.add_argument(
82-
"-f",
83-
"--foreground",
84-
action="store_true",
85-
help="launch qutebrowser in the foreground and print its stdout and stderr to the console",
86-
)
87-
launch.set_defaults(operation=build_op(operations.launch), passthrough=True)
88-
89-
list_ = subparsers.add_parser("list", help="list existing profiles")
90-
list_.set_defaults(operation=operations.list_)
91-
92-
choose = subparsers.add_parser(
93-
"choose",
94-
help="interactively choose a profile to launch",
95-
)
96-
menus = sorted(SUPPORTED_MENUS)
97-
choose.add_argument(
98-
"-m",
99-
"--menu",
100-
help=f'menu application to use. this may be any dmenu-compatible command (e.g. "dmenu -i -p qbpm" or "/path/to/rofi -d") or one of the following menus with built-in support: {menus}',
101-
)
102-
choose.add_argument(
103-
"-f",
104-
"--foreground",
105-
action="store_true",
106-
help="launch qutebrowser in the foreground and print its stdout and stderr to the console",
107-
)
108-
choose.set_defaults(operation=operations.choose, passthrough=True)
109-
110-
edit = subparsers.add_parser("edit", help="edit a profile's config.py")
111-
edit.add_argument("profile_name", metavar="profile", help="profile to edit")
112-
edit.set_defaults(operation=build_op(operations.edit))
113-
114-
raw_args = parser.parse_known_args(mock_args)
115-
args = raw_args[0]
116-
if args.passthrough:
117-
args.qb_args = raw_args[1]
118-
elif len(raw_args[1]) > 0:
119-
error(f"unrecognized arguments: {' '.join(raw_args[1])}")
120-
exit(1)
121-
122-
if not args.profile_dir:
123-
args.profile_dir = Path(environ.get("QBPM_PROFILE_DIR") or DEFAULT_PROFILE_DIR)
124-
125-
result = args.operation(args)
126-
if args.launch and result:
127-
profile = result if isinstance(result, Profile) else Profile.of(args)
128-
result = operations.launch(
129-
profile, False, args.foreground, getattr(args, "qb_args", [])
130-
)
131-
if not result:
132-
exit(1)
133-
134-
135-
def creator_args(parser: argparse.ArgumentParser) -> None:
136-
parser.add_argument(
137-
"-l",
138-
"--launch",
139-
action="store_true",
140-
help="launch the profile after creating",
141-
)
142-
parser.add_argument(
143-
"-f",
144-
"--foreground",
145-
action="store_true",
146-
help="if --launch is set, launch qutebrowser in the foreground",
147-
)
148-
parser.add_argument(
149-
"--no-desktop-file",
150-
dest="desktop_file",
151-
action="store_false",
152-
help="do not generate a desktop file for the profile",
153-
)
154-
parser.add_argument(
155-
"--overwrite",
156-
action="store_true",
157-
help="replace existing profile config",
158-
)
159-
parser.set_defaults(strict=True)
160-
161-
162-
def build_op(operation: Callable[..., Any]) -> Callable[[argparse.Namespace], Any]:
163-
def op(args: argparse.Namespace) -> Any:
164-
params = [
165-
param.name
166-
for param in inspect.signature(operation).parameters.values()
167-
if param.kind == param.POSITIONAL_OR_KEYWORD
168-
]
169-
kwargs = {param: getattr(args, param, None) for param in params}
170-
if "profile" in params:
171-
kwargs["profile"] = Profile.of(args)
172-
return operation(**kwargs)
173-
174-
return op
11+
from .utils import SUPPORTED_MENUS, default_profile_dir, error
12+
13+
14+
@click.group()
15+
@click.option(
16+
"-P",
17+
"--profile-dir",
18+
type=click.Path(file_okay=False, writable=True, path_type=Path),
19+
envvar="QBPM_PROFILE_DIR",
20+
default=default_profile_dir,
21+
)
22+
@click.pass_context
23+
def main(ctx, profile_dir: Path) -> None:
24+
# TODO version, documentation
25+
# TODO -h as --help
26+
ctx.ensure_object(dict)
27+
ctx.obj["PROFILE_DIR"] = profile_dir
28+
29+
30+
@main.command()
31+
@click.argument("profile_name")
32+
@click.argument("home_page", required=False)
33+
@click.option("--desktop-file/--no-desktop-file", default=True, is_flag=True)
34+
@click.option("--overwrite", is_flag=True)
35+
@click.option("-l", "--launch", is_flag=True)
36+
@click.option("-f", "--foreground", is_flag=True)
37+
@click.pass_context
38+
def new(ctx, profile_name: str, launch: bool, foreground: bool, **kwargs):
39+
profile = Profile(profile_name, ctx.obj["PROFILE_DIR"])
40+
result = profiles.new_profile(profile, **kwargs)
41+
if result and launch:
42+
# TODO args?
43+
then_launch(profile, foreground, [])
44+
45+
46+
@main.command()
47+
@click.argument("session")
48+
@click.argument("profile_name", required=False)
49+
@click.option("--desktop-file/--no-desktop-file", default=True, is_flag=True)
50+
@click.option("--overwrite", is_flag=True)
51+
@click.option("-l", "--launch", is_flag=True)
52+
@click.option("-f", "--foreground", is_flag=True)
53+
@click.pass_context
54+
def from_session(
55+
ctx,
56+
launch: bool,
57+
foreground: bool,
58+
**kwargs,
59+
):
60+
profile = operations.from_session(profile_dir=ctx.obj["PROFILE_DIR"], **kwargs)
61+
if profile and launch:
62+
# TODO args?
63+
then_launch(profile, foreground, [])
64+
65+
66+
@main.command()
67+
@click.argument("profile_name")
68+
@click.pass_context
69+
def desktop(
70+
ctx,
71+
profile_name: str,
72+
):
73+
profile = Profile(profile_name, ctx.obj["PROFILE_DIR"])
74+
return operations.desktop(profile)
75+
76+
77+
@main.command()
78+
@click.argument("profile_name")
79+
@click.option("-c", "--create", is_flag=True)
80+
@click.option("-f", "--foreground", is_flag=True)
81+
@click.pass_context
82+
def launch(ctx, profile_name: str, **kwargs):
83+
profile = Profile(profile_name, ctx.obj["PROFILE_DIR"])
84+
# TODO qb args
85+
return operations.launch(profile, **kwargs)
86+
87+
88+
@main.command()
89+
@click.option("-m", "--menu")
90+
@click.option("-f", "--foreground", is_flag=True)
91+
@click.pass_context
92+
def choose(ctx, **kwargs):
93+
# TODO qb args
94+
return operations.choose(profile_dir=ctx.obj["PROFILE_DIR"], qb_args=[], **kwargs)
95+
96+
97+
@main.command()
98+
@click.argument("profile_name")
99+
@click.pass_context
100+
def edit(ctx, profile_name):
101+
breakpoint()
102+
profile = Profile(profile_name, ctx.obj["PROFILE_DIR"])
103+
if not profile.exists():
104+
error(f"profile {profile.name} not found at {profile.root}")
105+
return False
106+
click.edit(filename=profile.root / "config" / "config.py")
107+
108+
109+
@main.command(name="list")
110+
@click.pass_context
111+
def list_(ctx):
112+
for profile in sorted(ctx.obj["PROFILE_DIR"].iterdir()):
113+
print(profile.name)
114+
return True
115+
116+
117+
def then_launch(profile: Profile, foreground: bool, qb_args: list[str]):
118+
result = False
119+
if profile:
120+
result = operations.launch(profile, False, foreground, qb_args)
121+
return result
175122

176123

177124
if __name__ == "__main__":
178-
main()
125+
main(obj={})

qbpm/operations.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import argparse
21
import os
32
import shutil
43
import subprocess
@@ -42,9 +41,9 @@ def from_session(
4241

4342

4443
def launch(
45-
profile: Profile, strict: bool, foreground: bool, qb_args: list[str]
44+
profile: Profile, create: bool, foreground: bool, qb_args: list[str]
4645
) -> bool:
47-
if not profiles.ensure_profile_exists(profile, not strict):
46+
if not profiles.ensure_profile_exists(profile, create):
4847
return False
4948

5049
args = profile.cmdline() + qb_args
@@ -78,26 +77,20 @@ def desktop(profile: Profile) -> bool:
7877
return exists
7978

8079

81-
def list_(args: argparse.Namespace) -> bool:
82-
for profile in sorted(args.profile_dir.iterdir()):
83-
print(profile.name)
84-
return True
85-
86-
87-
def choose(args: argparse.Namespace) -> bool:
88-
menu = args.menu or next(installed_menus())
80+
def choose(profile_dir: Path, menu: str, foreground: bool, qb_args: list[str]) -> bool:
81+
menu = menu or next(installed_menus())
8982
if not menu:
9083
error(f"No menu program found, please install one of: {AUTO_MENUS}")
9184
return False
9285
if menu == "applescript" and platform != "darwin":
9386
error(f"Menu applescript cannot be used on a {platform} host")
9487
return False
95-
profiles = [profile.name for profile in sorted(args.profile_dir.iterdir())]
88+
profiles = [profile.name for profile in sorted(profile_dir.iterdir())]
9689
if len(profiles) == 0:
9790
error("No profiles")
9891
return False
9992

100-
command = menu_command(menu, profiles, args)
93+
command = menu_command(menu, profiles, qb_args)
10194
if not command:
10295
return False
10396

@@ -111,18 +104,16 @@ def choose(args: argparse.Namespace) -> bool:
111104
selection = out and out.read().decode(errors="ignore").rstrip("\n")
112105

113106
if selection:
114-
profile = Profile(selection, args.profile_dir)
115-
launch(profile, True, args.foreground, args.qb_args)
107+
profile = Profile(selection, profile_dir)
108+
launch(profile, True, foreground, qb_args)
116109
else:
117110
error("No profile selected")
118111
return False
119112
return True
120113

121114

122-
def menu_command(
123-
menu: str, profiles: list[str], args: argparse.Namespace
124-
) -> Optional[str]:
125-
arg_string = " ".join(args.qb_args)
115+
def menu_command(menu: str, profiles: list[str], qb_args: list[str]) -> Optional[str]:
116+
arg_string = " ".join(qb_args)
126117
if menu == "applescript":
127118
profile_list = '", "'.join(profiles)
128119
return f"""osascript -e \'set profiles to {{"{profile_list}"}}
@@ -149,12 +140,3 @@ def menu_command(
149140
return None
150141
profile_list = "\n".join(profiles)
151142
return f'echo "{profile_list}" | {command}'
152-
153-
154-
def edit(profile: Profile) -> bool:
155-
if not profile.exists():
156-
error(f"profile {profile.name} not found at {profile.root}")
157-
return False
158-
editor = os.environ.get("VISUAL") or os.environ.get("EDITOR") or "vim"
159-
os.execlp(editor, editor, str(profile.root / "config" / "config.py"))
160-
return True

0 commit comments

Comments
 (0)