mud-git 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mud/runner.py ADDED
@@ -0,0 +1,498 @@
1
+ import os
2
+ import asyncio
3
+ import subprocess
4
+
5
+ from typing import List, Dict
6
+ from collections import Counter
7
+
8
+ from mud import utils
9
+ from mud.utils import glyphs
10
+ from mud.styles import *
11
+
12
+
13
+ class Runner:
14
+ _label_color_cache = {}
15
+ _current_color_index = 0
16
+
17
+ def __init__(self, repos):
18
+ self._last_printed_lines = 0
19
+ self.repos = repos
20
+
21
+ # `mud info` command implementation
22
+ def info(self, repos: Dict[str, List[str]]) -> None:
23
+ def get_directory_size(directory):
24
+ total_size = 0
25
+ for directory_path, directory_names, file_names in os.walk(directory):
26
+ for f in file_names:
27
+ fp = os.path.join(directory_path, f)
28
+ if os.path.isfile(fp):
29
+ total_size += os.path.getsize(fp)
30
+ return total_size
31
+
32
+ def format_size(size_in_bytes):
33
+ if size_in_bytes >= 1024 ** 3:
34
+ return f'{BOLD}{size_in_bytes / (1024 ** 3):.2f}{RESET} GB{glyphs("space")}{RED}{glyphs("weight")}{RESET}'
35
+ elif size_in_bytes >= 1024 ** 2:
36
+ return f'{BOLD}{size_in_bytes / (1024 ** 2):.2f}{RESET} MB{glyphs("space")}{YELLOW}{glyphs("weight")}{RESET}'
37
+ elif size_in_bytes >= 1024:
38
+ return f'{BOLD}{size_in_bytes / 1024:.2f}{RESET} KB{glyphs("space")}{GREEN}{glyphs("weight")}{RESET}'
39
+ else:
40
+ return f'{BOLD}{size_in_bytes}{RESET} Bytes{glyphs("space")}{BLUE}{glyphs("weight")}{RESET}'
41
+
42
+ def get_git_origin_host_icon(url: str):
43
+ icon = YELLOW + glyphs('git')
44
+
45
+ if 'azure' in url or 'visualstudio' in url:
46
+ icon = BLUE + glyphs('azure')
47
+ elif 'github' in url:
48
+ icon = GRAY + glyphs('github')
49
+ elif 'gitlab' in url:
50
+ icon = YELLOW + glyphs('gitlab')
51
+ elif 'bitbucket' in url:
52
+ icon = CYAN + glyphs('bitbucket')
53
+
54
+ icon += RESET + glyphs('space')
55
+ return icon
56
+
57
+ table = utils.get_table(['Path', 'Commits', 'User Commits', 'Size', 'Labels'])
58
+ table.align['Commits'] = 'r'
59
+ table.align['User Commits'] = 'r'
60
+ table.align['Size'] = 'r'
61
+
62
+ for path, labels in repos.items():
63
+ try:
64
+ origin_url = subprocess.check_output('git remote get-url origin', shell=True, text=True, cwd=path).strip()
65
+ except Exception:
66
+ origin_url = ''
67
+
68
+ formatted_path = f'{get_git_origin_host_icon(origin_url)}{self._get_formatted_path(path)}'
69
+ size = format_size(get_directory_size(path))
70
+ commits = f'{BOLD}{subprocess.check_output("git rev-list --count HEAD", shell=True, text=True, cwd=path).strip()}{RESET} {DIM}commits{RESET}'
71
+ user_commits = f'{GREEN}{BOLD}{subprocess.check_output("git rev-list --count --author=\"$(git config user.name)\" HEAD", shell=True, text=True, cwd=path).strip()}{RESET} {DIM}by you{RESET}'
72
+ colored_labels = self._get_formatted_labels(labels)
73
+
74
+ table.add_row([formatted_path, commits, user_commits, size, colored_labels])
75
+
76
+ utils.print_table(table)
77
+
78
+ # `mud status` command implementation
79
+ def status(self, repos: Dict[str, List[str]]) -> None:
80
+ table = utils.get_table(['Path', 'Branch', 'Origin Sync', 'Status', 'Edits'])
81
+
82
+ for path, labels in repos.items():
83
+ output = self._get_status_porcelain(path)
84
+ files = output.splitlines()
85
+
86
+ formatted_path = self._get_formatted_path(path)
87
+ branch = self._get_branch_status(path)
88
+ origin_sync = self._get_origin_sync(path)
89
+ status = self._get_status_string(files)
90
+
91
+ colored_output = []
92
+
93
+ for file in files:
94
+ file_status = file[:2].strip()
95
+ if file_status == 'M':
96
+ color = YELLOW
97
+ elif file_status == 'A':
98
+ color = GREEN
99
+ elif file_status == 'R':
100
+ color = BLUE
101
+ elif file_status == 'D':
102
+ color = RED
103
+ else:
104
+ color = CYAN
105
+
106
+ colored_output.append(self._get_formatted_path(file[3:].strip(), color))
107
+
108
+ table.add_row([formatted_path, branch, origin_sync, status, ', '.join(colored_output)])
109
+
110
+ utils.print_table(table)
111
+
112
+ # `mud labels` command implementation
113
+ def labels(self, repos: Dict[str, List[str]]) -> None:
114
+ table = utils.get_table(['Path', 'Labels'])
115
+
116
+ for path, labels in repos.items():
117
+ formatted_path = self._get_formatted_path(path)
118
+ colored_labels = self._get_formatted_labels(labels)
119
+ table.add_row([formatted_path, colored_labels])
120
+
121
+ utils.print_table(table)
122
+
123
+ # `mud log` command implementation
124
+ def log(self, repos: Dict[str, List[str]]) -> None:
125
+ table = utils.get_table(['Path', 'Branch', 'Author', 'Time', 'Message'])
126
+
127
+ for path in repos.keys():
128
+ formatted_path = self._get_formatted_path(path)
129
+ branch = self._get_branch_status(path)
130
+ author = self._get_authors_name(path)
131
+ commit = self._get_commit_message(path)
132
+
133
+ # Commit time
134
+ commit_time_cmd = subprocess.run('git log -1 --pretty=format:%cd --date=relative', shell=True, text=True, cwd=path, capture_output=True)
135
+ commit_time = commit_time_cmd.stdout.strip()
136
+
137
+ table.add_row([formatted_path, branch, author, commit_time, commit])
138
+
139
+ utils.print_table(table)
140
+
141
+ # `mud branch` command implementation
142
+ def branches(self, repos: Dict[str, List[str]]) -> None:
143
+ table = utils.get_table(['Path', 'Branches'])
144
+ all_branches = {}
145
+
146
+ # Preparing branches for sorting to display them in the right order.
147
+ for path in repos.keys():
148
+ raw_branches = [line.strip() for line in subprocess.check_output('git branch', shell=True, text=True, cwd=path).split('\n') if line.strip()]
149
+ for branch in raw_branches:
150
+ branch = branch.replace(' ', '').replace('*', '')
151
+ if branch not in all_branches:
152
+ all_branches[branch] = 0
153
+ all_branches[branch] += 1
154
+ branch_counter = Counter(all_branches)
155
+
156
+ for path, labels in repos.items():
157
+ formatted_path = self._get_formatted_path(path)
158
+ branches = subprocess.check_output('git branch --color=never', shell=True, text=True, cwd=path).splitlines()
159
+ current_branch = next((branch.lstrip('* ') for branch in branches if branch.startswith('*')), None)
160
+ branches = [branch.lstrip('* ') for branch in branches]
161
+ sorted_branches = sorted(branches, key=lambda x: branch_counter.get(x, 0), reverse=True)
162
+
163
+ if current_branch and current_branch in sorted_branches:
164
+ sorted_branches.remove(current_branch)
165
+ sorted_branches.insert(0, current_branch)
166
+
167
+ formatted_branches = self._get_formatted_branches(sorted_branches, current_branch)
168
+ table.add_row([formatted_path, formatted_branches])
169
+
170
+ utils.print_table(table)
171
+
172
+ # `mud branch` command implementation
173
+ def remote_branches(self, repos: Dict[str, List[str]]) -> None:
174
+ # TODO: merge with branches() function
175
+ table = utils.get_table(['Path', 'Branches'])
176
+ all_branches = {}
177
+
178
+ # Preparing branches for sorting to display them in the right order.
179
+ for path in repos.keys():
180
+ raw_branches = [
181
+ line.lstrip('* ').removeprefix('origin/')
182
+ for line in subprocess.check_output('git branch -r', shell=True, text=True, cwd=path).split('\n')
183
+ if line.strip() and '->' not in line
184
+ ]
185
+ for branch in raw_branches:
186
+ branch = branch.replace(' ', '').replace('*', '')
187
+ if branch not in all_branches:
188
+ all_branches[branch] = 0
189
+ all_branches[branch] += 1
190
+ branch_counter = Counter(all_branches)
191
+
192
+ for path, labels in repos.items():
193
+ formatted_path = self._get_formatted_path(path)
194
+ branches = subprocess.check_output('git branch -r --color=never', shell=True, text=True, cwd=path).splitlines()
195
+ current_branch = next((branch.lstrip('* ') for branch in branches if branch.startswith('*')), None)
196
+ branches = [branch.lstrip('* ').removeprefix('origin/') for branch in branches if '->' not in branch]
197
+ sorted_branches = sorted(branches, key=lambda x: branch_counter.get(x, 0), reverse=True)
198
+
199
+ if current_branch and current_branch in sorted_branches:
200
+ sorted_branches.remove(current_branch)
201
+ sorted_branches.insert(0, current_branch)
202
+
203
+ formatted_branches = self._get_formatted_branches(sorted_branches, current_branch)
204
+ table.add_row([formatted_path, formatted_branches])
205
+
206
+ utils.print_table(table)
207
+
208
+ # `mud tags` command implementation
209
+ def tags(self, repos: Dict[str, List[str]]) -> None:
210
+ COLORS = [196, 202, 208, 214, 220, 226, 118, 154, 190, 33, 39, 45, 51, 87, 93, 99, 105, 111, 27, 63, 69, 75, 81, 87, 123, 129, 135, 141, 147, 183, 189, 225]
211
+
212
+ tag_colors = {}
213
+
214
+ def assign_color(tag: str) -> str:
215
+ if tag not in tag_colors:
216
+ color_code = COLORS[len(tag_colors) % len(COLORS)]
217
+ tag_colors[tag] = f'\033[38;5;{color_code}m'
218
+ return tag_colors[tag]
219
+
220
+ table = utils.get_table(['Path', 'Tags'])
221
+
222
+ for path, labels in repos.items():
223
+ formatted_path = self._get_formatted_path(path)
224
+ tags = sorted([line.strip() for line in subprocess.check_output('git tag', shell=True, text=True, cwd=path).splitlines() if line.strip()], reverse=True)
225
+ tags = [f'{assign_color(tag)}{glyphs("tag")} {RESET}{tag}' for tag in tags]
226
+ tags = ' '.join(tags)
227
+ table.add_row([formatted_path, tags])
228
+
229
+ utils.print_table(table)
230
+
231
+ # `mud <COMMAND>` when run_async = 0 and run_table = 0
232
+ def run_ordered(self, repos: List[str], command: str) -> None:
233
+ for path in repos:
234
+ process = subprocess.run(command, cwd=path, universal_newlines=True, shell=True, capture_output=True, text=True)
235
+ self._print_process_header(path, command, process.returncode != 0, process.returncode)
236
+ if process.stdout and not process.stdout.isspace():
237
+ print(process.stdout)
238
+ if process.stderr and not process.stderr.isspace():
239
+ print(process.stderr)
240
+
241
+ # `mud <COMMAND>` when run_async = 1 and run_table = 0
242
+ async def run_async(self, repos: List[str], command: str) -> None:
243
+ sem = asyncio.Semaphore(len(repos))
244
+
245
+ async def run_process(path: str) -> None:
246
+ async with sem:
247
+ process = await asyncio.create_subprocess_shell(command, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
248
+ stdout, stderr = await process.communicate()
249
+ self._print_process_header(path, command, process.returncode != 0, process.returncode)
250
+ if stderr:
251
+ print(stderr.decode())
252
+ if stdout and not stdout.isspace():
253
+ print(stdout.decode())
254
+
255
+ await asyncio.gather(*(run_process(path) for path in repos))
256
+
257
+ # `mud <COMMAND>` when run_async = 1 and run_table = 1
258
+ async def run_async_table_view(self, repos: List[str], command: str) -> None:
259
+ sem = asyncio.Semaphore(len(repos))
260
+ table = {repo: ['', ''] for repo in repos}
261
+
262
+ async def task(repo: str) -> None:
263
+ async with sem:
264
+ await self._run_process(repo, table, command)
265
+
266
+ tasks = [asyncio.create_task(task(repo)) for repo in repos]
267
+ await asyncio.gather(*tasks)
268
+
269
+ async def _run_process(self, path: str, table: Dict[str, List[str]], command: str) -> None:
270
+ process = await asyncio.create_subprocess_shell(command, cwd=path, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
271
+ table[path] = ['', f'{YELLOW}{glyphs("running")}{RESET}']
272
+
273
+ while True:
274
+ line = await process.stdout.readline()
275
+ if not line:
276
+ line = await process.stderr.readline()
277
+ if not line:
278
+ break
279
+ line = line.decode().strip()
280
+ line = table[path][0] if not line.strip() else line
281
+ table[path] = [line, f'{YELLOW}{glyphs("running")}{RESET}']
282
+ self._print_process(table)
283
+
284
+ return_code = await process.wait()
285
+
286
+ if return_code == 0:
287
+ status = f'{GREEN}{glyphs("finished")}{RESET}'
288
+ else:
289
+ status = f'{RED}{glyphs("failed")} Code: {return_code}{RESET}'
290
+
291
+ table[path] = [table[path][0], status]
292
+ self._print_process(table)
293
+
294
+ def _print_process(self, info: Dict[str, List[str]]) -> None:
295
+ table = utils.get_table(['Path', 'Status', 'Output'])
296
+ for path, (line, status) in info.items():
297
+ formatted_path = self._get_formatted_path(path)
298
+ table.add_row([formatted_path, status, line])
299
+
300
+ table_str = utils.table_to_str(table)
301
+ num_lines = table_str.count('\n') + 1
302
+ self._clear_printed_lines()
303
+ utils.print_table(table)
304
+ self._last_printed_lines = num_lines
305
+
306
+ def _clear_printed_lines(self) -> None:
307
+ if self._last_printed_lines > 0:
308
+ for _ in range(self._last_printed_lines):
309
+ # Clear previous line
310
+ print('\033[A\033[K', end='')
311
+ self._last_printed_lines = 0
312
+
313
+ @staticmethod
314
+ def _get_status_porcelain(path: str) -> str:
315
+ try:
316
+ output = subprocess.check_output('git status --porcelain', shell=True, text=True, cwd=path)
317
+ return output
318
+ except Exception as e:
319
+ return str(e)
320
+
321
+ @staticmethod
322
+ def _get_status_string(files: List[str]) -> str:
323
+ modified, added, removed, moved = 0, 0, 0, 0
324
+
325
+ for file in files:
326
+ file = file.lstrip()
327
+ if file.startswith('M'):
328
+ modified += 1
329
+ elif file.startswith('A') or file.startswith('??'):
330
+ added += 1
331
+ elif file.startswith('D'):
332
+ removed += 1
333
+ elif file.startswith('R'):
334
+ moved += 1
335
+ status = ''
336
+ if added:
337
+ status += f'{BRIGHT_GREEN}{added} {glyphs("added")}{RESET} '
338
+ if modified:
339
+ status += f'{YELLOW}{modified} {glyphs("modified")}{RESET} '
340
+ if moved:
341
+ status += f'{BLUE}{moved} {glyphs("moved")}{RESET} '
342
+ if removed:
343
+ status += f'{RED}{removed} {glyphs("removed")}{RESET} '
344
+ if not files:
345
+ status = f'{GREEN}{glyphs("clear")}{RESET}'
346
+ return status
347
+
348
+ @staticmethod
349
+ def _get_branch_status(path: str) -> str:
350
+ try:
351
+ branch_cmd = subprocess.run('git rev-parse --abbrev-ref HEAD', shell=True, text=True, cwd=path, capture_output=True)
352
+ branch_stdout = branch_cmd.stdout.strip()
353
+ except subprocess.CalledProcessError:
354
+ branch_stdout = 'NA'
355
+ if '/' in branch_stdout:
356
+ branch_path = branch_stdout.split('/')
357
+ icon = Runner._get_branch_icon(branch_path[0])
358
+ return f'{icon}{RESET}{glyphs("space")}{branch_path[0]}{RESET}/{BOLD}{("/".join(branch_path[1:]))}{RESET}'
359
+ elif branch_stdout == 'HEAD':
360
+ # check if we are on tag
361
+ glyph = glyphs('tag')
362
+ color = BRIGHT_MAGENTA
363
+ info_cmd = subprocess.run('git describe --tags --exact-match', shell=True, text=True, cwd=path, capture_output=True)
364
+ info_cmd = info_cmd.stdout.strip()
365
+
366
+ if not info_cmd.strip():
367
+ glyph = glyphs("branch")
368
+ color = CYAN
369
+ info_cmd = subprocess.run('git rev-parse --short HEAD', shell=True, text=True, cwd=path, capture_output=True)
370
+ info_cmd = info_cmd.stdout.strip()
371
+
372
+ return f'{color}{glyph}{RESET}{glyphs("space")}{DIM}{branch_stdout}{RESET}:{info_cmd}'
373
+ else:
374
+ return f'{Runner._get_branch_icon(branch_stdout)}{RESET}{glyphs("space")}{branch_stdout}'
375
+
376
+ @staticmethod
377
+ def _get_origin_sync(path: str) -> str:
378
+ try:
379
+ ahead_behind_cmd = subprocess.run('git rev-list --left-right --count HEAD...@{upstream}', shell=True, text=True, cwd=path, capture_output=True)
380
+ stdout = ahead_behind_cmd.stdout.strip().split()
381
+ except subprocess.CalledProcessError:
382
+ stdout = ['0', '0']
383
+
384
+ origin_sync = ''
385
+ if len(stdout) >= 2:
386
+ ahead, behind = stdout[0], stdout[1]
387
+ if ahead and ahead != '0':
388
+ origin_sync += f'{BRIGHT_GREEN}{glyphs("ahead")} {ahead}{RESET}'
389
+ if behind and behind != '0':
390
+ if origin_sync:
391
+ origin_sync += ' '
392
+ origin_sync += f'{BRIGHT_BLUE}{glyphs("behind")} {behind}{RESET}'
393
+
394
+ if not origin_sync.strip():
395
+ origin_sync = f'{BLUE}{glyphs("synced")}{RESET}'
396
+
397
+ return origin_sync
398
+
399
+ @staticmethod
400
+ def _print_process_header(path: str, command: str, failed: bool, code: int) -> None:
401
+ path = f'{BKG_BLACK}{Runner._get_formatted_path(path)}{RESET}'
402
+ command = f'{BKG_WHITE}{BLACK}{glyphs(")")}{glyphs("space")}{glyphs("terminal")}{glyphs("space")}{BOLD}{command} {END_BOLD}{WHITE}{RESET}'
403
+ code = f'{WHITE}{BKG_RED if failed else BKG_GREEN}{glyphs(")")}{BRIGHT_WHITE}{glyphs("space")}{glyphs("failed") if failed else glyphs("finished")} {f"Code: {BOLD}{code}" if failed else ""}{glyphs("space")}{RESET}{RED if failed else GREEN}{glyphs(")")}{RESET}'
404
+ print(f'{path} {command}{code}')
405
+
406
+ @staticmethod
407
+ def _get_formatted_path(path: str, color: str = None) -> str:
408
+ collapse_paths = utils.settings.config['mud'].getboolean('collapse_paths', fallback=False)
409
+
410
+ if color is None:
411
+ color = WHITE
412
+
413
+ in_quotes = path.startswith('\"') and path.endswith('\"')
414
+ quote = '\"' if in_quotes else ''
415
+
416
+ if in_quotes:
417
+ path = path.replace('\"', '')
418
+
419
+ def apply_styles(text: str) -> str:
420
+ return color + quote + text + quote + RESET
421
+
422
+ if os.path.isabs(path):
423
+ home = os.path.expanduser('~')
424
+ if path.startswith(home):
425
+ path = path.replace(home, '~', 1)
426
+ if path.startswith('/'):
427
+ path = path[1:]
428
+ parts = path.split('/')
429
+ if collapse_paths:
430
+ return apply_styles((DIM + '/'.join([p[0] for p in parts[:-1]] + [END_DIM + parts[-1]])))
431
+ else:
432
+ return apply_styles((DIM + '/'.join(parts[:-1]) + '/' + END_DIM + parts[-1]))
433
+ if '/' not in path:
434
+ return apply_styles(path)
435
+
436
+ parts = path.split('/')
437
+ if collapse_paths:
438
+ return apply_styles((DIM + '/'.join([p[0] for p in parts[:-1]] + [END_DIM + parts[-1]])))
439
+ else:
440
+ return apply_styles((DIM + '/'.join(parts[:-1]) + '/' + END_DIM + parts[-1]))
441
+
442
+ @staticmethod
443
+ def _get_authors_name(path: str) -> str:
444
+ cmd = subprocess.run('git log -1 --pretty=format:%an', shell=True, text=True, cwd=path, capture_output=True)
445
+ git_config_user_cmd = subprocess.run(['git', 'config', 'user.name'], text=True, capture_output=True)
446
+ committer_color = '' if cmd.stdout.strip() == git_config_user_cmd.stdout.strip() else DIM
447
+ return f'{committer_color}{cmd.stdout.strip()}{RESET}'
448
+
449
+ @staticmethod
450
+ def _get_commit_message(path: str) -> str:
451
+ cmd = subprocess.run('git log -1 --pretty=format:%s', shell=True, text=True, cwd=path, capture_output=True)
452
+ return cmd.stdout.strip()
453
+
454
+ @staticmethod
455
+ def _get_formatted_labels(labels: List[str]) -> str:
456
+ if len(labels) == 0:
457
+ return ''
458
+ colored_labels = ''
459
+ for label in labels:
460
+ color_index = Runner._get_color_index(label) % len(TEXT)
461
+ colored_labels += f'{TEXT[(color_index + 3) % len(TEXT)]}{glyphs("label")}{glyphs("space")}{label}{RESET} '
462
+
463
+ return colored_labels.rstrip()
464
+
465
+ @staticmethod
466
+ def _get_formatted_branches(branches: List[str], current_branch: str) -> str:
467
+ if len(branches) == 0:
468
+ return ''
469
+
470
+ output = ''
471
+ for branch in branches:
472
+ prefix = f'{BOLD}{RED}*{RESET}' if current_branch == branch else ''
473
+ icon = Runner._get_branch_icon(branch.split('/')[0])
474
+ branch = Runner._get_formatted_path(branch, BRIGHT_WHITE)
475
+ output += f'{icon}{glyphs("space")}{prefix}{BRIGHT_WHITE}{branch}{RESET} '
476
+ return output
477
+
478
+ @staticmethod
479
+ def _get_branch_icon(branch_prefix: str) -> str:
480
+ if branch_prefix in ['bugfix', 'bug', 'hotfix']:
481
+ return RED + glyphs('bugfix') + RESET
482
+ elif branch_prefix in ['feature', 'feat', 'develop']:
483
+ return GREEN + glyphs('feature') + RESET
484
+ elif branch_prefix in ['release']:
485
+ return BLUE + glyphs('release') + RESET
486
+ elif branch_prefix in ['master', 'main']:
487
+ return YELLOW + glyphs('master') + RESET
488
+ elif branch_prefix in ['test']:
489
+ return MAGENTA + glyphs('test') + RESET
490
+ else:
491
+ return CYAN + glyphs('branch') + RESET
492
+
493
+ @staticmethod
494
+ def _get_color_index(label: str) -> (str, str):
495
+ if label not in Runner._label_color_cache:
496
+ Runner._label_color_cache[label] = Runner._current_color_index
497
+ Runner._current_color_index = (Runner._current_color_index + 1) % len(BKG)
498
+ return Runner._label_color_cache[label]
mud/settings.py ADDED
@@ -0,0 +1,51 @@
1
+ import os
2
+ import configparser
3
+
4
+ MAIN_SCOPE = 'mud'
5
+ ALIAS_SCOPE = 'alias'
6
+
7
+
8
+ class Settings:
9
+ def __init__(self, file_name: str) -> None:
10
+ self.file_name = file_name
11
+ self.mud_settings = None
12
+ self.alias_settings = None
13
+ self.config = configparser.ConfigParser()
14
+ self.settings_file = os.path.join(os.path.expanduser('~'), self.file_name)
15
+ self.defaults = {
16
+ 'mud': {
17
+ 'config_path': '',
18
+ 'nerd_fonts': True,
19
+ 'run_async': True,
20
+ 'run_table': True,
21
+ 'simplify_branches': True
22
+ },
23
+ 'alias': {
24
+ 'to': 'git checkout',
25
+ 'fetch': 'git fetch',
26
+ 'pull': 'git pull',
27
+ 'push': 'git push'
28
+ }
29
+ }
30
+ self.load_settings()
31
+
32
+ def load_settings(self) -> None:
33
+ if not os.path.exists(self.settings_file):
34
+ self.config.read_dict(self.defaults)
35
+ self.save()
36
+ else:
37
+ self.config.read(self.settings_file)
38
+
39
+ self.mud_settings = {}
40
+ for key in self.defaults[MAIN_SCOPE]:
41
+ if isinstance(self.defaults[MAIN_SCOPE][key], bool):
42
+ self.mud_settings[key] = self.config.getboolean(MAIN_SCOPE, key, fallback=self.defaults[MAIN_SCOPE][key])
43
+ else:
44
+ self.mud_settings[key] = self.config.get(MAIN_SCOPE, key, fallback=self.defaults[MAIN_SCOPE][key])
45
+
46
+ if ALIAS_SCOPE in self.config:
47
+ self.alias_settings = self.config[ALIAS_SCOPE]
48
+
49
+ def save(self) -> None:
50
+ with open(self.settings_file, 'w') as configfile:
51
+ self.config.write(configfile)
mud/styles.py ADDED
@@ -0,0 +1,95 @@
1
+ BOLD = '\033[1m'
2
+ DIM = '\033[2m'
3
+ ITALIC = '\033[3m'
4
+ UNDERLINE = '\033[4m'
5
+ BLINK = '\033[5m'
6
+
7
+ STYLES = [BOLD, DIM, ITALIC, UNDERLINE, BLINK]
8
+
9
+ END_BOLD = '\033[22m'
10
+ END_DIM = '\033[22m'
11
+ END_ITALIC = '\033[23m'
12
+ END_UNDERLINE = '\033[24m'
13
+ END_BLINK = '\033[25m'
14
+
15
+ END = [END_BOLD, END_DIM, END_ITALIC, END_UNDERLINE, END_BLINK]
16
+
17
+ # Text colors
18
+ WHITE = '\033[37m'
19
+ GRAY = '\033[90m'
20
+ BLACK = '\033[30m'
21
+ RED = '\033[31m'
22
+ GREEN = '\033[32m'
23
+ YELLOW = '\033[33m'
24
+ BLUE = '\033[34m'
25
+ MAGENTA = '\033[35m'
26
+ CYAN = '\033[36m'
27
+ BRIGHT_WHITE = '\033[97m'
28
+ BRIGHT_RED = '\033[91m'
29
+ BRIGHT_GREEN = '\033[92m'
30
+ BRIGHT_YELLOW = '\033[93m'
31
+ BRIGHT_BLUE = '\033[94m'
32
+ BRIGHT_MAGENTA = '\033[95m'
33
+ BRIGHT_CYAN = '\033[96m'
34
+
35
+ TEXT = [WHITE, GRAY, BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, BRIGHT_WHITE, BRIGHT_RED, BRIGHT_GREEN, BRIGHT_YELLOW, BRIGHT_BLUE, BRIGHT_MAGENTA, BRIGHT_CYAN]
36
+
37
+ # Background colors
38
+ BKG_WHITE = '\033[47m'
39
+ BKG_MEDIUM_GRAY = '\033[100m'
40
+ BKG_BLACK = '\033[40m'
41
+ BKG_RED = '\033[41m'
42
+ BKG_GREEN = '\033[42m'
43
+ BKG_YELLOW = '\033[43m'
44
+ BKG_BLUE = '\033[44m'
45
+ BKG_MAGENTA = '\033[45m'
46
+ BKG_CYAN = '\033[46m'
47
+ BKG_BRIGHT_WHITE = '\033[107m'
48
+ BKG_BRIGHT_RED = '\033[101m'
49
+ BKG_BRIGHT_GREEN = '\033[102m'
50
+ BKG_BRIGHT_YELLOW = '\033[103m'
51
+ BKG_BRIGHT_BLUE = '\033[104m'
52
+ BKG_BRIGHT_MAGENTA = '\033[105m'
53
+ BKG_BRIGHT_CYAN = '\033[106m'
54
+
55
+ BKG = [BKG_WHITE, BKG_MEDIUM_GRAY, BKG_BLACK, BKG_RED, BKG_GREEN, BKG_YELLOW, BKG_BLUE, BKG_MAGENTA, BKG_CYAN, BKG_BRIGHT_WHITE, BKG_BRIGHT_RED, BKG_BRIGHT_GREEN, BKG_BRIGHT_YELLOW, BKG_BRIGHT_BLUE, BKG_BRIGHT_MAGENTA, BKG_BRIGHT_CYAN]
56
+
57
+ RESET = '\033[0m'
58
+
59
+ URL_START = '\033]8;;'
60
+ URL_TEXT = '\a'
61
+ URL_END = '\033]8;;\a'
62
+
63
+ ALL = BKG + TEXT + STYLES + END + [RESET, URL_START, URL_TEXT, URL_END]
64
+
65
+ GLYPHS = {
66
+ 'ahead': ['\uf062', 'Ahead'],
67
+ 'behind': ['\uf063', 'Behind'],
68
+ 'modified': ['\uf040', '*'],
69
+ 'added': ['\uf067', '+'],
70
+ 'removed': ['\uf1f8', '-'],
71
+ 'moved': ['\uf064', 'M'],
72
+ 'clear': ['\uf00c', 'Clear'],
73
+ 'synced': ['\uf00c', 'Up to date'],
74
+ 'master': ['\uf015', ''],
75
+ 'bugfix': ['\uf188', ''],
76
+ 'release': ['\uf135', ''],
77
+ 'feature': ['\uf0ad', ''],
78
+ 'test': ['\uf0c3', ''],
79
+ 'branch': ['\ue725', ''],
80
+ 'failed': ['\uf00d', 'Failed'],
81
+ 'finished': ['\uf00c', 'Finished'],
82
+ 'running': ['\uf46a', 'Running'],
83
+ 'label': ['\uf435', ''],
84
+ 'tag': ['\uf02b', '>'],
85
+ 'terminal': ['\ue795', ''],
86
+ '(': ['\uE0B2', ''],
87
+ ')': ['\uE0B0', ' '],
88
+ 'weight': ['\uee94', ''],
89
+ 'space': [' ', ''],
90
+ 'git': ['\uefa0', ''],
91
+ 'github': ['\uf09b', ''],
92
+ 'gitlab': ['\uf296', ''],
93
+ 'azure': ['\uebe8', ''],
94
+ 'bitbucket': ['\ue703', '']
95
+ }