"""various commandline tools"""
# this is not good enough as of 2.7.13, which doesn't have
# print(flush=True)
from __future__ import print_function
import getpass
import math
import sys
[docs]
class throbber:
"""weird logarithmic "progress bar"
when a throbber object is called, will display progress using the
provided "symbol"
the throbber will print the symbol every time it's called until it
crosses a logarithmic threshold (the "factor"), at which point the
factor is increased.
this is useful to display progress on large datasets that have an
unknown size (so we can't guess completion time *and* we can't
reasonably guess the progress/display ratio).
originally from the code I wrote for the Euler project
this function requires Python 3.3 at least, because it uses
print(flush=True)
Other progress bars include:
Rich: https://rich.readthedocs.io/en/stable/progress.html
tqdm: https://github.com/tqdm/tqdm
progress: https://pypi.org/project/progress/
progressbar: https://pypi.org/project/progressbar/
"""
def __init__(self, factor=0, stream=sys.stderr, symbol=".", fmt="{}", i=1):
"""build a throbber object and pass along the settings
>>> throbber(stream='')
throbber(i=1, factor=0, stream=, symbol=., fmt={})
"""
self.i = i
self.factor = factor
self.stream = stream
self.symbol = symbol
self.fmt = fmt
def __repr__(self):
"""nicer representation of this object
mainly to ease testing of the constructor
>>> throbber(factor=1,stream='',symbol='!',fmt='{s}')
throbber(i=1, factor=1, stream=, symbol=!, fmt={s})
"""
return "throbber(i={i}, factor={factor}, stream={stream}, symbol={symbol}, fmt={fmt})".format(
**self.__dict__
) # noqa
def __call__(self, symbol=None):
"""increment the counter and potentially print something
>>> t = throbber(stream=sys.stdout)
>>> t()
.
>>> for i in range(1,100): t('+')
++++++++10+++++++++100
>>> # here we overrode the throbber symbol otherwise it breaks doctest
"""
if symbol is None:
symbol = self.symbol
self.i += 1
# put a dot every modulo(log10(i))
if (self.i % 10**self.factor) == 0:
print(symbol, end="", file=self.stream, flush=True)
# and every time we go one log10 higher, slow down the throbber
if (self.i % 10 ** (self.factor + 1)) == 0:
print(self.fmt.format(self.i), end="", file=self.stream, flush=True)
self.factor = math.floor(float(math.log10(self.i)))
[docs]
class Prompter(object):
"""Set of prompt utilities.
This is untested. It mostly comes from Monkeysign, but was
rewritten for notmuch-sync-flagged and in doing so, was
significantly refactored without further tests.
This could possibly be replaced with:
https://github.com/prompt-toolkit/python-prompt-toolkit
https://github.com/Mckinsey666/bullet
"""
[docs]
def yes_no(self, prompt, default="y", choices=["y", "n"]):
"""This will show the given prompt, check if it matches the given
choices, and return True if it matches the first choice
provided. If some "false" string (e.g. empty string which happens
when you just hit "enter") is provided, the default value (which
should be a boolean) is returned.
For unit testing, the input function can be overridden with
input_func.
>>> prompter = Prompter()
>>> prompter.input = lambda x: 'y'
>>> prompter.yes_no('foo')
True
>>> prompter.input = lambda x: 'n'
>>> prompter.yes_no('foo')
False
>>> prompter.input = lambda x: ''
>>> prompter.yes_no('foo', default='y')
True
>>> prompter.yes_no('foo', default='n')
False
"""
return self.pick(prompt, default, choices) == choices[0]
[docs]
def pick(self, prompt, default, choices):
# prompt the user
ans = self.input(prompt)
# reprompt the user if choice is invalid...
while ans.lower() not in choices:
# ... except if the choice is "empty" (e.g. just hit "enter")
# and we have a default to return (true/false)
if not ans.lower() and default is not None:
return default
ans = self.input(prompt)
# first choice is the "yes" or "truth"
return ans.lower()
[docs]
def acknowledge(self, prompt=None):
"""Just wait for the user to hit enter and return."""
if prompt is None:
prompt = "press enter when ready"
return self.input(prompt)