File: faker.py

package info (click to toggle)
scriv 1.8.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 668 kB
  • sloc: python: 4,136; makefile: 167
file content (120 lines) | stat: -rw-r--r-- 4,125 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""Fake implementations of some of our external information sources."""

import re
import shlex
from collections.abc import Callable, Iterable

from scriv.shell import CmdResult

# A function that simulates run_command.
CmdHandler = Callable[[list[str]], CmdResult]


# A regex to help with catching some (but not all) invalid Git config keys.
WORD = r"[a-zA-Z][a-zA-Z0-9-]*"
GIT_CONFIG_KEY = rf"{WORD}(\..*)?\.{WORD}"


class FakeRunCommand:
    """
    A fake implementation of run_command.

    Add handlers for commands with `add_handler`.
    """

    def __init__(self, mocker):
        """Make the faker."""
        self.handlers: dict[str, CmdHandler] = {}
        self.mocker = mocker
        self.patch_module("scriv.shell")

    def patch_module(self, mod_name: str) -> None:
        """Replace ``run_command`` in `mod_name` with our fake."""
        self.mocker.patch(f"{mod_name}.run_command", self)

    def add_handler(self, argv0: str, handler: CmdHandler) -> None:
        """
        Add a handler for a command.

        The first word of the command is `argv0`.  The handler will be called
        with the complete argv list.  It must return the same results that
        `run_command` would have returned.
        """
        self.handlers[argv0] = handler

    def __call__(self, cmd: str) -> CmdResult:
        """Do the faking!."""
        argv = shlex.split(cmd)
        if argv[0] in self.handlers:
            return self.handlers[argv[0]](argv)
        return (False, f"no fake command handler: {argv}")


class FakeGit:
    """Simulate aspects of our local Git."""

    def __init__(self, frc: FakeRunCommand) -> None:
        """Make a FakeGit from a FakeRunCommand."""
        # Initialize with basic defaults.
        self.config: dict[str, str] = {
            "core.bare": "false",
            "core.repositoryformatversion": "0",
        }
        self.branch = "main"
        self.editor = "vi"
        self.tags: set[str] = set()
        self.remotes: dict[str, tuple[str, str]] = {}

        # Hook up our run_command handler.
        frc.add_handler("git", self.run_command)

    def run_command(self, argv: list[str]) -> CmdResult:
        """Simulate git commands."""
        # todo: match/case someday
        if argv[1] == "config":
            if argv[2] == "--get":
                if argv[3] in self.config:
                    return (True, self.config[argv[3]] + "\n")
                else:
                    return (False, f"error: no such key: {argv[3]}")
        elif argv[1:] == ["rev-parse", "--abbrev-ref", "HEAD"]:
            return (True, self.branch + "\n")
        elif argv[1:] == ["tag"]:
            return (True, "".join(tag + "\n" for tag in self.tags))
        elif argv[1:] == ["var", "GIT_EDITOR"]:
            return (True, self.editor + "\n")
        elif argv[1:] == ["remote", "-v"]:
            out = []
            for name, (url, push_url) in self.remotes.items():
                out.append(f"{name}\t{url} (fetch)\n")
                out.append(f"{name}\t{push_url} (push)\n")
            return (True, "".join(out))
        return (False, f"no fake git command: {argv}")

    def set_config(self, name: str, value: str) -> None:
        """Set a fake Git configuration value."""
        if re.fullmatch(GIT_CONFIG_KEY, name) is None:
            raise ValueError(f"invalid key: {name!r}")
        self.config[name] = value

    def set_branch(self, branch_name: str) -> None:
        """Set the current fake branch."""
        self.branch = branch_name

    def set_editor(self, editor_name: str) -> None:
        """Set the name of the fake editor Git will launch."""
        self.editor = editor_name

    def add_tags(self, tags: Iterable[str]) -> None:
        """Add tags to the repo."""
        self.tags.update(tags)

    def add_remote(
        self, name: str, url: str, push_url: str | None = None
    ) -> None:
        """Add a remote with a name and a url."""
        self.remotes[name] = (url, push_url or url)

    def remove_remote(self, name: str) -> None:
        """Remove the remote `name`."""
        del self.remotes[name]