"""progress~~~~~~~~An object for announcing the progress towards a goal."""fromcollectionsimportdequefromdataclassesimportdataclassfromdatetimeimportdatetimefromtypingimportOptional,Sequencefromthurible.panelimportContent,Message,Title# Message classes.
[docs]@dataclassclassNoTick(Message):"""Create a new :class:`thurible.progress.NoTick` object. When sent to :meth:`thurible.Progress.update`, this will not cause the progress bar to advance. :param message: A message to display. :return: A :class:`thurible.NoTick` object. :rtype: thurible.NoTick :usage: To create a message to advance a :class:`thurible.Progress` object with the text "still working...": .. testcode:: import thurible notick = thurible.NoTick('still working...') """message:str=''
[docs]@dataclassclassTick(Message):"""Create a new :class:`thurible.progress.Tick` object. When sent to :meth:`thurible.Progress.update`, this will cause the progress bar to advance. :param message: A message to display. :return: A :class:`thurible.Tick` object. :rtype: thurible.Tick :usage: To create a message to advance a :class:`thurible.Progress` object with the text "another step completed": .. testcode:: import thurible tick = thurible.Tick('another step completed') """message:str=''
# Panel class.
[docs]classProgress(Content,Title):"""Create a new :class:`thurible.Progress` object. This object displays a bar representing how much progress has been achieved towards a goal. As a subclass of :class:`thurible.panel.Content` and :class:`thurible.panel.Title`, it can also take those parameters and has those public methods and properties. :param steps: The number of steps required to achieve the goal. :param progress: (Optional.) The number of steps that have been completed. :param bar_bg: (Optional.) A string describing the background color of the bar. See the documentation for :mod:`blessed` for more detail on the available options. :param bar_fg: (Optional.) A string describing the foreground color of the bar. See the documentation for :mod:`blessed` for more detail on the available options. :param max_messages: (Optional.) How many status messages should be stored to be displayed. :param messages: (Optional.) Any status messages to start in the display. Since new messages are added to the display at the top, the messages passed in this sequence should be stored in reverse chronological order. :param timestamp: (Optional.) Add a timestamp to the messages when they are displayed. :return: A :class:`thurible.Progress` object. :rtype: thurible.Progress :usage: To create a :class:`thurible.Progress` object with six steps: .. testcode:: import thurible progress = thurible.Progress(6) To send an update message to a :class:`thurible.Progress` object that advances the bar use a :class:`thurible.Tick` message: .. testsetup:: progress import thurible progress = thurible.Progress(6) .. testcode:: progress tick = thurible.Tick('First step complete.') progress.update(tick) To send an update message to a :class:`thurible.Progress` object that does not advance the bar use a :class:`thurible.NoTick` message: .. testcode:: progress notick = thurible.NoTick('A thing happened.') progress.update(notick) Information on the sizing of :class:`thurible.Progress` objects can be found in the :ref:`sizing` section below. """def__init__(self,steps:int,progress:int=0,bar_bg:str='',bar_fg:str='',max_messages:int=0,messages:Optional[Sequence[str]]=None,timestamp:bool=False,*args,**kwargs)->None:self._notick=Falseself._t0=datetime.now()self._wrapped_width=-1self.steps=stepsself.progress=progressself.bar_bg=bar_bgself.bar_fg=bar_fgself.max_messages=max_messagesself.timestamp=timestampself.messages:deque=deque(maxlen=self.max_messages)ifmessages:formsginmessages:self._add_message(msg)super().__init__(*args,**kwargs)def__str__(self)->str:"""Return a string that will draw the entire panel."""# Set up.result=super().__str__()height=1+self.max_messagesy=self._align_v('middle',height,self.inner_height)+self.inner_yx=self.content_x# Add the progress bar.result+=self.term.move(y,x)+self.progress_bary+=1# Add messages.ifself.max_messages:result+=self._visible_messages(x,y)# Return the resulting string.returnresult# Properties.@propertydeflines(self)->list[str]:"""The lines of text available to be displayed in the panel after they have been wrapped to fit the width of the interior of the panel. A message from the application may be split into multiple lines. :return: A :class:`list` object containing each line of text as a :class:`str`. :rtype: list """width=self.content_widthifwidth!=self._wrapped_width:wrapped=[]forlineinself.messages:wrapped.extend(self.term.wrap(line,width=width))self._lines=wrappedself._wrapped_width=widthreturnself._lines@propertydefprogress_bar(self)->str:"""The progress bar as a string. :return: A :class:`str` object. :rtype: str """# Color the bar.result=self._get_color(self.bar_fg,self.bar_bg)# Unicode has characters to fill eighths of a character,# so we can resolve progress at eight times the width available# to us.notches=self.content_width*8# Determine the number of notches filled.notches_per_step=notches/self.stepsprogress_notches=notches_per_step*self.progressfull=int(progress_notches//8)part=int(progress_notches%8)# The Unicode characters we are using are the block fill# characters in the range 0x2588–0x258F. This takes# advantage of the fact they are in order to make it# easier to find the one we need.blocks={i:chr(0x2590-i)foriinrange(1,9)}# Build the bar.progress=blocks[8]*fullifpart:progress+=blocks[part]result+=f'{progress:<{self.content_width}}'# If a color was set, return to normal to avoid unexpected# behavior. Then return the string.ifself.bar_bgorself.bar_fg:result+=self.term.normalreturnresult# Public methods.
[docs]defupdate(self,msg:Message)->str:"""Act on a message sent by the application. :class:`thurible.Progress` responds to the following update messages: * :class:`thurible.progress.Tick`: Advance the progress bar and display any message passed. * :class:`thurible.progress.NoTick`: Do not advance the progress bar but display the message passed as a temporary message. The temporary message will be replaced by the next message received. :param msg: A message sent by the application. :return: A :class:`str` object containing any updates needed to be made to the terminal display. :rtype: str """result=''# If a tick is received, advance the progress bar.ifisinstance(msg,Tick):ifself._notickandself.max_messages:self.messages.popleft()self._notick=Falseself.progress+=1ifself.max_messages:self._add_message(msg.message)self._wrapped_width=-1result+=self._make_display()# If a notick is received, update the status messages but# don't advance the progress bar.elifisinstance(msg,NoTick)andself.max_messages:ifself._notick:self.messages.popleft()self._notick=Trueself._add_message(msg.message)self._wrapped_width=-1result+=self._make_display()returnresult