~pvsr/qpm

f7707a4247367c074bb686414fd705cf7aa6420f — Peter Rice 3 months ago e3eadb8
turn Profile into a class
4 files changed, 91 insertions(+), 100 deletions(-)

M qpm/main.py
M qpm/operations.py
M qpm/profiles.py
M tests/test_profiles.py
M qpm/main.py => qpm/main.py +19 -5
@@ 3,6 3,7 @@ from pathlib import Path
from typing import Callable, Optional

from qpm import config, operations, profiles
from qpm.profiles import Profile


def main() -> None:


@@ 18,7 19,11 @@ def main() -> None:

    subparsers = parser.add_subparsers()
    new = subparsers.add_parser("new", help="create a new profile")
    new.set_defaults(operation=lambda args: profiles.new_profile(args.profile_name))
    new.set_defaults(
        operation=lambda args: wrap_op(
            lambda profile: profiles.new_profile(profile), args.profile_name
        )
    )
    new.add_argument("profile_name", metavar="name", help="name of the new profile")
    creator_args(new)



@@ 43,8 48,11 @@ def main() -> None:
        "launch", aliases=["run"], help="launch qutebrowser with the given profile"
    )
    launch.set_defaults(
        operation=lambda args: operations.launch(
            args.profile_name, args.strict, args.foreground, args.qb_args or []
        operation=lambda args: wrap_op(
            lambda profile: operations.launch(
                profile, args.strict, args.foreground, args.qb_args or []
            ),
            args.profile_name,
        )
    )
    launch.add_argument(


@@ 90,6 98,11 @@ def creator_args(parser: argparse.ArgumentParser) -> None:
    )


def wrap_op(op: Callable[[Profile], bool], profile_name: str) -> Optional[Profile]:
    profile = Profile(profile_name)
    return profile if op(profile) else None


class ThenLaunchAction(argparse.Action):
    def __init__(self, option_strings, dest, nargs=0, **kwargs):
        super(ThenLaunchAction, self).__init__(


@@ 104,11 117,12 @@ class ThenLaunchAction(argparse.Action):


def then_launch(
    args: argparse.Namespace, operation: Callable[[argparse.Namespace], Optional[Path]]
    args: argparse.Namespace,
    operation: Callable[[argparse.Namespace], Optional[Profile]],
) -> bool:
    profile = operation(args)
    if profile:
        return operations.launch(profile, args.strict, args.foreground)
        return operations.launch(profile, args.strict, args.foreground, [])
    return False



M qpm/operations.py => qpm/operations.py +8 -9
@@ 11,35 11,34 @@ from qpm.utils import error

def from_session(
    session_name: str, profile_name: Optional[str] = None
) -> Optional[Path]:
) -> Optional[Profile]:
    session = profiles.main_data_dir / "sessions" / (session_name + ".yml")
    if not session.is_file():
        error(f"{session} is not a file")
        return None

    profile_root = profiles.new_profile(profile_name or session_name)
    if not profile_root:
    profile = Profile(profile_name or session_name)
    if not profiles.new_profile(profile):
        return None

    session_dir = profile_root / "data" / "sessions"
    session_dir = profile.root / "data" / "sessions"
    session_dir.mkdir(parents=True)
    shutil.copy(session, session_dir / "_autosave.yml")

    return profile_root
    return profile


def launch(
    profile: Profile, strict: bool, foreground: bool, args: Iterable[str]
) -> bool:
    profile_root = profiles.ensure_profile_exists(profile, not strict)
    if not profile_root:
    if not profiles.ensure_profile_exists(profile, not strict):
        return False

    if foreground:
        os.execlp("qutebrowser", "qutebrowser", "-B", str(profile_root), *args)
        os.execlp("qutebrowser", "qutebrowser", "-B", str(profile.root), *args)
    else:
        p = subprocess.Popen(
            ["qutebrowser", "-B", str(profile_root), *args],
            ["qutebrowser", "-B", str(profile.root), *args],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.PIPE,
        )

M qpm/profiles.py => qpm/profiles.py +34 -26
@@ 8,8 8,14 @@ from xdg import BaseDirectory  # type: ignore
from qpm import config
from qpm.utils import error

# profile name or path
Profile = Union[str, Path]

class Profile:
    name: str
    root: Path

    def __init__(self, name: str) -> None:
        self.name = name
        self.root = config.profiles_dir / name


main_config_dir = Path(BaseDirectory.save_config_path("qutebrowser"))


@@ 24,6 30,7 @@ else:


def get_profile_root(profile: Profile) -> Path:
    return profile.root
    if isinstance(profile, str):
        return config.profiles_dir / profile
    else:


@@ 46,38 53,39 @@ def check_profile(profile_root: Path) -> bool:
    return True


def create_profile(profile: Profile) -> Optional[Path]:
    profile_root = get_profile_root(profile)

    if not check_profile(profile_root):
        return None
def create_profile(profile: Profile) -> bool:
    if not check_profile(profile.root):
        return False

    config_dir = profile_root / "config"
    config_dir = profile.root / "config"
    config_dir.mkdir(parents=True)
    return profile_root
    return True


def create_config(profile_root: Path) -> None:
    with (profile_root / "config" / "config.py").open(mode="x") as config:
        # print(
        #     "c.window.title_format = '{perc}{current_title}{title_sep}"
        #     + f"{profile_root}'",
        #     file=config,
        # )
        print(f"config.source('{main_config_dir / 'config.py'}')", file=config)


def ensure_profile_exists(profile: Profile, create: bool = True) -> Optional[Path]:
    profile_root = get_profile_root(profile)
    if profile_root.exists() and not profile_root.is_dir():
        error(f"{profile_root} is not a directory")
        return None
    if not profile_root.exists() and create:
        return new_profile(profile_root)
    if not profile_root.exists():
        error(f"{profile_root} does not exist")
        return None
    return profile_root

def ensure_profile_exists(profile: Profile, create: bool = True) -> bool:
    if profile.root.exists() and not profile.root.is_dir():
        error(f"{profile.root} is not a directory")
        return False
    if not profile.root.exists() and create:
        return new_profile(profile)
    if not profile.root.exists():
        error(f"{profile.root} does not exist")
        return False
    return True

def new_profile(profile: Profile) -> Optional[Path]:
    profile_root = create_profile(profile)
    if profile_root:
        create_config(profile_root)

    return profile_root
def new_profile(profile: Profile) -> bool:
    if create_profile(profile):
        create_config(profile.root)
        return True
    return False

M tests/test_profiles.py => tests/test_profiles.py +30 -60
@@ 1,69 1,59 @@
from pathlib import Path
from typing import Optional

from qpm import config, operations, profiles
from qpm.profiles import Profile


def check_is_empty(path: Path):
    assert len(list(path.iterdir())) == 0


def check_empty_profile(profile: Path):
def check_empty_profile(profile: Optional[Profile]):
    assert profile
    config_dir = profile / "config"
    assert list(profile.iterdir()) == [config_dir]
    config_dir = profile.root / "config"
    assert list(profile.root.iterdir()) == [config_dir]
    assert list(config_dir.iterdir()) == []


def check_new_profile(profile: Path):
def check_new_profile(profile: Profile):
    assert profile
    config_dir = profile / "config"
    assert list(profile.iterdir()) == [config_dir]
    config_dir = profile.root / "config"
    assert list(profile.root.iterdir()) == [config_dir]
    assert list(config_dir.iterdir()) == [config_dir / "config.py"]


def test_set_profile(tmp_path: Path):
    config.profiles_dir = tmp_path
    assert profiles.get_profile_root("test") == tmp_path / "test"


def test_get_profile_path(tmp_path: Path):
    config.profiles_dir = tmp_path
    assert profiles.get_profile_root(tmp_path) == tmp_path
    assert Profile("test").root == tmp_path / "test"


def test_create_profile(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = profiles.create_profile("test")
    assert profile
    assert list(tmp_path.iterdir()) == [profile]
    check_empty_profile(profile)


def test_create_profile_path(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = profiles.create_profile(tmp_path / "test")
    assert profile
    assert list(tmp_path.iterdir()) == [profile]
    profile = Profile("test")
    assert profiles.create_profile(profile)
    assert list(tmp_path.iterdir()) == [profile.root]
    check_empty_profile(profile)


def test_create_profile_conflict(tmp_path: Path):
    config.profiles_dir = tmp_path
    (tmp_path / "test").touch()
    profile = profiles.create_profile("test")
    assert not profile
    profile = Profile("test")
    assert not profiles.create_profile(profile)


def test_create_profile_parent(tmp_path: Path):
    config.profiles_dir = tmp_path / "profiles"
    profile = profiles.create_profile("../test")
    profile = Profile("../test")
    assert not profiles.create_profile(profile)
    assert not (tmp_path / "test").exists()


def test_create_profile_nested_conflict(tmp_path: Path):
    config.profiles_dir = tmp_path
    assert profiles.create_profile("test")
    assert not profiles.create_profile("test/a")
    assert profiles.create_profile(Profile("test"))
    assert not profiles.create_profile(Profile("test/a"))


def test_create_config(tmp_path: Path):


@@ 76,56 66,36 @@ def test_create_config(tmp_path: Path):

def test_ensure_profile_exists_exists(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    profile.mkdir()
    assert profiles.ensure_profile_exists("test", False) == profile
    assert profiles.ensure_profile_exists("test", True) == profile
    assert profiles.ensure_profile_exists(profile, False) == profile
    assert profiles.ensure_profile_exists(profile, True) == profile
    check_is_empty(profile)
    profile = Profile("test")
    profile.root.mkdir()
    assert profiles.ensure_profile_exists(profile, False)
    assert profiles.ensure_profile_exists(profile, True)
    check_is_empty(profile.root)


def test_ensure_profile_exists_does_not_exist(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    assert not profiles.ensure_profile_exists("test", False)
    assert not profiles.ensure_profile_exists(profile, False)
    assert not profiles.ensure_profile_exists(Profile("test"), False)
    check_is_empty(tmp_path)


def test_ensure_profile_exists_not_dir(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    profile.touch()
    assert not profiles.ensure_profile_exists("test", False)
    profile = Profile("test")
    profile.root.touch()
    assert not profiles.ensure_profile_exists(profile, False)
    assert not profiles.ensure_profile_exists("test", True)
    assert not profiles.ensure_profile_exists(profile, True)


def test_ensure_profile_exists_create(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    assert profiles.ensure_profile_exists("test", True) == profile
    check_new_profile(profile)


def test_ensure_profile_exists_create_path(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    assert profiles.ensure_profile_exists(profile, True) == profile
    profile = Profile("test")
    assert profiles.ensure_profile_exists(profile, True)
    check_new_profile(profile)


def test_new_profile(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    assert profiles.new_profile("test") == profile
    check_new_profile(profile)


def test_new_profile_path(tmp_path: Path):
    config.profiles_dir = tmp_path
    profile = tmp_path / "test"
    assert profiles.ensure_profile_exists(profile) == profile
    profile = Profile("test")
    assert profiles.new_profile(profile)
    check_new_profile(profile)