In the end I had to attach the hook to the read()
method, but it works!
Any way, here's the full code for those that are interested:
from types import MethodType
import os
ESCAPE = {
'c': { # colours
'fg': { # foreground
'b': '30', # black
'r': '31', # red
'g': '32', # green
'y': '33', # yellow
'u': '34', # blue
'p': '35', # purple
'c': '36', # cyan
'w': '37', # white
},
'bg': { # background
'b': '40', # black
'r': '41', # red
'g': '42', # green
'y': '43', # yellow
'u': '44', # blue
'p': '45', # purple
'c': '46', # cyan
'w': '47', # white
}
},
's': { # style
'n': '0', # none
'b': '1', # bold
'u': '2', # underline
'n1': '3', # negative1
'n2': '5', # negative2
},
't': '\033[{s};{fg};{bg}m', # template
'util': {
'hide': '\033[?25l',
'show': '\033[?25h',
'go_up': '\033[{}A',
'go_down': '\033[{}B',
'erase_line': '\033[K',
}
}
# Open a file but attach an upload progress display if it was requested
def open_file(args, mode='r'):
f = open(args.file_name, mode)
if args.progress:
f.size = os.path.getsize(args.file_name)
f.spinner = spinner(
f"Uploading image({colour(s='b')}{args.file_name}{colour()}) \
as '{colour(s='b')}{args.image_name}{colour()}' -"
)
def read_progress(self, size):
print(
f"{next(self.spinner)}{next(progress(self.size, step=size, p=self.tell()))}", end='\r'
)
f.read = hook(f.read, read_progress)
return f
# Attach a callback to be executed whenever this method is called
# Note, callback args must match method being hooked
def hook(oldfunc, hookfunk):
def merged(self, *args, **kwargs):
hookfunk(self, *args, **kwargs)
return oldfunc(*args, **kwargs)
return MethodType(merged, oldfunc.__self__)
# A spinner with a message, use `next()` to get the next frame
def spinner(msg: str):
template = f"{colour(fg='p', s='b')}{'{}'}{colour()} {msg}"
while True:
for spin in '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏':
yield template.format(spin)
# Generate a progress bar, use `next()` to get the next progress
def progress(end: int, max_col=80, step=1024, p=1):
template = f"[{colour(fg='c', s='b')}{'{}'}{colour(fg='w')}{'{}'}{colour()}|{'{}'}]"
while (p <= end):
bar = '▇' * int(max_col * p/(end-1))
togo = '▇' * int(max_col - len(bar))
perc = "%6.2f %%" % (p/(end-1)*100)
yield template.format(bar, togo, perc)
p += step
# Set the colour of the next segment of text
def colour(fg='w', bg='b', s='n'):
return ESCAPE['t'].format(
s=ESCAPE['s'][s],
fg=ESCAPE['c']['fg'][fg],
bg=ESCAPE['c']['bg'][bg]
)
P.s. I realise that it's not efficient to re-create the progress bar every time, but I don't know any other way to change the step
value each time as the speed of upload is not constant and I still need it to work with next
for use in other places.