skilleter-thingy 0.2.0__py3-none-any.whl → 0.2.1__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.

Potentially problematic release.


This version of skilleter-thingy might be problematic. Click here for more details.

Files changed (32) hide show
  1. skilleter_thingy/borger.py +273 -0
  2. skilleter_thingy/diskspacecheck.py +67 -0
  3. skilleter_thingy/ggit.py +1 -0
  4. skilleter_thingy/ggrep.py +1 -0
  5. skilleter_thingy/git_br.py +7 -0
  6. skilleter_thingy/git_ca.py +8 -0
  7. skilleter_thingy/git_cleanup.py +11 -0
  8. skilleter_thingy/git_co.py +8 -3
  9. skilleter_thingy/git_common.py +12 -4
  10. skilleter_thingy/git_hold.py +9 -0
  11. skilleter_thingy/git_mr.py +11 -0
  12. skilleter_thingy/git_parent.py +23 -18
  13. skilleter_thingy/git_retag.py +10 -0
  14. skilleter_thingy/git_review.py +1 -0
  15. skilleter_thingy/git_update.py +1 -0
  16. skilleter_thingy/git_wt.py +2 -0
  17. skilleter_thingy/gitprompt.py +1 -0
  18. skilleter_thingy/localphotosync.py +201 -0
  19. skilleter_thingy/moviemover.py +133 -0
  20. skilleter_thingy/photodupe.py +135 -0
  21. skilleter_thingy/phototidier.py +248 -0
  22. skilleter_thingy/splitpics.py +99 -0
  23. skilleter_thingy/sysmon.py +435 -0
  24. skilleter_thingy/thingy/git.py +18 -5
  25. skilleter_thingy/thingy/git2.py +20 -7
  26. skilleter_thingy/window_rename.py +92 -0
  27. {skilleter_thingy-0.2.0.dist-info → skilleter_thingy-0.2.1.dist-info}/METADATA +46 -1
  28. {skilleter_thingy-0.2.0.dist-info → skilleter_thingy-0.2.1.dist-info}/RECORD +32 -23
  29. {skilleter_thingy-0.2.0.dist-info → skilleter_thingy-0.2.1.dist-info}/entry_points.txt +9 -0
  30. {skilleter_thingy-0.2.0.dist-info → skilleter_thingy-0.2.1.dist-info}/WHEEL +0 -0
  31. {skilleter_thingy-0.2.0.dist-info → skilleter_thingy-0.2.1.dist-info}/licenses/LICENSE +0 -0
  32. {skilleter_thingy-0.2.0.dist-info → skilleter_thingy-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,435 @@
1
+ #!/usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """Very simple system monitoring dashboard"""
5
+ ################################################################################
6
+
7
+ import sys
8
+ import datetime
9
+ import time
10
+ import curses
11
+
12
+ import psutil
13
+
14
+ ################################################################################
15
+
16
+ NUM_BOXES_V = 5
17
+ NUM_BOXES_H = 2
18
+
19
+ UPDATE_PERIOD = 1
20
+
21
+ ################################################################################
22
+
23
+ def show_system_load(scr, first, w, h, x, y):
24
+ """Load averaged"""
25
+
26
+ load = psutil.getloadavg()
27
+
28
+ x += 2
29
+
30
+ if first:
31
+ scr.addstr(y+1, x, '1 minute:')
32
+ scr.addstr(y+2, x, '5 minute:')
33
+ scr.addstr(y+3, x, '15 minute:')
34
+ else:
35
+ scr.addstr(y+1, x+10, f'{load[0]:6.2f}')
36
+ scr.addstr(y+2, x+10, f'{load[1]:6.2f}')
37
+ scr.addstr(y+3, x+10, f'{load[2]:6.2f}')
38
+
39
+ ################################################################################
40
+
41
+ def show_cpu_times(scr, first, w, h, x, y):
42
+ """CPU times"""
43
+
44
+ info = psutil.cpu_times()
45
+
46
+ x += 2
47
+
48
+ if first:
49
+ scr.addstr(y+1, x, 'Idle:')
50
+ scr.addstr(y+2, x, 'System:')
51
+ scr.addstr(y+3, x, 'User:')
52
+ scr.addstr(y+4, x, 'Nice:')
53
+
54
+ x += w//3
55
+
56
+ scr.addstr(y+1, x, 'I/O Wait:')
57
+ scr.addstr(y+2, x, 'IRQ:')
58
+ scr.addstr(y+3, x, 'Soft IRQ:')
59
+
60
+ x += w//3
61
+
62
+ scr.addstr(y+1, x, 'Guest:')
63
+ scr.addstr(y+2, x, 'Guest Nice:')
64
+ else:
65
+ total = (info.user + info.system + info.idle + info.nice + info.iowait + info.irq + info.softirq + info.guest + info.guest_nice)/100
66
+
67
+ user = info.user / total
68
+ system = info.system / total
69
+ idle = info.idle / total
70
+ nice = info.nice / total
71
+ iowait = info.iowait / total
72
+ irq = info.irq / total
73
+ softirq = info.softirq / total
74
+ guest = info.guest / total
75
+ guest_nice = info.guest_nice / total
76
+
77
+ scr.addstr(y+1, x+9, f'{idle:6.2f}')
78
+ scr.addstr(y+2, x+9, f'{system:6.2f}')
79
+ scr.addstr(y+3, x+9, f'{user:6.2f}')
80
+ scr.addstr(y+4, x+9, f'{nice:6.2f}')
81
+
82
+ x += w//3
83
+
84
+ scr.addstr(y+1, x+10, f'{iowait:6.2f}')
85
+ scr.addstr(y+2, x+10, f'{irq:6.2f}')
86
+ scr.addstr(y+3, x+10, f'{softirq:6.2f}')
87
+
88
+ x += w//3
89
+
90
+ scr.addstr(y+1, x+12, f'{guest:6.2f}')
91
+ scr.addstr(y+2, x+12, f'{guest_nice:6.2f}')
92
+
93
+ ################################################################################
94
+
95
+ def show_disk_access(scr, first, w, h, x, y):
96
+ """Disk I/O statistics"""
97
+
98
+ info = psutil.disk_io_counters()
99
+
100
+ x += 2
101
+
102
+ if first:
103
+ scr.addstr(y+1, x, 'Read count:')
104
+ scr.addstr(y+2, x, 'Write count:')
105
+
106
+ scr.addstr(y+4, x, 'Read bytes:')
107
+ scr.addstr(y+5, x, 'Write bytes:')
108
+
109
+ x += w//3
110
+
111
+ scr.addstr(y+1, x, 'Read time:')
112
+ scr.addstr(y+2, x, 'Write time:')
113
+
114
+ scr.addstr(y+4, x, 'I/O time:')
115
+
116
+ x += w//3
117
+
118
+ scr.addstr(y+1, x, 'Read merged:')
119
+ scr.addstr(y+2, x, 'Write merged:')
120
+ else:
121
+ scr.addstr(y+1, x+14, f'{info.read_count:12}')
122
+ scr.addstr(y+2, x+14, f'{info.write_count:12}')
123
+
124
+ scr.addstr(y+4, x+14, f'{info.read_bytes:12}')
125
+ scr.addstr(y+5, x+14, f'{info.write_bytes:12}')
126
+
127
+ x += w//3
128
+
129
+ scr.addstr(y+1, x+14, f'{info.read_time:12}')
130
+ scr.addstr(y+2, x+14, f'{info.write_time:12}')
131
+
132
+ scr.addstr(y+4, x+14, f'{info.busy_time:12}')
133
+
134
+ x += w//3
135
+
136
+ scr.addstr(y+1, x+14, f'{info.read_merged_count:12}')
137
+ scr.addstr(y+2, x+14, f'{info.write_merged_count:12}')
138
+
139
+ ################################################################################
140
+
141
+ def show_processes(scr, first, w, h, x, y):
142
+ """TBD: Process information"""
143
+
144
+ pass
145
+
146
+ ################################################################################
147
+
148
+ def show_memory(scr, first, w, h, x, y):
149
+ """Memory usage"""
150
+
151
+ x += 2
152
+
153
+ if first:
154
+ scr.addstr(y+1, x, 'Total:')
155
+ scr.addstr(y+2, x, 'Used:')
156
+ scr.addstr(y+3, x, 'Buffers:')
157
+ scr.addstr(y+4, x, 'Free:')
158
+
159
+ x += w//3
160
+
161
+ scr.addstr(y+1, x, 'Active:')
162
+ scr.addstr(y+2, x, 'Inactive:')
163
+
164
+ scr.addstr(y+4, x, 'Usage:')
165
+
166
+ x += w//3
167
+
168
+ scr.addstr(y+1, x, 'Shared:')
169
+ scr.addstr(y+2, x, 'Slab:')
170
+ else:
171
+ meminfo = psutil.virtual_memory()
172
+ x += 11
173
+
174
+ scr.addstr(y+1, x, f'{meminfo.total:12}')
175
+ scr.addstr(y+2, x, f'{meminfo.used:12}')
176
+ scr.addstr(y+3, x, f'{meminfo.buffers:12}')
177
+ scr.addstr(y+4, x, f'{meminfo.free:12}')
178
+
179
+ x += w//3
180
+
181
+ scr.addstr(y+1, x, f'{meminfo.active:12}')
182
+ scr.addstr(y+2, x, f'{meminfo.inactive:12}')
183
+
184
+ scr.addstr(y+4, x, f'{meminfo.percent:6.1f}%')
185
+
186
+ x += w//3
187
+
188
+ scr.addstr(y+1, x, f'{meminfo.shared:12}')
189
+ scr.addstr(y+2, x, f'{meminfo.slab:12}')
190
+
191
+ ################################################################################
192
+
193
+ def show_voltages(scr, first, w, h, x, y):
194
+ """TBD: Voltages"""
195
+
196
+ pass
197
+
198
+ ################################################################################
199
+
200
+ def show_cpu_load(scr, first, w, h, x, y):
201
+ """CPU load and frequencies"""
202
+
203
+ info = psutil.cpu_percent(percpu=True)
204
+ freq = psutil.cpu_freq(percpu=True)
205
+
206
+ stats = psutil.cpu_stats()
207
+
208
+ xo = yo = 0
209
+ for n, cpu in enumerate(info):
210
+ if first:
211
+ scr.addstr(y+yo+1, x+xo+5, 'CPU # : % at MHz')
212
+ else:
213
+ scr.addstr(y+yo+1, x+xo+10, f'{n:<2}')
214
+ scr.addstr(y+yo+1, x+xo+14, f'{cpu:5.1f}%')
215
+ scr.addstr(y+yo+1, x+xo+23, f'{freq[n].current:8.2f}')
216
+
217
+ yo += 1
218
+
219
+ if yo > h-2:
220
+ xo += w//3
221
+ yo = 0
222
+
223
+ x += w//2
224
+
225
+ if first:
226
+ scr.addstr(y+1, x, 'Context switches:')
227
+ scr.addstr(y+2, x, 'Interrupts:')
228
+ scr.addstr(y+3, x, 'Soft interrupts:')
229
+ else:
230
+ x += 18
231
+ scr.addstr(y+1, x, f'{stats.ctx_switches:12}')
232
+ scr.addstr(y+2, x, f'{stats.interrupts:12}')
233
+ scr.addstr(y+3, x, f'{stats.soft_interrupts:12}')
234
+
235
+ ################################################################################
236
+
237
+ def show_swappery(scr, first, w, h, x, y):
238
+ """Swap info"""
239
+
240
+ x += 2
241
+
242
+ if first:
243
+ scr.addstr(y+1, x, 'Swap total:')
244
+ scr.addstr(y+2, x, 'Swap used:')
245
+ scr.addstr(y+3, x, 'Swap free:')
246
+
247
+ x += w//3
248
+
249
+ scr.addstr(y+1, x, 'Swap used:')
250
+
251
+ x += w//3
252
+
253
+ scr.addstr(y+1, x, 'Swapped in:')
254
+ scr.addstr(y+2, x, 'Swapped out:')
255
+ else:
256
+ info = psutil.swap_memory()
257
+
258
+ x += 14
259
+
260
+ scr.addstr(y+1, x, f'{info.total:12}')
261
+ scr.addstr(y+2, x, f'{info.used:12}')
262
+ scr.addstr(y+3, x, f'{info.free:12}')
263
+
264
+ x += w//3
265
+
266
+ scr.addstr(y+1, x, f'{info.percent:6.2f}%')
267
+
268
+ x += w//3
269
+
270
+ scr.addstr(y+1, x, f'{info.sin:12}')
271
+ scr.addstr(y+2, x, f'{info.sout:12}')
272
+
273
+ ################################################################################
274
+
275
+ def show_network(scr, first, w, h, x, y):
276
+ """Network statistics"""
277
+
278
+ x += 2
279
+
280
+ if first:
281
+ scr.addstr(y+1, x, 'Bytes sent:')
282
+ scr.addstr(y+2, x, 'Bytes received:')
283
+
284
+ scr.addstr(y+4, x, 'Packets sent:')
285
+ scr.addstr(y+5, x, 'Packets received:')
286
+
287
+ x += w//2
288
+
289
+ scr.addstr(y+1, x, 'Send errors:')
290
+ scr.addstr(y+2, x, 'Receive errors:')
291
+
292
+ scr.addstr(y+4, x, 'Outgoing dropped:')
293
+ scr.addstr(y+5, x, 'Incoming dropped:')
294
+ else:
295
+ info = psutil.net_io_counters()
296
+ x += 19
297
+
298
+ scr.addstr(y+1, x, f'{info.bytes_sent:12}')
299
+ scr.addstr(y+2, x, f'{info.bytes_recv:12}')
300
+
301
+ scr.addstr(y+4, x, f'{info.packets_sent:12}')
302
+ scr.addstr(y+5, x, f'{info.packets_recv:12}')
303
+
304
+ x += w//2
305
+
306
+ scr.addstr(y+1, x, f'{info.errout:12}')
307
+ scr.addstr(y+2, x, f'{info.errin:12}')
308
+
309
+ scr.addstr(y+4, x, f'{info.dropout:12}')
310
+ scr.addstr(y+5, x, f'{info.dropin:12}')
311
+
312
+ ################################################################################
313
+
314
+ def show_temperatures(scr, first, w, h, x, y):
315
+ """TBD: Temperatures"""
316
+
317
+ pass
318
+
319
+ ################################################################################
320
+
321
+ # Panel title and the functions used to update them
322
+
323
+ BOXES = {
324
+ 'System Load': show_system_load,
325
+ 'Disk Access': show_disk_access,
326
+ 'Processes': show_processes,
327
+ 'Memory': show_memory,
328
+ 'Voltages': show_voltages,
329
+
330
+ 'CPU Load': show_cpu_load,
331
+ 'Swap and Paging': show_swappery,
332
+ 'Temperatures': show_temperatures,
333
+ 'Network': show_network,
334
+ 'Total CPU Times': show_cpu_times,
335
+ }
336
+
337
+ ################################################################################
338
+
339
+ def main(stdscr):
340
+ """Main function"""
341
+
342
+ # Configure curses
343
+
344
+ curses.curs_set(0)
345
+ curses.start_color()
346
+ curses.noecho()
347
+ curses.cbreak()
348
+ curses.use_default_colors()
349
+
350
+ curses.init_pair(1, curses.COLOR_GREEN, 15)
351
+ curses.init_pair(2, curses.COLOR_BLUE, 15)
352
+
353
+ # Set up the display
354
+
355
+ stdscr.keypad(1)
356
+ stdscr.nodelay(True)
357
+ stdscr.bkgdset(' ', curses.color_pair(0))
358
+
359
+ # Outer loop iterates whenever the console window changes size
360
+
361
+ terminate = False
362
+
363
+ while not terminate:
364
+ stdscr.clear()
365
+
366
+ height, width = stdscr.getmaxyx()
367
+
368
+ box_h = height // NUM_BOXES_V
369
+ box_w = width // NUM_BOXES_H
370
+
371
+ # Draw the titles and text on the first iteration
372
+ # Just draw the statistics on the subsequent ones
373
+
374
+ first_time = True
375
+ window_resize = False
376
+
377
+ # Inner loop just updates display until
378
+
379
+ while not window_resize and not terminate:
380
+ now = datetime.datetime.now()
381
+
382
+ stdscr.addstr(0, 1, now.strftime('%02H:%02M:%02S'), curses.COLOR_BLACK)
383
+ stdscr.addstr(0, width-11, now.strftime('%02Y-%02m-%02d'), curses.COLOR_BLACK)
384
+
385
+ for i, box in enumerate(BOXES):
386
+ x, y = divmod(i, NUM_BOXES_V)
387
+
388
+ x *= box_w
389
+ y *= box_h
390
+
391
+ title_x = x+(box_w - len(box))//2
392
+ stdscr.addstr(y, title_x, box, curses.A_BOLD)
393
+
394
+ stdscr.attron(curses.color_pair(1 if first_time else 2))
395
+
396
+ BOXES[box](stdscr, first_time, box_w, box_h, x, y+1)
397
+
398
+ # Update the display, clear the first-time draw-static-content flag
399
+
400
+ stdscr.refresh()
401
+ first_time = False
402
+
403
+ # Wait for the next update
404
+
405
+ elapsed = (datetime.datetime.now() - now).total_seconds()
406
+
407
+ if elapsed < UPDATE_PERIOD:
408
+ time.sleep(UPDATE_PERIOD - elapsed)
409
+
410
+ # Check for keypress or window resize
411
+
412
+ keyboard = stdscr.getch()
413
+
414
+ if keyboard in (ord('Q'), ord('q')):
415
+ terminate = True
416
+
417
+ elif keyboard == curses.KEY_RESIZE:
418
+ window_resize = True
419
+
420
+ ################################################################################
421
+
422
+ def sysmon():
423
+ """Entry point"""
424
+
425
+ try:
426
+ curses.wrapper(main)
427
+ except KeyboardInterrupt:
428
+ sys.exit(1)
429
+ except BrokenPipeError:
430
+ sys.exit(2)
431
+
432
+ ################################################################################
433
+
434
+ if __name__ == "__main__":
435
+ sysmon()
@@ -34,7 +34,11 @@ import thingy.gitlab as gitlab
34
34
  ################################################################################
35
35
  # Configuration files to access
36
36
 
37
- (LOCAL, GLOBAL, SYSTEM) = list(range(3))
37
+ (WORKTREE, LOCAL, GLOBAL, SYSTEM) = list(range(4))
38
+
39
+ # Default default branches (can be overridden in .gitconfig via skilleter-thingy.defaultBranches
40
+
41
+ DEFAULT_DEFAULT_BRANCHES = 'develop,main,master'
38
42
 
39
43
  ################################################################################
40
44
 
@@ -814,7 +818,7 @@ def reset(sha1):
814
818
 
815
819
  ################################################################################
816
820
 
817
- def config_get(section, key, source=LOCAL, defaultvalue=None):
821
+ def config_get(section, key, source=None, defaultvalue=None):
818
822
  """ Return the specified configuration entry
819
823
  Returns a default value if no matching configuration entry exists """
820
824
 
@@ -824,6 +828,10 @@ def config_get(section, key, source=LOCAL, defaultvalue=None):
824
828
  cmd.append('--global')
825
829
  elif source == SYSTEM:
826
830
  cmd.append('--system')
831
+ elif source == LOCAL:
832
+ cmd.append('--local')
833
+ elif source == WORKTREE:
834
+ cmd.append('--worktree')
827
835
 
828
836
  cmd += ['--get', f'{section}.{key}']
829
837
 
@@ -834,7 +842,7 @@ def config_get(section, key, source=LOCAL, defaultvalue=None):
834
842
 
835
843
  ################################################################################
836
844
 
837
- def config_set(section, key, value, source=LOCAL):
845
+ def config_set(section, key, value, source=None):
838
846
  """ Set a configuration entry """
839
847
 
840
848
  cmd = ['config']
@@ -843,6 +851,10 @@ def config_set(section, key, value, source=LOCAL):
843
851
  cmd.append('--global')
844
852
  elif source == SYSTEM:
845
853
  cmd.append('--system')
854
+ elif source == LOCAL:
855
+ cmd.append('--local')
856
+ elif source == WORKTREE:
857
+ cmd.append('--worktree')
846
858
 
847
859
  cmd += ['--replace-all', f'{section}.{key}', value]
848
860
 
@@ -1172,12 +1184,13 @@ def default_branch():
1172
1184
  return None
1173
1185
 
1174
1186
  git_branches = branches()
1187
+ default_branches = config_get('skilleter-thingy', 'defaultBranches', defaultvalue=DEFAULT_DEFAULT_BRANCHES).split(',')
1175
1188
 
1176
- for branch in ('develop', 'main', 'master'):
1189
+ for branch in default_branches:
1177
1190
  if branch in git_branches:
1178
1191
  return branch
1179
1192
 
1180
- return None
1193
+ raise GitError('Unable to determine default branch in the repo')
1181
1194
 
1182
1195
  ################################################################################
1183
1196
 
@@ -36,13 +36,17 @@ import thingy.gitlab as gitlab
36
36
  ################################################################################
37
37
  # Configuration files to access
38
38
 
39
- (LOCAL, GLOBAL, SYSTEM) = list(range(3))
39
+ (WORKTREE, LOCAL, GLOBAL, SYSTEM) = list(range(4))
40
40
 
41
41
  # Options always passed to Git (disable autocorrect to stop it running the wrong command
42
42
  # if an invalid command name has been specified).
43
43
 
44
44
  STANDARD_GIT_OPTIONS = ['-c', 'help.autoCorrect=never']
45
45
 
46
+ # Default default branches (can be overridden in .gitconfig via skilleter-thingy.defaultBranches
47
+
48
+ DEFAULT_DEFAULT_BRANCHES = 'develop,main,master'
49
+
46
50
  ################################################################################
47
51
 
48
52
  class GitError(run.RunError):
@@ -74,7 +78,7 @@ def git(cmd, stdout=None, stderr=None, path=None):
74
78
  try:
75
79
  return run.run(git_cmd, stdout=stdout, stderr=stderr)
76
80
  except run.RunError as exc:
77
- raise GitError(exc.msg, exc.status)
81
+ raise GitError(exc.msg, exc.status) from exc
78
82
 
79
83
  ################################################################################
80
84
 
@@ -103,7 +107,7 @@ def git_run_status(cmd, stdout=None, stderr=None, path=None, redirect=True):
103
107
  errors='ignore',
104
108
  universal_newlines=True)
105
109
  else:
106
- result = subprocess.run(git_cmd)
110
+ result = subprocess.run(git_cmd, check=False)
107
111
 
108
112
  return (result.stdout or result.stderr), result.returncode
109
113
 
@@ -775,7 +779,7 @@ def reset(sha1, path=None):
775
779
 
776
780
  ################################################################################
777
781
 
778
- def config_get(section, key, source=LOCAL, defaultvalue=None, path=None):
782
+ def config_get(section, key, source=None, defaultvalue=None, path=None):
779
783
  """ Return the specified configuration entry
780
784
  Returns a default value if no matching configuration entry exists """
781
785
 
@@ -785,6 +789,10 @@ def config_get(section, key, source=LOCAL, defaultvalue=None, path=None):
785
789
  cmd.append('--global')
786
790
  elif source == SYSTEM:
787
791
  cmd.append('--system')
792
+ elif source == LOCAL:
793
+ cmd.append('--local')
794
+ elif source == WORKTREE:
795
+ cmd.append('--worktree')
788
796
 
789
797
  cmd += ['--get', f'{section}.{key}']
790
798
 
@@ -795,7 +803,7 @@ def config_get(section, key, source=LOCAL, defaultvalue=None, path=None):
795
803
 
796
804
  ################################################################################
797
805
 
798
- def config_set(section, key, value, source=LOCAL, path=None):
806
+ def config_set(section, key, value, source=None, path=None):
799
807
  """ Set a configuration entry """
800
808
 
801
809
  cmd = ['config']
@@ -804,6 +812,10 @@ def config_set(section, key, value, source=LOCAL, path=None):
804
812
  cmd.append('--global')
805
813
  elif source == SYSTEM:
806
814
  cmd.append('--system')
815
+ elif source == LOCAL:
816
+ cmd.append('--local')
817
+ elif source == WORKTREE:
818
+ cmd.append('--worktree')
807
819
 
808
820
  cmd += ['--replace-all', f'{section}.{key}', value]
809
821
 
@@ -1130,12 +1142,13 @@ def default_branch(path=None):
1130
1142
  return None
1131
1143
 
1132
1144
  git_branches = branches(path=path)
1145
+ default_branches = config_get('skilleter-thingy', 'defaultBranches', defaultvalue=DEFAULT_DEFAULT_BRANCHES).split(',')
1133
1146
 
1134
- for branch in ('develop', 'main', 'master'):
1147
+ for branch in default_branches:
1135
1148
  if branch in git_branches:
1136
1149
  return branch
1137
1150
 
1138
- return None
1151
+ raise GitError('Unable to determine default branch in the repo')
1139
1152
 
1140
1153
  ################################################################################
1141
1154
 
@@ -0,0 +1,92 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """
5
+ Monitor window titles and rename them to fit an alphabetical grouping
6
+ in 'Appname - Document' format.
7
+ """
8
+ ################################################################################
9
+
10
+ import sys
11
+ import re
12
+ import subprocess
13
+
14
+ ################################################################################
15
+ # Hard coded table of regexes for window titles to rename
16
+ # TODO: This should be a configuration file
17
+
18
+ RENAMES = [
19
+ [r'^\[(.*)\]$', r'\1'],
20
+ [r'(.*) - Mozilla Firefox', r'Firefox - \1'],
21
+ [r'(.*) - Mozilla Thunderbird', r'Thunderbird - \1'],
22
+ [r'\[(.*) - KeePass\]', r'Keepass - \1'],
23
+ [r'(.*) - LibreOffice Calc', r'LibreOffice Calc - \1'],
24
+ [r'(.*) - Zim$', r'Zim - \1'],
25
+ [r'(.*) - Chromium$', r'Chromium - \1'],
26
+ ]
27
+
28
+ ################################################################################
29
+
30
+ def main():
31
+ """ Search for windows and rename them appropriately """
32
+
33
+ # Build the re. to match renameable windows
34
+
35
+ regex = '|'.join([rename[0] for rename in RENAMES])
36
+
37
+ # Build the command to wait for a matching visible window to appear
38
+
39
+ cmd = ['xdotool', 'search', '--sync', '--name', '--onlyvisible', regex]
40
+
41
+ while True:
42
+ # Wait for one or more matching windows
43
+
44
+ result = subprocess.run(cmd, stdout=subprocess.PIPE, universal_newlines=True)
45
+
46
+ if result.returncode:
47
+ sys.stderr.write('ERROR %d returned from xdotool search, ignoring it.\n' % result.returncode)
48
+ continue
49
+
50
+ # Parse the list of window IDs
51
+
52
+ for window_id in result.stdout.split('\n'):
53
+ if window_id:
54
+ # Get the window name
55
+
56
+ names = subprocess.run(['xdotool', 'getwindowname', window_id], stdout=subprocess.PIPE, universal_newlines=True)
57
+
58
+ if result.returncode:
59
+ sys.stderr.write('ERROR %d returned from xdotool getwindowname, ignoring it.\n' % result.returncode)
60
+ return
61
+
62
+ name = names.stdout.split('\n')[0].strip()
63
+
64
+ if name:
65
+ # If it matches an entry in the list then rename it
66
+
67
+ for entry in RENAMES:
68
+ new_name = re.sub(entry[0], entry[1], name)
69
+
70
+ if new_name != name:
71
+ print('%s -> %s' % (name, new_name))
72
+ subprocess.run(['wmctrl', '-i', '-r', window_id, '-N', new_name])
73
+ if result.returncode:
74
+ sys.stderr.write('ERROR %d returned from wmctrl set_window, ignoring it.\n' % result.returncode)
75
+
76
+ break
77
+
78
+ ################################################################################
79
+
80
+ def window_rename():
81
+ """Entry point"""
82
+ try:
83
+ main()
84
+ except KeyboardInterrupt:
85
+ sys.exit(1)
86
+ except BrokenPipeError:
87
+ sys.exit(2)
88
+
89
+ ################################################################################
90
+
91
+ if __name__ == '__main__':
92
+ window_rename()