summaryrefslogtreecommitdiff
path: root/organize/console.py
blob: 3f75a244395618422feb00942e57c6726951f206 (plain)
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
from pathlib import Path

from fs.base import FS
from fs.path import basename, dirname, forcedir, relpath
from rich.console import Console
from rich.panel import Panel
from rich.prompt import Confirm as RichConfirm
from rich.prompt import Prompt as RichPrompt
from rich.status import Status
from rich.text import Text
from rich.theme import Theme

from organize.__version__ import __version__

from .utils import safe_description

ICON_DIR = "🗁"
ICON_FILE = ""
INDENT = " " * 2

theme = Theme(
    {
        "info": "dim cyan",
        "warning": "yellow",
        "error": "bold red",
        "simulation": "bold green",
        "status": "bold green",
        "rule": "bold cyan",
        "location.fs": "yellow",
        "location.base": "green",
        "location.main": "bold green",
        "path.base": "dim green",
        "path.main": "green",
        "path.icon": "green",
        "pipeline.source": "cyan",
        "pipeline.msg": "white",
        "pipeline.error": "bold red",
        "pipeline.prompt": "bold yellow",
        "summary.done": "bold green",
        "summary.fail": "red",
    }
)
console = Console(theme=theme, highlight=False)
status = Status("", console=console)


class Prefixer:
    def __init__(self):
        self.reset()

    def reset(self):
        self._args = None
        self._kwargs = None

    def set_prefix(self, *args, **kwargs):
        self._args = args
        self._kwargs = kwargs

    def ensure_prefix(self):
        if self._args is not None:
            console.print(*self._args, **self._kwargs)
            self.reset()

    def print(self, *args, **kwargs):
        self.ensure_prefix()
        console.print(*args, **kwargs)


with_path = Prefixer()
with_newline = Prefixer()


class PipelineMixin:
    @classmethod
    def set_source(cls, source):
        cls.validate_error_message = Text.assemble(
            _pipeline_base(source),
            ("Please enter Y or N", "prompt.invalid"),
        )

    def pre_prompt(self):
        with_path.ensure_prefix()


class Confirm(PipelineMixin, RichConfirm):
    pass


class Prompt(PipelineMixin, RichPrompt):
    pass


def _highlight_path(path, base_style, main_style, relative=False):
    dir_ = forcedir(dirname(path))
    if relative:
        dir_ = relpath(dir_)
    name = basename(path)
    return Text.assemble(
        (dir_, base_style),
        (name, main_style),
    )


def info(config: str, working_dir: str):
    console.print("organize {}".format(__version__))
    console.print('Config: "{}"'.format(config))
    if working_dir != Path("."):
        console.print('Working dir: "{}"'.format(working_dir))


def warn(msg, title="Warning"):
    console.print("[warning][b]{}:[/b] {}[/warning]".format(title, msg))


def deprecated(msg):
    warn(msg, title="Deprecated")


def error(msg, title="Error"):
    console.print("[error]{}: {}[/error]".format(title, msg))


def simulation_banner():
    console.print()
    console.print(Panel("SIMULATION", style="simulation"))


def spinner(simulate: bool):
    status_verb = "simulating" if simulate else "organizing"
    status.update(Text(status_verb, "status"))
    status.start()


def rule(rule):
    console.print()
    console.rule("[rule]:gear: %s" % rule, align="left", style="rule")
    with_newline.reset()


def location(fs: FS, fs_path: str):
    result = Text()
    if fs.hassyspath(fs_path):
        syspath = fs.getsyspath(fs_path)
        result = _highlight_path(syspath.rstrip("/"), "location.base", "location.main")
    else:
        result = Text.assemble(
            (str(fs), "location.fs"),
            " ",
            _highlight_path(fs_path.rstrip("/"), "location.base", "location.main"),
        )
    with_newline.print(result)


def path(fs: FS, fs_path: str):
    icon = ICON_DIR if fs.isdir(fs_path) else ICON_FILE
    msg = Text.assemble(
        INDENT,
        _highlight_path(fs_path, "path.base", "path.main", relative=True),
        " ",
        (icon, "path.icon"),
    )
    with_path.set_prefix(msg)


def path_changed_during_pipeline(
    fs: FS, fs_path: str, new_fs: FS, new_path: str, reason="deferred from"
):
    icon = ICON_DIR if new_fs.isdir(new_path) else ICON_FILE
    msg = Text.assemble(
        INDENT,
        _highlight_path(
            safe_description(new_fs, new_path), "path.base", "path.main", relative=True
        ),
        (" <- %s " % reason, "yellow"),
        _highlight_path(fs_path, "path.base", "path.main", relative=True),
        " ",
        (icon, "path.icon"),
    )
    with_path.set_prefix(msg)


def _pipeline_base(source: str):
    return Text.assemble(
        INDENT * 2,
        ("- ({}) ".format(source), "pipeline.source"),
    )


def pipeline_message(source: str, msg: str) -> None:
    line = Text.assemble(
        _pipeline_base(source),
        (msg, "pipeline.msg"),
    )
    with_path.print(line)
    with_newline.set_prefix("")


def pipeline_error(source: str, msg: str):
    line = _pipeline_base(source)
    line.append("ERROR! {}".format(msg), "pipeline.error")
    with_path.print(line)
    with_newline.set_prefix("")


def pipeline_confirm(source: str, msg: str, default: bool):
    status.stop()
    line = _pipeline_base(source)
    line.append(msg, "pipeline.prompt")
    Confirm.set_source(source)
    result = Confirm.ask(
        line,
        console=console,
        default=default,
    )
    with_newline.set_prefix("")
    status.start()
    return result


def summary(count: dict):
    status.stop()
    console.print()
    if not sum(count.values()):
        console.print("Nothing to do.")
    else:
        result = Text.assemble(
            ("success {done}".format(**count), "summary.done"),
            " / ",
            ("fail {fail}".format(**count), "summary.fail"),
        )
        console.print(result)