aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvg <vgm+dev@devys.org>2024-04-04 15:20:01 +0200
committervg <vgm+dev@devys.org>2024-04-04 15:20:01 +0200
commit8ea01476f31691fb628163e8dd2320abd76a901a (patch)
tree99ff5d25aece5472c2a17035a8fd7b9c95b62298
parentd3a1c560e6a6f58d0159864f70ec75fb07ed1933 (diff)
downloadscripts-8ea01476f31691fb628163e8dd2320abd76a901a.tar.gz
scripts-8ea01476f31691fb628163e8dd2320abd76a901a.tar.bz2
scripts-8ea01476f31691fb628163e8dd2320abd76a901a.zip
Update fancy_sleep.py script
- fancy-sleep now accepts an optional command. - time parsing is done on a single argument to differentiate between time and command. - better display by disable tty echo to avoid breaking the fancy display while countdown is active. - simpler code for keyboardexception handling.
-rwxr-xr-xscripts/fancy_sleep.py173
1 files changed, 96 insertions, 77 deletions
diff --git a/scripts/fancy_sleep.py b/scripts/fancy_sleep.py
index b56c7cb..5839a32 100755
--- a/scripts/fancy_sleep.py
+++ b/scripts/fancy_sleep.py
@@ -1,25 +1,36 @@
#!/usr/bin/env python3
-# Copyright 2017 vgm+dev@devys.org
+# Copyright 2024 vgm+dev@devys.org
# SPDX-License-Identifier: MIT
'''
Simple and fancy sleep for console.
-Time can be precised as XX[h|m|s] [YY[m|s]] [ZZ[s]] or XX:YY[:ZZ].
+Time can be precised as [DDd][HHh][MMm][SS[s]]. Number of digits for days,
+hours, minutes or seconds are not restricted. Every type (days, hours,
+minutes, seconds) are optional, but at least one must be given. If no suffix
+is given, it means seconds. Spaces between group of types can be given but
+TIME as a whole must be a single argument.
-Usage: fancy-sleep [options] TIME...
+Time can also be given as HH:MM or HH:MM:SS.
+
+An optional command to run at the end of the time can be given.
+
+Usage: fancy-sleep [options] TIME [--] [COMMAND...]
fancy-sleep -h|--help
Options:
-h, --help Display this help message
+
'''
import contextlib
import datetime
+import os
+import re
import sys
+import termios
import time
-import re
import docopt
@@ -124,104 +135,112 @@ def print_translated(string):
print('\n'.join(display_lines), end='')
-def determine_delta(elements,
- time_pattern=re.compile(r'^(\d+)([dhms]?)$')):
- 'return a cumulative datetime.timedelta from elements list of strings'
+def parse_time(times, time_pattern=re.compile(r'(\d+)([dhms]?)')):
+ 'return a cumulative datetime.timedelta from time string (times)'
default = datetime.datetime.strptime('0', '%S')
timedelta = datetime.timedelta()
- for element in elements:
- groups = time_pattern.match(element)
- if groups:
- if groups.group(2) == 'd':
- timedelta += datetime.timedelta(days=float(groups.group(1)))
- elif groups.group(2) == 'h':
- timedelta += datetime.timedelta(hours=float(groups.group(1)))
- elif groups.group(2) == 'm':
- timedelta += datetime.timedelta(minutes=float(groups.group(1)))
- elif groups.group(2) == 's' or groups.group(2) == '':
- timedelta += datetime.timedelta(seconds=float(groups.group(1)))
-
-
- #with contextlib.suppress(ValueError):
- # timedelta += datetime.datetime.strptime(element, '%dd') - default
- #with contextlib.suppress(ValueError):
- # timedelta += datetime.datetime.strptime(element, '%Hh') - default
- #with contextlib.suppress(ValueError):
- # timedelta += datetime.datetime.strptime(element, '%Mm') - default
- #with contextlib.suppress(ValueError):
- # timedelta += datetime.datetime.strptime(element, '%Ss') - default
- #with contextlib.suppress(ValueError):
- # timedelta += datetime.datetime.strptime(element, '%S') - default
- else:
- with contextlib.suppress(ValueError):
- timedelta += datetime.datetime.strptime(element, '%H:%M') - default
- with contextlib.suppress(ValueError):
- timedelta += datetime.datetime.strptime(element, '%H:%M:%S') - default
+ try:
+ timedelta += datetime.datetime.strptime(times, '%H:%M:%S') - default
+ return timedelta
+ except ValueError:
+ try:
+ timedelta += datetime.datetime.strptime(times, '%H:%M') - default
+ return timedelta
+ except ValueError:
+ pass
+ for match in time_pattern.finditer(times):
+ if match.group(2) == 'd':
+ timedelta += datetime.timedelta(days=float(match.group(1)))
+ elif match.group(2) == 'h':
+ timedelta += datetime.timedelta(hours=float(match.group(1)))
+ elif match.group(2) == 'm':
+ timedelta += datetime.timedelta(minutes=float(match.group(1)))
+ elif match.group(2) == 's' or match.group(2) == '':
+ timedelta += datetime.timedelta(seconds=float(match.group(1)))
return timedelta
-def test_determine_delta():
- 'test determine_delta function (callable with pytest-3)'
- assert determine_delta('11'.split()) \
+def test_parse_time():
+ 'test parse_time function (callable with pytest-3)'
+ assert parse_time('11') \
== datetime.timedelta(seconds=11)
- assert determine_delta('11s'.split()) \
+ assert parse_time('11s') \
== datetime.timedelta(seconds=11)
- assert determine_delta('11h 47m'.split()) \
+ assert parse_time('11s') == parse_time('11')
+ assert parse_time('11h 47m') \
== datetime.timedelta(hours=11, minutes=47)
- assert determine_delta('11h 47'.split()) \
+ assert parse_time('11h 47m') == parse_time('0d11h47m0s')
+ assert parse_time('11h 47') \
== datetime.timedelta(hours=11, seconds=47)
- assert determine_delta('11h 47m 09'.split()) \
+ assert parse_time('11h 47') == parse_time('11h47s')
+ assert parse_time('11h 47m 09') \
== datetime.timedelta(hours=11, minutes=47, seconds=9)
- assert determine_delta('09:53'.split()) \
+ assert parse_time('11h 47m 09') == parse_time('11h47m9s')
+ assert parse_time('09:53') \
== datetime.timedelta(hours=9, minutes=53)
- assert determine_delta('8:2 9'.split()) \
- == datetime.timedelta(hours=8, minutes=2, seconds=9)
- assert determine_delta('8d 5:32 6s'.split()) \
- == datetime.timedelta(days=8, hours=5, minutes=32, seconds=6)
+ assert parse_time('09:53') == parse_time('09h 53m')
+ assert parse_time('8:2') \
+ == datetime.timedelta(hours=8, minutes=2)
+ assert parse_time('8:2') == parse_time('08:02')
+ assert parse_time('8d 6s') \
+ == datetime.timedelta(days=8, seconds=6)
+ assert parse_time('8d 6s') == parse_time('8d6s')
+
+
+def fancy_sleep_display(target_time):
+ print()
+ print('\n'*5, '\x1b[5A', sep='', end='')
+ print('\x1b[s', end='') # save cursor
+ original_termios_attr = termios.tcgetattr(sys.stdin)
+ noecho_termios_attr = original_termios_attr[:]
+ noecho_termios_attr[3] &= ~termios.ECHO
+ termios.tcsetattr(sys.stdin, termios.TCSANOW, noecho_termios_attr)
+ try:
+ while True:
+ delta = target_time.timestamp() - time.time()
+ if delta <= 0:
+ break
+ hours = int(delta / 3600)
+ minutes = int(delta / 60) % 60
+ seconds = int(delta) % 60
+
+ print('\x1b[u', end='') # restore cursor
+ time_string = '%02d:%02d:%02d' % (hours, minutes, seconds)
+ #print('\rremainining time: %s' % time_string, end='')
+ print_translated(time_string)
+
+ time.sleep(1)
+ finally:
+ print()
+ termios.tcsetattr(sys.stdin, termios.TCSANOW, original_termios_attr)
def main():
'function called only when script invoked directly on command line'
-
args = docopt.docopt(__doc__)
curtime = datetime.datetime.now()
- timedelta = determine_delta(args['TIME'])
+ timedelta = parse_time(args['TIME'])
target_time = curtime + timedelta
+ if timedelta == datetime.timedelta(0):
+ print('ERROR: TIME is unparsable or equals to 0', file=sys.stderr)
+ sys.exit(1)
+
print('Launched at:', curtime.strftime('%F %T'))
print('Run until:', target_time.strftime('%F %T'))
sys.stdout.flush()
- if sys.stdout.isatty():
- print()
-
- print('\n'*5, '\x1b[5A', sep='', end='')
- print('\x1b[s', end='') # save cursor
- try:
- while True:
- delta = target_time.timestamp() - time.time()
- if delta <= 0:
- break
- hours = int(delta / 3600)
- minutes = int(delta / 60) % 60
- seconds = int(delta) % 60
-
- print('\x1b[u', end='') # restore cursor
- time_string = '%02d:%02d:%02d' % (hours, minutes, seconds)
- #print('\rremainining time: %s' % time_string, end='')
- print_translated(time_string)
-
- time.sleep(1)
- except KeyboardInterrupt:
- sys.exit(130)
- finally:
- print()
- else:
- try:
+ try:
+ if not sys.stdout.isatty():
time.sleep(target_time.timestamp() - time.time())
- except KeyboardInterrupt:
- sys.exit(130)
+ else:
+ fancy_sleep_display(target_time)
+ except KeyboardInterrupt:
+ sys.exit(130)
+
+ if args['COMMAND']:
+ os.execvp(args['COMMAND'][0], args['COMMAND'])
if __name__ == '__main__':