skilleter-thingy 0.0.40__py3-none-any.whl → 0.0.41__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 (68) hide show
  1. skilleter_thingy/__init__.py +6 -0
  2. skilleter_thingy/addpath.py +107 -0
  3. skilleter_thingy/borger.py +269 -0
  4. skilleter_thingy/console_colours.py +63 -0
  5. skilleter_thingy/diskspacecheck.py +67 -0
  6. skilleter_thingy/docker_purge.py +113 -0
  7. skilleter_thingy/ffind.py +536 -0
  8. skilleter_thingy/ggit.py +90 -0
  9. skilleter_thingy/ggrep.py +154 -0
  10. skilleter_thingy/git_br.py +180 -0
  11. skilleter_thingy/git_ca.py +142 -0
  12. skilleter_thingy/git_cleanup.py +287 -0
  13. skilleter_thingy/git_co.py +220 -0
  14. skilleter_thingy/git_common.py +61 -0
  15. skilleter_thingy/git_hold.py +154 -0
  16. skilleter_thingy/git_mr.py +92 -0
  17. skilleter_thingy/git_parent.py +77 -0
  18. skilleter_thingy/git_review.py +1428 -0
  19. skilleter_thingy/git_update.py +385 -0
  20. skilleter_thingy/git_wt.py +96 -0
  21. skilleter_thingy/gitcmp_helper.py +322 -0
  22. skilleter_thingy/gitprompt.py +274 -0
  23. skilleter_thingy/gl.py +174 -0
  24. skilleter_thingy/gphotosync.py +610 -0
  25. skilleter_thingy/linecount.py +155 -0
  26. skilleter_thingy/moviemover.py +133 -0
  27. skilleter_thingy/photodupe.py +136 -0
  28. skilleter_thingy/phototidier.py +248 -0
  29. skilleter_thingy/py_audit.py +131 -0
  30. skilleter_thingy/readable.py +270 -0
  31. skilleter_thingy/remdir.py +126 -0
  32. skilleter_thingy/rmdupe.py +550 -0
  33. skilleter_thingy/rpylint.py +91 -0
  34. skilleter_thingy/splitpics.py +99 -0
  35. skilleter_thingy/strreplace.py +82 -0
  36. skilleter_thingy/sysmon.py +435 -0
  37. skilleter_thingy/tfm.py +920 -0
  38. skilleter_thingy/tfparse.py +101 -0
  39. skilleter_thingy/thingy/__init__.py +6 -0
  40. skilleter_thingy/thingy/colour.py +213 -0
  41. skilleter_thingy/thingy/dc_curses.py +278 -0
  42. skilleter_thingy/thingy/dc_defaults.py +221 -0
  43. skilleter_thingy/thingy/dc_util.py +50 -0
  44. skilleter_thingy/thingy/dircolors.py +308 -0
  45. skilleter_thingy/thingy/docker.py +95 -0
  46. skilleter_thingy/thingy/files.py +142 -0
  47. skilleter_thingy/thingy/git.py +1371 -0
  48. skilleter_thingy/thingy/git2.py +1307 -0
  49. skilleter_thingy/thingy/gitlab.py +193 -0
  50. skilleter_thingy/thingy/logger.py +112 -0
  51. skilleter_thingy/thingy/path.py +156 -0
  52. skilleter_thingy/thingy/popup.py +87 -0
  53. skilleter_thingy/thingy/process.py +112 -0
  54. skilleter_thingy/thingy/run.py +334 -0
  55. skilleter_thingy/thingy/tfm_pane.py +595 -0
  56. skilleter_thingy/thingy/tidy.py +160 -0
  57. skilleter_thingy/trimpath.py +84 -0
  58. skilleter_thingy/window_rename.py +92 -0
  59. skilleter_thingy/xchmod.py +125 -0
  60. skilleter_thingy/yamlcheck.py +89 -0
  61. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.41.dist-info}/METADATA +1 -1
  62. skilleter_thingy-0.0.41.dist-info/RECORD +66 -0
  63. skilleter_thingy-0.0.41.dist-info/top_level.txt +1 -0
  64. skilleter_thingy-0.0.40.dist-info/RECORD +0 -6
  65. skilleter_thingy-0.0.40.dist-info/top_level.txt +0 -1
  66. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.41.dist-info}/LICENSE +0 -0
  67. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.41.dist-info}/WHEEL +0 -0
  68. {skilleter_thingy-0.0.40.dist-info → skilleter_thingy-0.0.41.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,99 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Copy a directory full of pictures to a destination, creating subdiretories
5
+ with N pictures in each in the destination directory
6
+ """
7
+ ################################################################################
8
+
9
+ import os
10
+ import glob
11
+ import argparse
12
+
13
+ from PIL import Image
14
+
15
+ ################################################################################
16
+ # Constants
17
+
18
+ DEFAULT_SOURCE_DIR = '/storage/Starred Photos/'
19
+ DEFAULT_DEST_DIR = '/media/jms/48A7-BE16'
20
+ DEFAULT_MAX_SIZE = 3840
21
+
22
+ ################################################################################
23
+
24
+ def parse_command_line():
25
+ """ Parse the command line """
26
+
27
+ parser = argparse.ArgumentParser(description='Copy a collection of pictures to a set of numbered directories')
28
+
29
+ parser.add_argument('--pics', type=int, help='Number of pictures per directory (default is not to use numbered subdirectories)', default=None)
30
+ parser.add_argument('--max-size', type=int, help='Maximum size for each image in pixels (default=%d, images will be resized if larger)' %
31
+ DEFAULT_MAX_SIZE, default=DEFAULT_MAX_SIZE)
32
+ parser.add_argument('source', nargs=1, help='Source directory', default=DEFAULT_SOURCE_DIR)
33
+ parser.add_argument('destination', nargs=1, help='Destination directory', default=DEFAULT_DEST_DIR)
34
+
35
+ args = parser.parse_args()
36
+
37
+ return args
38
+
39
+ ################################################################################
40
+
41
+ def copy_images(args):
42
+ """ Copy the images """
43
+
44
+ dir_num = -1
45
+
46
+ pictures = glob.glob(os.path.join(args.source[0], '*'))
47
+ dest_dir = args.destination[0]
48
+
49
+ if not os.path.isdir(dest_dir):
50
+ os.makedirs(dest_dir)
51
+
52
+ for index, picture in enumerate(pictures):
53
+ picture_name = os.path.basename(picture)
54
+
55
+ # Create the new directory in the destination every N pcitures
56
+
57
+ if args.pics and index % args.pics == 0:
58
+ dir_num += 1
59
+ dest_dir = os.path.join(args.destination[0], '%05d' % dir_num)
60
+ if not os.path.isdir(dest_dir):
61
+ os.makedirs(dest_dir)
62
+
63
+ print('%d/%d: Copying %s to %s' % (index + 1, len(pictures), picture, dest_dir))
64
+
65
+ # Resize the image if neccessary
66
+
67
+ image = Image.open(picture)
68
+
69
+ if args.max_size and (image.width > args.max_size or image.height > args.max_size):
70
+ if image.width > image.height:
71
+ scale = image.width / args.max_size
72
+ else:
73
+ scale = image.height / args.max_size
74
+
75
+ new_size = (round(image.width / scale), round(image.height / scale))
76
+
77
+ print(' Resizing from %d x %d to %d x %d' % (image.width, image.height, new_size[0], new_size[1]))
78
+
79
+ image.resize(new_size)
80
+
81
+ # Write the image
82
+
83
+ destination = os.path.join(dest_dir, picture_name)
84
+
85
+ image.save(destination)
86
+
87
+ ################################################################################
88
+
89
+ def splitpics():
90
+ """Entry point"""
91
+
92
+ args = parse_command_line()
93
+
94
+ copy_images(args)
95
+
96
+ ################################################################################
97
+
98
+ if __name__ == '__main__':
99
+ splitpics()
@@ -0,0 +1,82 @@
1
+ #! /usr/bin/env python3
2
+
3
+ ################################################################################
4
+ """ Textual search and replace
5
+
6
+ For those occasions when you want to search and replace strings with
7
+ regexppy characters that upset sed.
8
+
9
+ Copyright (C) 2018 John Skilleter """
10
+ ################################################################################
11
+
12
+ import os
13
+ import sys
14
+ import argparse
15
+ import tempfile
16
+
17
+ ################################################################################
18
+
19
+ def main():
20
+ """ Main function """
21
+
22
+ parser = argparse.ArgumentParser(description='Textual search and replace')
23
+ parser.add_argument('-i', '--inplace', action='store_true', help='Do an in-place search and replace on the input file')
24
+ parser.add_argument('search', nargs=1, action='store', help='Search text')
25
+ parser.add_argument('replace', nargs=1, action='store', help='Replacment text')
26
+ parser.add_argument('infile', nargs='?', action='store', help='Input file')
27
+ parser.add_argument('outfile', nargs='?', action='store', help='Output file')
28
+
29
+ args = parser.parse_args()
30
+
31
+ # Sanity check
32
+
33
+ if args.inplace and not args.infile or args.outfile:
34
+ print('For in-place operations you must specify and input file and no output file')
35
+
36
+ # Open the input file
37
+
38
+ if args.infile:
39
+ infile = open(args.infile, 'r')
40
+ else:
41
+ infile = sys.stdin
42
+
43
+ # Open the output file, using a temporary file in the same directory as the input file
44
+ # if we are doing in-place operations
45
+
46
+ if args.outfile:
47
+ outfile = open(args.outfile, 'w')
48
+ elif args.inplace:
49
+ outfile = tempfile.NamedTemporaryFile(mode='w', delete=False, dir=os.path.dirname(args.infile))
50
+ else:
51
+ outfile = sys.stdout
52
+
53
+ # Perform the searchy-replacey-ness
54
+
55
+ for data in infile:
56
+ outfile.write(data.replace(args.search[0], args.replace[0]))
57
+
58
+ # If we doing in-place then juggle the temporary and input files
59
+
60
+ if args.inplace:
61
+ mode = os.stat(args.infile).st_mode
62
+ outfile.close()
63
+ infile.close()
64
+ os.rename(outfile.name, args.infile)
65
+ os.chmod(args.infile, mode)
66
+
67
+ ################################################################################
68
+
69
+ def strreplace():
70
+ """Entry point"""
71
+
72
+ try:
73
+ main()
74
+ except KeyboardInterrupt:
75
+ sys.exit(1)
76
+ except BrokenPipeError:
77
+ sys.exit(2)
78
+
79
+ ################################################################################
80
+
81
+ if __name__ == '__main__':
82
+ strreplace()
@@ -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()