From bfed2c4da0d679c06ce0b83c1619da0eb0b5e6fa Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 16:54:36 +0100 Subject: [PATCH 01/24] strings are also supported as "widgets" --- progressbar/bar.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 806a98a8..6be96e07 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -37,7 +37,7 @@ class ProgressBarMixinBase(abc.ABC): #: fall back to 80 if auto detection is not possible. term_width: int = 80 #: The widgets to render, defaults to the result of `default_widget()` - widgets: types.List[widgets_module.WidgetBase] + widgets: types.List[widgets_module.WidgetBase | str] #: When going beyond the max_value, raise an error if True or silently #: ignore otherwise max_error: bool @@ -439,7 +439,9 @@ def __init__( self, min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, - widgets: types.Optional[types.List[widgets_module.WidgetBase]] = None, + widgets: types.Optional[ + types.List[widgets_module.WidgetBase | str] + ] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, From 3796498c4ebc65aae171cc45250e8809efc11110 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 1 Nov 2022 22:38:21 +0100 Subject: [PATCH 02/24] Added more tests and clarified more errors --- progressbar/bar.py | 34 +++++++++++++++------------------- tests/test_custom_widgets.py | 5 +++++ tests/test_failure.py | 12 ++++++++++++ 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 6be96e07..9a0afd2d 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -111,14 +111,7 @@ def finish(self): # pragma: no cover def __del__(self): if not self._finished and self._started: # pragma: no cover - try: - self.finish() - except Exception: - # Never raise during cleanup. We're too late now - logging.debug( - 'Exception raised during ProgressBar cleanup', - exc_info=True, - ) + self.finish() def __getstate__(self): return self.__dict__ @@ -656,7 +649,7 @@ def data(self) -> types.Dict[str, types.Any]: total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.0), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -787,20 +780,23 @@ def update(self, value=None, force=False, **kwargs): self.start() return self.update(value, force=force, **kwargs) - if value is not None and value is not base.UnknownLength: + if value is not None and value is not base.UnknownLength and isinstance(value, int): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update pass - elif self.min_value <= value <= self.max_value: # pragma: no cover - # Correct value, let's accept - pass - elif self.max_error: + elif self.min_value > value: raise ValueError( - 'Value %s is out of range, should be between %s and %s' + 'Value %s is too small. Should be between %s and %s' % (value, self.min_value, self.max_value) ) - else: - self.max_value = value + elif self.max_value < value: + if self.max_error: + raise ValueError( + 'Value %s is too large. Should be between %s and %s' + % (value, self.min_value, self.max_value) + ) + else: + value = self.max_value self.previous_value = self.value self.value = value @@ -810,8 +806,8 @@ def update(self, value=None, force=False, **kwargs): for key in kwargs: if key not in self.variables: raise TypeError( - 'update() got an unexpected keyword ' - + 'argument {0!r}'.format(key) + 'update() got an unexpected variable name as argument ' + '{0!r}'.format(key) ) elif self.variables[key] != kwargs[key]: self.variables[key] = kwargs[key] diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index 95252818..e757ded5 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -1,4 +1,7 @@ import time + +import pytest + import progressbar @@ -60,6 +63,8 @@ def test_variable_widget_widget(): p.update(i, text=False) i += 1 p.update(i, text=True, error='a') + with pytest.raises(TypeError): + p.update(i, non_existing_variable='error!') p.finish() diff --git a/tests/test_failure.py b/tests/test_failure.py index 030ab292..a389da4b 100644 --- a/tests/test_failure.py +++ b/tests/test_failure.py @@ -122,3 +122,15 @@ def test_variable_not_str(): def test_variable_too_many_strs(): with pytest.raises(ValueError): progressbar.Variable('too long') + + +def test_negative_value(): + bar = progressbar.ProgressBar(max_value=10) + with pytest.raises(ValueError): + bar.update(value=-1) + + +def test_increment(): + bar = progressbar.ProgressBar(max_value=10) + bar.increment() + del bar From f6196caf132096ed50dfc8c7b02888409d57729c Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Wed, 2 Nov 2022 02:47:58 +0100 Subject: [PATCH 03/24] Small type improvements --- progressbar/bar.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 9a0afd2d..b1a5c96b 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -2,6 +2,7 @@ import abc import logging +import math import os import sys import time @@ -11,7 +12,6 @@ from datetime import datetime from typing import Type -import math from python_utils import converters, types from . import ( @@ -37,7 +37,7 @@ class ProgressBarMixinBase(abc.ABC): #: fall back to 80 if auto detection is not possible. term_width: int = 80 #: The widgets to render, defaults to the result of `default_widget()` - widgets: types.List[widgets_module.WidgetBase | str] + widgets: types.MutableSequence[widgets_module.WidgetBase | str] #: When going beyond the max_value, raise an error if True or silently #: ignore otherwise max_error: bool @@ -433,8 +433,7 @@ def __init__( min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, widgets: types.Optional[ - types.List[widgets_module.WidgetBase | str] - ] = None, + types.Sequence[widgets_module.WidgetBase | str]] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, @@ -780,16 +779,19 @@ def update(self, value=None, force=False, **kwargs): self.start() return self.update(value, force=force, **kwargs) - if value is not None and value is not base.UnknownLength and isinstance(value, int): + if value is not None and value is not base.UnknownLength and isinstance( + value, + int + ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update pass - elif self.min_value > value: + elif self.min_value > value: # type: ignore raise ValueError( 'Value %s is too small. Should be between %s and %s' % (value, self.min_value, self.max_value) ) - elif self.max_value < value: + elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( 'Value %s is too large. Should be between %s and %s' @@ -799,7 +801,7 @@ def update(self, value=None, force=False, **kwargs): value = self.max_value self.previous_value = self.value - self.value = value + self.value = value # type: ignore # Save the updated values for dynamic messages variables_changed = False @@ -817,7 +819,7 @@ def update(self, value=None, force=False, **kwargs): self.updates += 1 ResizableMixin.update(self, value=value) ProgressBarBase.update(self, value=value) - StdRedirectMixin.update(self, value=value) + StdRedirectMixin.update(self, value=value) # type: ignore # Only flush if something was actually written self.fd.flush() From 1e04d2792d7dd66bb1e31056cad2e942f8c779cb Mon Sep 17 00:00:00 2001 From: LGTM Migrator Date: Wed, 9 Nov 2022 08:20:47 +0000 Subject: [PATCH 04/24] Add CodeQL workflow for GitHub code scanning --- .github/workflows/codeql.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000..44ccfb21 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,42 @@ +name: "CodeQL" + +on: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + schedule: + - cron: "24 21 * * 1" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ python, javascript ] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + queries: +security-and-quality + + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + if: ${{ matrix.language == 'python' || matrix.language == 'javascript' }} + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{ matrix.language }}" From f26746f99044af9f699ff68c80a43c5c98f80958 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 13 Nov 2022 22:22:52 +0100 Subject: [PATCH 05/24] added ansi code from @kdschlosser --- progressbar/ansi.py | 1042 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1042 insertions(+) create mode 100644 progressbar/ansi.py diff --git a/progressbar/ansi.py b/progressbar/ansi.py new file mode 100644 index 00000000..9b9f2dbc --- /dev/null +++ b/progressbar/ansi.py @@ -0,0 +1,1042 @@ +import threading + +from . import utils +from .os_functions import getch + +ESC = '\x1B' +CSI = ESC + '[' + + +CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) + + +# Report Cursor Position (CPR), response = [row;column] as row;columnR +class _CPR(str): + _response_lock = threading.Lock() + + def __call__(self, stream): + res = '' + + with self._response_lock: + stream.write(str(self)) + stream.flush() + + while not res.endswith('R'): + char = getch() + + if char is not None: + res += char + + res = res[2:-1].split(';') + + res = tuple(int(item) if item.isdigit() else item for item in res) + + if len(res) == 1: + return res[0] + + return res + + def row(self, stream): + row, _ = self(stream) + return row + + def column(self, stream): + _, column = self(stream) + return column + + +DSR = CSI + '{n}n' # Device Status Report (DSR) +CPR = _CPR(DSR.format(n=6)) + +IL = CSI + '{n}L' # Insert n Line(s) (default = 1) + +DECRST = CSI + '?{n}l' # DEC Private Mode Reset +DECRTCEM = DECRST.format(n=25) # Hide Cursor + +DECSET = CSI + '?{n}h' # DEC Private Mode Set +DECTCEM = DECSET.format(n=25) # Show Cursor + + +# possible values: +# 0 = Normal (default) +# 1 = Bold +# 2 = Faint +# 3 = Italic +# 4 = Underlined +# 5 = Slow blink (appears as Bold) +# 6 = Rapid Blink +# 7 = Inverse +# 8 = Invisible, i.e., hidden (VT300) +# 9 = Strike through +# 10 = Primary (default) font +# 20 = Gothic Font +# 21 = Double underline +# 22 = Normal intensity (neither bold nor faint) +# 23 = Not italic +# 24 = Not underlined +# 25 = Steady (not blinking) +# 26 = Proportional spacing +# 27 = Not inverse +# 28 = Visible, i.e., not hidden (VT300) +# 29 = No strike through +# 30 = Set foreground color to Black +# 31 = Set foreground color to Red +# 32 = Set foreground color to Green +# 33 = Set foreground color to Yellow +# 34 = Set foreground color to Blue +# 35 = Set foreground color to Magenta +# 36 = Set foreground color to Cyan +# 37 = Set foreground color to White +# 39 = Set foreground color to default (original) +# 40 = Set background color to Black +# 41 = Set background color to Red +# 42 = Set background color to Green +# 43 = Set background color to Yellow +# 44 = Set background color to Blue +# 45 = Set background color to Magenta +# 46 = Set background color to Cyan +# 47 = Set background color to White +# 49 = Set background color to default (original). +# 50 = Disable proportional spacing +# 51 = Framed +# 52 = Encircled +# 53 = Overlined +# 54 = Neither framed nor encircled +# 55 = Not overlined +# 58 = Set underine color (2;r;g;b) +# 59 = Default underline color +# If 16-color support is compiled, the following apply. +# Assume that xterm’s resources are set so that the ISO color codes are the +# first 8 of a set of 16. Then the aixterm colors are the bright versions of +# the ISO colors: +# 90 = Set foreground color to Black +# 91 = Set foreground color to Red +# 92 = Set foreground color to Green +# 93 = Set foreground color to Yellow +# 94 = Set foreground color to Blue +# 95 = Set foreground color to Magenta +# 96 = Set foreground color to Cyan +# 97 = Set foreground color to White +# 100 = Set background color to Black +# 101 = Set background color to Red +# 102 = Set background color to Green +# 103 = Set background color to Yellow +# 104 = Set background color to Blue +# 105 = Set background color to Magenta +# 106 = Set background color to Cyan +# 107 = Set background color to White +# +# If xterm is compiled with the 16-color support disabled, it supports the +# following, from rxvt: +# 100 = Set foreground and background color to default + +# If 88- or 256-color support is compiled, the following apply. +# 38;5;x = Set foreground color to x +# 48;5;x = Set background color to x +SGR = CSI + '{n}m' # Character Attributes + + +class ENCIRCLED(str): + """ + Your guess is as good as mine. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=52), args[1], SGR.format(n=54)]) + return super(ENCIRCLED, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes encircled? + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=52), SGR.format(n=54)) + + +class FRAMED(str): + """ + Your guess is as good as mine. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=51), args[1], SGR.format(n=54)]) + return super(FRAMED, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes Frame? + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=51), SGR.format(n=54)) + + +class GOTHIC(str): + """ + Changes text font to Gothic + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=20), args[1], SGR.format(n=10)]) + return super(GOTHIC, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Makes text font normal + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=20), SGR.format(n=10)) + + +class ITALIC(str): + """ + Makes the text italic + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=3), args[1], SGR.format(n=23)]) + return super(ITALIC, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the italic. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=3), SGR.format(n=23)) + + +class STRIKE_THROUGH(str): + """ + Strikes through the text. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=9), args[1], SGR.format(n=29)]) + return super(STRIKE_THROUGH, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the strike through + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=9), SGR.format(n=29)) + + +class FAST_BLINK(str): + """ + Makes the text blink fast + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=6), args[1], SGR.format(n=25)]) + return super(FAST_BLINK, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Makes the text steady + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=6), SGR.format(n=25)) + + +class SLOW_BLINK(str): + """ + Makes the text blonk slowely. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=5), args[1], SGR.format(n=25)]) + return super(SLOW_BLINK, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Makes the text steady + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=5), SGR.format(n=25)) + + +class OVERLINE(str): + """ + Overlines the text provided. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=53), args[1], SGR.format(n=55)]) + return super(OVERLINE, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the overline from the text + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=53), SGR.format(n=55)) + + +class UNDERLINE(str): + """ + Underlines the text provided. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=4), args[1], SGR.format(n=24)]) + return super(UNDERLINE, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the underline from the text + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=4), SGR.format(n=24)) + + +class DOUBLE_UNDERLINE(str): + """ + Double underlines the text provided. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=21), args[1], SGR.format(n=24)]) + return super(DOUBLE_UNDERLINE, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the double underline from the text + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=21), SGR.format(n=24)) + + +class BOLD(str): + """ + Makes the supplied text BOLD + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=1), args[1], SGR.format(n=22)]) + return super(BOLD, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the BOLD from the text. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=1), SGR.format(n=22)) + + +class FAINT(str): + """ + Makes the supplied text FAINT + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=2), args[1], SGR.format(n=22)]) + return super(FAINT, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the FAINT from the text. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=2), SGR.format(n=22)) + + +class INVERT_COLORS(str): + """ + Switches the background and forground colors. + """ + + @classmethod + def __new__(cls, *args, **kwargs): + args = list(args) + args[1] = ''.join([SGR.format(n=7), args[1], SGR.format(n=27)]) + return super(INVERT_COLORS, cls).__new__(*args, **kwargs) + + @property + def raw(self): + """ + Removes the color inversion and returns the original text provided. + """ + text = self.__str__() + return utils.remove_ansi(text, SGR.format(n=7), SGR.format(n=27)) + + +class Color(str): + """ + Color base class + + This class is a wrapper for the `str` class that adds a couple of + class methods. It makes it easier to add and remove an ansi color escape + sequence from a string of text. + + There are 141 HTML colors that have already been provided however you can + make a custom color if you would like. + + To make a custom color simply subclass this class and override the `_rgb` + class attribute supplying your own RGB value as a tuple (R, G, B) + """ + _rgb = (0, 0, 0) + + @classmethod + def fg(cls, text): + """ + Adds the ansi escape codes to set the foreground color to this color. + """ + return cls(''.join([ + CSI, + '38;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=39) + ])) + + @classmethod + def bg(cls, text): + """ + Adds the ansi escape codes to set the background color to this color. + """ + return cls(''.join([ + CSI, + '48;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=49) + ])) + + @classmethod + def ul(cls, text): + """ + Adds the ansi escape codes to set the underline color to this color. + """ + return cls(''.join([ + CSI, + '58;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=59) + ])) + + @property + def raw(self): + """ + Removes this color from the text provided + """ + text = self.__str__() + + if text.startswith(CSI + '48;2'): + text = utils.remove_ansi( + text, + CSI + '48;2;{0};{1};{2}m'.format(*self._rgb), + SGR.format(n=49) + ) + elif text.startswith(CSI + '38;2'): + text = utils.remove_ansi( + text, + CSI + '38;2;{0};{1};{2}m'.format(*self._rgb), + SGR.format(n=39) + ) + + else: + text = utils.remove_ansi( + text, + CSI + '58;2;{0};{1};{2}m'.format(*self._rgb), + SGR.format(n=59) + ) + + return text + + +class INDIAN_RED(Color): + _rgb = (205, 92, 92) + + +class LIGHT_CORAL(Color): + _rgb = (240, 128, 128) + + +class SALMON(Color): + _rgb = (250, 128, 114) + + +class DARK_SALMON(Color): + _rgb = (233, 150, 122) + + +class LIGHT_SALMON(Color): + _rgb = (255, 160, 122) + + +class CRIMSON(Color): + _rgb = (220, 20, 60) + + +class RED(Color): + _rgb = (255, 0, 0) + + +class FIRE_BRICK(Color): + _rgb = (178, 34, 34) + + +class DARK_RED(Color): + _rgb = (139, 0, 0) + + +class PINK(Color): + _rgb = (255, 192, 203) + + +class LIGHT_PINK(Color): + _rgb = (255, 182, 193) + + +class HOT_PINK(Color): + _rgb = (255, 105, 180) + + +class DEEP_PINK(Color): + _rgb = (255, 20, 147) + + +class MEDIUM_VIOLET_RED(Color): + _rgb = (199, 21, 133) + + +class PALE_VIOLET_RED(Color): + _rgb = (219, 112, 147) + + +class CORAL(Color): + _rgb = (255, 127, 80) + + +class TOMATO(Color): + _rgb = (255, 99, 71) + + +class ORANGE_RED(Color): + _rgb = (255, 69, 0) + + +class DARK_ORANGE(Color): + _rgb = (255, 140, 0) + + +class ORANGE(Color): + _rgb = (255, 165, 0) + + +class GOLD(Color): + _rgb = (255, 215, 0) + + +class YELLOW(Color): + _rgb = (255, 255, 0) + + +class LIGHT_YELLOW(Color): + _rgb = (255, 255, 224) + + +class LEMON_CHIFFON(Color): + _rgb = (255, 250, 205) + + +class LIGHT_GOLDENROD_YELLOW(Color): + _rgb = (250, 250, 210) + + +class PAPAYA_WHIP(Color): + _rgb = (255, 239, 213) + + +class MOCCASIN(Color): + _rgb = (255, 228, 181) + + +class PEACH_PUFF(Color): + _rgb = (255, 218, 185) + + +class PALE_GOLDENROD(Color): + _rgb = (238, 232, 170) + + +class KHAKI(Color): + _rgb = (240, 230, 140) + + +class DARK_KHAKI(Color): + _rgb = (189, 183, 107) + + +class LAVENDER(Color): + _rgb = (230, 230, 250) + + +class THISTLE(Color): + _rgb = (216, 191, 216) + + +class PLUM(Color): + _rgb = (221, 160, 221) + + +class VIOLET(Color): + _rgb = (238, 130, 238) + + +class ORCHID(Color): + _rgb = (218, 112, 214) + + +class FUCHSIA(Color): + _rgb = (255, 0, 255) + + +class MAGENTA(Color): + _rgb = (255, 0, 255) + + +class MEDIUM_ORCHID(Color): + _rgb = (186, 85, 211) + + +class MEDIUM_PURPLE(Color): + _rgb = (147, 112, 219) + + +class REBECCA_PURPLE(Color): + _rgb = (102, 51, 153) + + +class BLUE_VIOLET(Color): + _rgb = (138, 43, 226) + + +class DARK_VIOLET(Color): + _rgb = (148, 0, 211) + + +class DARK_ORCHID(Color): + _rgb = (153, 50, 204) + + +class DARK_MAGENTA(Color): + _rgb = (139, 0, 139) + + +class PURPLE(Color): + _rgb = (128, 0, 128) + + +class INDIGO(Color): + _rgb = (75, 0, 130) + + +class SLATE_BLUE(Color): + _rgb = (106, 90, 205) + + +class DARK_SLATE_BLUE(Color): + _rgb = (72, 61, 139) + + +class MEDIUM_SLATE_BLUE(Color): + _rgb = (123, 104, 238) + + +class GREEN_YELLOW(Color): + _rgb = (173, 255, 47) + + +class CHARTREUSE(Color): + _rgb = (127, 255, 0) + + +class LAWN_GREEN(Color): + _rgb = (124, 252, 0) + + +class LIME(Color): + _rgb = (0, 255, 0) + + +class LIME_GREEN(Color): + _rgb = (50, 205, 50) + + +class PALE_GREEN(Color): + _rgb = (152, 251, 152) + + +class LIGHT_GREEN(Color): + _rgb = (144, 238, 144) + + +class MEDIUM_SPRING_GREEN(Color): + _rgb = (0, 250, 154) + + +class SPRING_GREEN(Color): + _rgb = (0, 255, 127) + + +class MEDIUM_SEA_GREEN(Color): + _rgb = (60, 179, 113) + + +class SEA_GREEN(Color): + _rgb = (46, 139, 87) + + +class FOREST_GREEN(Color): + _rgb = (34, 139, 34) + + +class GREEN(Color): + _rgb = (0, 128, 0) + + +class DARK_GREEN(Color): + _rgb = (0, 100, 0) + + +class YELLOW_GREEN(Color): + _rgb = (154, 205, 50) + + +class OLIVE_DRAB(Color): + _rgb = (107, 142, 35) + + +class OLIVE(Color): + _rgb = (128, 128, 0) + + +class DARK_OLIVE_GREEN(Color): + _rgb = (85, 107, 47) + + +class MEDIUM_AQUAMARINE(Color): + _rgb = (102, 205, 170) + + +class DARK_SEA_GREEN(Color): + _rgb = (143, 188, 139) + + +class LIGHT_SEA_GREEN(Color): + _rgb = (32, 178, 170) + + +class DARK_CYAN(Color): + _rgb = (0, 139, 139) + + +class TEAL(Color): + _rgb = (0, 128, 128) + + +class AQUA(Color): + _rgb = (0, 255, 255) + + +class CYAN(Color): + _rgb = (0, 255, 255) + + +class LIGHT_CYAN(Color): + _rgb = (224, 255, 255) + + +class PALE_TURQUOISE(Color): + _rgb = (175, 238, 238) + + +class AQUAMARINE(Color): + _rgb = (127, 255, 212) + + +class TURQUOISE(Color): + _rgb = (64, 224, 208) + + +class MEDIUM_TURQUOISE(Color): + _rgb = (72, 209, 204) + + +class DARK_TURQUOISE(Color): + _rgb = (0, 206, 209) + + +class CADET_BLUE(Color): + _rgb = (95, 158, 160) + + +class STEEL_BLUE(Color): + _rgb = (70, 130, 180) + + +class LIGHT_STEEL_BLUE(Color): + _rgb = (176, 196, 222) + + +class POWDER_BLUE(Color): + _rgb = (176, 224, 230) + + +class LIGHT_BLUE(Color): + _rgb = (173, 216, 230) + + +class SKY_BLUE(Color): + _rgb = (135, 206, 235) + + +class LIGHT_SKY_BLUE(Color): + _rgb = (135, 206, 250) + + +class DEEP_SKY_BLUE(Color): + _rgb = (0, 191, 255) + + +class DODGER_BLUE(Color): + _rgb = (30, 144, 255) + + +class CORNFLOWER_BLUE(Color): + _rgb = (100, 149, 237) + + +class ROYAL_BLUE(Color): + _rgb = (65, 105, 225) + + +class BLUE(Color): + _rgb = (0, 0, 255) + + +class MEDIUM_BLUE(Color): + _rgb = (0, 0, 205) + + +class DARK_BLUE(Color): + _rgb = (0, 0, 139) + + +class NAVY(Color): + _rgb = (0, 0, 128) + + +class MIDNIGHT_BLUE(Color): + _rgb = (25, 25, 112) + + +class CORNSILK(Color): + _rgb = (255, 248, 220) + + +class BLANCHED_ALMOND(Color): + _rgb = (255, 235, 205) + + +class BISQUE(Color): + _rgb = (255, 228, 196) + + +class NAVAJO_WHITE(Color): + _rgb = (255, 222, 173) + + +class WHEAT(Color): + _rgb = (245, 222, 179) + + +class BURLY_WOOD(Color): + _rgb = (222, 184, 135) + + +class TAN(Color): + _rgb = (210, 180, 140) + + +class ROSY_BROWN(Color): + _rgb = (188, 143, 143) + + +class SANDY_BROWN(Color): + _rgb = (244, 164, 96) + + +class GOLDENROD(Color): + _rgb = (218, 165, 32) + + +class DARK_GOLDENROD(Color): + _rgb = (184, 134, 11) + + +class PERU(Color): + _rgb = (205, 133, 63) + + +class CHOCOLATE(Color): + _rgb = (210, 105, 30) + + +class SADDLE_BROWN(Color): + _rgb = (139, 69, 19) + + +class SIENNA(Color): + _rgb = (160, 82, 45) + + +class BROWN(Color): + _rgb = (165, 42, 42) + + +class MAROON(Color): + _rgb = (128, 0, 0) + + +class WHITE(Color): + _rgb = (255, 255, 255) + + +class SNOW(Color): + _rgb = (255, 250, 250) + + +class HONEY_DEW(Color): + _rgb = (240, 255, 240) + + +class MINT_CREAM(Color): + _rgb = (245, 255, 250) + + +class AZURE(Color): + _rgb = (240, 255, 255) + + +class ALICE_BLUE(Color): + _rgb = (240, 248, 255) + + +class GHOST_WHITE(Color): + _rgb = (248, 248, 255) + + +class WHITE_SMOKE(Color): + _rgb = (245, 245, 245) + + +class SEA_SHELL(Color): + _rgb = (255, 245, 238) + + +class BEIGE(Color): + _rgb = (245, 245, 220) + + +class OLD_LACE(Color): + _rgb = (253, 245, 230) + + +class FLORAL_WHITE(Color): + _rgb = (255, 250, 240) + + +class IVORY(Color): + _rgb = (255, 255, 240) + + +class ANTIQUE_WHITE(Color): + _rgb = (250, 235, 215) + + +class LINEN(Color): + _rgb = (250, 240, 230) + + +class LAVENDER_BLUSH(Color): + _rgb = (255, 240, 245) + + +class MISTY_ROSE(Color): + _rgb = (255, 228, 225) + + +class GAINSBORO(Color): + _rgb = (220, 220, 220) + + +class LIGHT_GRAY(Color): + _rgb = (211, 211, 211) + + +class SILVER(Color): + _rgb = (192, 192, 192) + + +class DARK_GRAY(Color): + _rgb = (169, 169, 169) + + +class GRAY(Color): + _rgb = (128, 128, 128) + + +class DIM_GRAY(Color): + _rgb = (105, 105, 105) + + +class LIGHT_SLATE_GRAY(Color): + _rgb = (119, 136, 153) + + +class SLATE_GRAY(Color): + _rgb = (112, 128, 144) + + +class DARK_SLATE_GRAY(Color): + _rgb = (47, 79, 79) + + +class BLACK(Color): + _rgb = (0, 0, 0) From 99ac5bc9cae8c7db16ed644bec711c2bed3fe7e2 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 13 Nov 2022 02:24:34 +0100 Subject: [PATCH 06/24] added linux code from @kdschlosser --- progressbar/os_functions/__init__.py | 24 ++++++++++++++++++++++++ progressbar/os_functions/nix.py | 15 +++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 progressbar/os_functions/__init__.py create mode 100644 progressbar/os_functions/nix.py diff --git a/progressbar/os_functions/__init__.py b/progressbar/os_functions/__init__.py new file mode 100644 index 00000000..8b442283 --- /dev/null +++ b/progressbar/os_functions/__init__.py @@ -0,0 +1,24 @@ +import sys + +if sys.platform.startswith('win'): + from .windows import ( + getch as _getch, + set_console_mode as _set_console_mode, + reset_console_mode as _reset_console_mode + ) + +else: + from .nix import getch as _getch + + def _reset_console_mode(): + pass + + + def _set_console_mode(): + pass + + +getch = _getch +reset_console_mode = _reset_console_mode +set_console_mode = _set_console_mode + diff --git a/progressbar/os_functions/nix.py b/progressbar/os_functions/nix.py new file mode 100644 index 00000000..e46fbdf0 --- /dev/null +++ b/progressbar/os_functions/nix.py @@ -0,0 +1,15 @@ +import sys +import tty +import termios + + +def getch(): + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + return ch From 2fc0dea6a448079522658114d629b9c0bff6459e Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 20 Nov 2022 04:01:43 +0100 Subject: [PATCH 07/24] added windows code from @kdschlosser --- progressbar/os_functions/windows.py | 144 ++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 progressbar/os_functions/windows.py diff --git a/progressbar/os_functions/windows.py b/progressbar/os_functions/windows.py new file mode 100644 index 00000000..edac0696 --- /dev/null +++ b/progressbar/os_functions/windows.py @@ -0,0 +1,144 @@ +import ctypes +from ctypes.wintypes import ( + DWORD as _DWORD, + HANDLE as _HANDLE, + BOOL as _BOOL, + WORD as _WORD, + UINT as _UINT, + WCHAR as _WCHAR, + CHAR as _CHAR, + SHORT as _SHORT +) + +_kernel32 = ctypes.windll.Kernel32 + +_ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 +_ENABLE_PROCESSED_OUTPUT = 0x0001 +_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + +_STD_INPUT_HANDLE = _DWORD(-10) +_STD_OUTPUT_HANDLE = _DWORD(-11) + + +_GetConsoleMode = _kernel32.GetConsoleMode +_GetConsoleMode.restype = _BOOL + +_SetConsoleMode = _kernel32.SetConsoleMode +_SetConsoleMode.restype = _BOOL + +_GetStdHandle = _kernel32.GetStdHandle +_GetStdHandle.restype = _HANDLE + +_ReadConsoleInput = _kernel32.ReadConsoleInputA +_ReadConsoleInput.restype = _BOOL + + +_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) +_input_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) + +_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) +_output_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) + + +class _COORD(ctypes.Structure): + _fields_ = [ + ('X', _SHORT), + ('Y', _SHORT) + ] + + +class _FOCUS_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('bSetFocus', _BOOL) + ] + + +class _KEY_EVENT_RECORD(ctypes.Structure): + class _uchar(ctypes.Union): + _fields_ = [ + ('UnicodeChar', _WCHAR), + ('AsciiChar', _CHAR) + ] + + _fields_ = [ + ('bKeyDown', _BOOL), + ('wRepeatCount', _WORD), + ('wVirtualKeyCode', _WORD), + ('wVirtualScanCode', _WORD), + ('uChar', _uchar), + ('dwControlKeyState', _DWORD) + ] + + +class _MENU_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwCommandId', _UINT) + ] + + +class _MOUSE_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwMousePosition', _COORD), + ('dwButtonState', _DWORD), + ('dwControlKeyState', _DWORD), + ('dwEventFlags', _DWORD) + ] + + +class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): + _fields_ = [ + ('dwSize', _COORD) + ] + + +class _INPUT_RECORD(ctypes.Structure): + class _Event(ctypes.Union): + _fields_ = [ + ('KeyEvent', _KEY_EVENT_RECORD), + ('MouseEvent', _MOUSE_EVENT_RECORD), + ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), + ('MenuEvent', _MENU_EVENT_RECORD), + ('FocusEvent', _FOCUS_EVENT_RECORD) + ] + + _fields_ = [ + ('EventType', _WORD), + ('Event', _Event) + ] + + +def reset_console_mode(): + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) + + +def set_console_mode(): + mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) + + mode = ( + _output_mode.value | + _ENABLE_PROCESSED_OUTPUT | + _ENABLE_VIRTUAL_TERMINAL_PROCESSING + ) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) + + +def getch(): + lpBuffer = (_INPUT_RECORD * 2)() + nLength = _DWORD(2) + lpNumberOfEventsRead = _DWORD() + + _ReadConsoleInput( + _HANDLE(_hConsoleInput), + lpBuffer, nLength, + ctypes.byref(lpNumberOfEventsRead) + ) + + char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') + if char == '\x00': + return None + + return char From c3688ac3ddbbcab8b491608e2552ce78b7824a65 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Nov 2022 17:37:55 +0100 Subject: [PATCH 08/24] consistent code styling and converted color to namedtuple --- progressbar/ansi.py | 466 +++++++++++++++++++++++--------------------- 1 file changed, 240 insertions(+), 226 deletions(-) diff --git a/progressbar/ansi.py b/progressbar/ansi.py index 9b9f2dbc..2ace68d6 100644 --- a/progressbar/ansi.py +++ b/progressbar/ansi.py @@ -1,3 +1,4 @@ +import collections import threading from . import utils @@ -6,7 +7,6 @@ ESC = '\x1B' CSI = ESC + '[' - CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) @@ -56,7 +56,6 @@ def column(self, stream): DECSET = CSI + '?{n}h' # DEC Private Mode Set DECTCEM = DECSET.format(n=25) # Show Cursor - # possible values: # 0 = Normal (default) # 1 = Bold @@ -137,9 +136,9 @@ def column(self, stream): class ENCIRCLED(str): - """ + ''' Your guess is as good as mine. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -149,17 +148,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes encircled? - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=52), SGR.format(n=54)) class FRAMED(str): - """ + ''' Your guess is as good as mine. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -169,17 +168,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes Frame? - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=51), SGR.format(n=54)) class GOTHIC(str): - """ + ''' Changes text font to Gothic - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -189,17 +188,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Makes text font normal - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=20), SGR.format(n=10)) class ITALIC(str): - """ + ''' Makes the text italic - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -209,17 +208,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the italic. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=3), SGR.format(n=23)) class STRIKE_THROUGH(str): - """ + ''' Strikes through the text. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -229,17 +228,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the strike through - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=9), SGR.format(n=29)) class FAST_BLINK(str): - """ + ''' Makes the text blink fast - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -249,17 +248,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Makes the text steady - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=6), SGR.format(n=25)) class SLOW_BLINK(str): - """ + ''' Makes the text blonk slowely. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -269,17 +268,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Makes the text steady - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=5), SGR.format(n=25)) class OVERLINE(str): - """ + ''' Overlines the text provided. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -289,17 +288,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the overline from the text - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=53), SGR.format(n=55)) class UNDERLINE(str): - """ + ''' Underlines the text provided. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -309,17 +308,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the underline from the text - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=4), SGR.format(n=24)) class DOUBLE_UNDERLINE(str): - """ + ''' Double underlines the text provided. - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -329,17 +328,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the double underline from the text - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=21), SGR.format(n=24)) - + class BOLD(str): - """ + ''' Makes the supplied text BOLD - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -349,17 +348,17 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the BOLD from the text. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=1), SGR.format(n=22)) class FAINT(str): - """ + ''' Makes the supplied text FAINT - """ + ''' @classmethod def __new__(cls, *args, **kwargs): @@ -369,18 +368,18 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the FAINT from the text. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=2), SGR.format(n=22)) class INVERT_COLORS(str): - """ + ''' Switches the background and forground colors. - """ - + ''' + @classmethod def __new__(cls, *args, **kwargs): args = list(args) @@ -389,15 +388,18 @@ def __new__(cls, *args, **kwargs): @property def raw(self): - """ + ''' Removes the color inversion and returns the original text provided. - """ + ''' text = self.__str__() return utils.remove_ansi(text, SGR.format(n=7), SGR.format(n=27)) +RGB = collections.namedtuple('RGB', ['r', 'g', 'b']) + + class Color(str): - """ + ''' Color base class This class is a wrapper for the `str` class that adds a couple of @@ -409,50 +411,62 @@ class methods. It makes it easier to add and remove an ansi color escape To make a custom color simply subclass this class and override the `_rgb` class attribute supplying your own RGB value as a tuple (R, G, B) - """ - _rgb = (0, 0, 0) + ''' + _rgb: RGB = RGB(0, 0, 0) @classmethod def fg(cls, text): - """ + ''' Adds the ansi escape codes to set the foreground color to this color. - """ - return cls(''.join([ - CSI, - '38;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=39) - ])) + ''' + return cls( + ''.join( + [ + CSI, + '38;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=39) + ] + ) + ) @classmethod def bg(cls, text): - """ + ''' Adds the ansi escape codes to set the background color to this color. - """ - return cls(''.join([ - CSI, - '48;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=49) - ])) + ''' + return cls( + ''.join( + [ + CSI, + '48;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=49) + ] + ) + ) @classmethod def ul(cls, text): - """ + ''' Adds the ansi escape codes to set the underline color to this color. - """ - return cls(''.join([ - CSI, - '58;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=59) - ])) + ''' + return cls( + ''.join( + [ + CSI, + '58;2;{0};{1};{2}m'.format(*cls._rgb), + text, + SGR.format(n=59) + ] + ) + ) @property def raw(self): - """ + ''' Removes this color from the text provided - """ + ''' text = self.__str__() if text.startswith(CSI + '48;2'): @@ -479,564 +493,564 @@ def raw(self): class INDIAN_RED(Color): - _rgb = (205, 92, 92) + _rgb = RGB(205, 92, 92) class LIGHT_CORAL(Color): - _rgb = (240, 128, 128) + _rgb = RGB(240, 128, 128) class SALMON(Color): - _rgb = (250, 128, 114) + _rgb = RGB(250, 128, 114) class DARK_SALMON(Color): - _rgb = (233, 150, 122) + _rgb = RGB(233, 150, 122) class LIGHT_SALMON(Color): - _rgb = (255, 160, 122) + _rgb = RGB(255, 160, 122) class CRIMSON(Color): - _rgb = (220, 20, 60) + _rgb = RGB(220, 20, 60) class RED(Color): - _rgb = (255, 0, 0) + _rgb = RGB(255, 0, 0) class FIRE_BRICK(Color): - _rgb = (178, 34, 34) + _rgb = RGB(178, 34, 34) class DARK_RED(Color): - _rgb = (139, 0, 0) + _rgb = RGB(139, 0, 0) class PINK(Color): - _rgb = (255, 192, 203) + _rgb = RGB(255, 192, 203) class LIGHT_PINK(Color): - _rgb = (255, 182, 193) + _rgb = RGB(255, 182, 193) class HOT_PINK(Color): - _rgb = (255, 105, 180) + _rgb = RGB(255, 105, 180) class DEEP_PINK(Color): - _rgb = (255, 20, 147) + _rgb = RGB(255, 20, 147) class MEDIUM_VIOLET_RED(Color): - _rgb = (199, 21, 133) + _rgb = RGB(199, 21, 133) class PALE_VIOLET_RED(Color): - _rgb = (219, 112, 147) + _rgb = RGB(219, 112, 147) class CORAL(Color): - _rgb = (255, 127, 80) + _rgb = RGB(255, 127, 80) class TOMATO(Color): - _rgb = (255, 99, 71) + _rgb = RGB(255, 99, 71) class ORANGE_RED(Color): - _rgb = (255, 69, 0) + _rgb = RGB(255, 69, 0) class DARK_ORANGE(Color): - _rgb = (255, 140, 0) + _rgb = RGB(255, 140, 0) class ORANGE(Color): - _rgb = (255, 165, 0) + _rgb = RGB(255, 165, 0) class GOLD(Color): - _rgb = (255, 215, 0) + _rgb = RGB(255, 215, 0) class YELLOW(Color): - _rgb = (255, 255, 0) + _rgb = RGB(255, 255, 0) class LIGHT_YELLOW(Color): - _rgb = (255, 255, 224) + _rgb = RGB(255, 255, 224) class LEMON_CHIFFON(Color): - _rgb = (255, 250, 205) + _rgb = RGB(255, 250, 205) class LIGHT_GOLDENROD_YELLOW(Color): - _rgb = (250, 250, 210) + _rgb = RGB(250, 250, 210) class PAPAYA_WHIP(Color): - _rgb = (255, 239, 213) + _rgb = RGB(255, 239, 213) class MOCCASIN(Color): - _rgb = (255, 228, 181) + _rgb = RGB(255, 228, 181) class PEACH_PUFF(Color): - _rgb = (255, 218, 185) + _rgb = RGB(255, 218, 185) class PALE_GOLDENROD(Color): - _rgb = (238, 232, 170) + _rgb = RGB(238, 232, 170) class KHAKI(Color): - _rgb = (240, 230, 140) + _rgb = RGB(240, 230, 140) class DARK_KHAKI(Color): - _rgb = (189, 183, 107) + _rgb = RGB(189, 183, 107) class LAVENDER(Color): - _rgb = (230, 230, 250) + _rgb = RGB(230, 230, 250) class THISTLE(Color): - _rgb = (216, 191, 216) + _rgb = RGB(216, 191, 216) class PLUM(Color): - _rgb = (221, 160, 221) + _rgb = RGB(221, 160, 221) class VIOLET(Color): - _rgb = (238, 130, 238) + _rgb = RGB(238, 130, 238) class ORCHID(Color): - _rgb = (218, 112, 214) + _rgb = RGB(218, 112, 214) class FUCHSIA(Color): - _rgb = (255, 0, 255) + _rgb = RGB(255, 0, 255) class MAGENTA(Color): - _rgb = (255, 0, 255) + _rgb = RGB(255, 0, 255) class MEDIUM_ORCHID(Color): - _rgb = (186, 85, 211) + _rgb = RGB(186, 85, 211) class MEDIUM_PURPLE(Color): - _rgb = (147, 112, 219) + _rgb = RGB(147, 112, 219) class REBECCA_PURPLE(Color): - _rgb = (102, 51, 153) + _rgb = RGB(102, 51, 153) class BLUE_VIOLET(Color): - _rgb = (138, 43, 226) + _rgb = RGB(138, 43, 226) class DARK_VIOLET(Color): - _rgb = (148, 0, 211) + _rgb = RGB(148, 0, 211) class DARK_ORCHID(Color): - _rgb = (153, 50, 204) + _rgb = RGB(153, 50, 204) class DARK_MAGENTA(Color): - _rgb = (139, 0, 139) + _rgb = RGB(139, 0, 139) class PURPLE(Color): - _rgb = (128, 0, 128) + _rgb = RGB(128, 0, 128) class INDIGO(Color): - _rgb = (75, 0, 130) + _rgb = RGB(75, 0, 130) class SLATE_BLUE(Color): - _rgb = (106, 90, 205) + _rgb = RGB(106, 90, 205) class DARK_SLATE_BLUE(Color): - _rgb = (72, 61, 139) + _rgb = RGB(72, 61, 139) class MEDIUM_SLATE_BLUE(Color): - _rgb = (123, 104, 238) + _rgb = RGB(123, 104, 238) class GREEN_YELLOW(Color): - _rgb = (173, 255, 47) + _rgb = RGB(173, 255, 47) class CHARTREUSE(Color): - _rgb = (127, 255, 0) + _rgb = RGB(127, 255, 0) class LAWN_GREEN(Color): - _rgb = (124, 252, 0) + _rgb = RGB(124, 252, 0) class LIME(Color): - _rgb = (0, 255, 0) + _rgb = RGB(0, 255, 0) class LIME_GREEN(Color): - _rgb = (50, 205, 50) + _rgb = RGB(50, 205, 50) class PALE_GREEN(Color): - _rgb = (152, 251, 152) + _rgb = RGB(152, 251, 152) class LIGHT_GREEN(Color): - _rgb = (144, 238, 144) + _rgb = RGB(144, 238, 144) class MEDIUM_SPRING_GREEN(Color): - _rgb = (0, 250, 154) + _rgb = RGB(0, 250, 154) class SPRING_GREEN(Color): - _rgb = (0, 255, 127) + _rgb = RGB(0, 255, 127) class MEDIUM_SEA_GREEN(Color): - _rgb = (60, 179, 113) + _rgb = RGB(60, 179, 113) class SEA_GREEN(Color): - _rgb = (46, 139, 87) + _rgb = RGB(46, 139, 87) class FOREST_GREEN(Color): - _rgb = (34, 139, 34) + _rgb = RGB(34, 139, 34) class GREEN(Color): - _rgb = (0, 128, 0) + _rgb = RGB(0, 128, 0) class DARK_GREEN(Color): - _rgb = (0, 100, 0) + _rgb = RGB(0, 100, 0) class YELLOW_GREEN(Color): - _rgb = (154, 205, 50) + _rgb = RGB(154, 205, 50) class OLIVE_DRAB(Color): - _rgb = (107, 142, 35) + _rgb = RGB(107, 142, 35) class OLIVE(Color): - _rgb = (128, 128, 0) + _rgb = RGB(128, 128, 0) class DARK_OLIVE_GREEN(Color): - _rgb = (85, 107, 47) + _rgb = RGB(85, 107, 47) class MEDIUM_AQUAMARINE(Color): - _rgb = (102, 205, 170) + _rgb = RGB(102, 205, 170) class DARK_SEA_GREEN(Color): - _rgb = (143, 188, 139) + _rgb = RGB(143, 188, 139) class LIGHT_SEA_GREEN(Color): - _rgb = (32, 178, 170) + _rgb = RGB(32, 178, 170) class DARK_CYAN(Color): - _rgb = (0, 139, 139) + _rgb = RGB(0, 139, 139) class TEAL(Color): - _rgb = (0, 128, 128) + _rgb = RGB(0, 128, 128) class AQUA(Color): - _rgb = (0, 255, 255) + _rgb = RGB(0, 255, 255) class CYAN(Color): - _rgb = (0, 255, 255) + _rgb = RGB(0, 255, 255) class LIGHT_CYAN(Color): - _rgb = (224, 255, 255) + _rgb = RGB(224, 255, 255) class PALE_TURQUOISE(Color): - _rgb = (175, 238, 238) + _rgb = RGB(175, 238, 238) class AQUAMARINE(Color): - _rgb = (127, 255, 212) + _rgb = RGB(127, 255, 212) class TURQUOISE(Color): - _rgb = (64, 224, 208) + _rgb = RGB(64, 224, 208) class MEDIUM_TURQUOISE(Color): - _rgb = (72, 209, 204) + _rgb = RGB(72, 209, 204) class DARK_TURQUOISE(Color): - _rgb = (0, 206, 209) + _rgb = RGB(0, 206, 209) class CADET_BLUE(Color): - _rgb = (95, 158, 160) + _rgb = RGB(95, 158, 160) class STEEL_BLUE(Color): - _rgb = (70, 130, 180) + _rgb = RGB(70, 130, 180) class LIGHT_STEEL_BLUE(Color): - _rgb = (176, 196, 222) + _rgb = RGB(176, 196, 222) class POWDER_BLUE(Color): - _rgb = (176, 224, 230) + _rgb = RGB(176, 224, 230) class LIGHT_BLUE(Color): - _rgb = (173, 216, 230) + _rgb = RGB(173, 216, 230) class SKY_BLUE(Color): - _rgb = (135, 206, 235) + _rgb = RGB(135, 206, 235) class LIGHT_SKY_BLUE(Color): - _rgb = (135, 206, 250) + _rgb = RGB(135, 206, 250) class DEEP_SKY_BLUE(Color): - _rgb = (0, 191, 255) + _rgb = RGB(0, 191, 255) class DODGER_BLUE(Color): - _rgb = (30, 144, 255) + _rgb = RGB(30, 144, 255) class CORNFLOWER_BLUE(Color): - _rgb = (100, 149, 237) + _rgb = RGB(100, 149, 237) class ROYAL_BLUE(Color): - _rgb = (65, 105, 225) + _rgb = RGB(65, 105, 225) class BLUE(Color): - _rgb = (0, 0, 255) + _rgb = RGB(0, 0, 255) class MEDIUM_BLUE(Color): - _rgb = (0, 0, 205) + _rgb = RGB(0, 0, 205) class DARK_BLUE(Color): - _rgb = (0, 0, 139) + _rgb = RGB(0, 0, 139) class NAVY(Color): - _rgb = (0, 0, 128) + _rgb = RGB(0, 0, 128) class MIDNIGHT_BLUE(Color): - _rgb = (25, 25, 112) + _rgb = RGB(25, 25, 112) class CORNSILK(Color): - _rgb = (255, 248, 220) + _rgb = RGB(255, 248, 220) class BLANCHED_ALMOND(Color): - _rgb = (255, 235, 205) + _rgb = RGB(255, 235, 205) class BISQUE(Color): - _rgb = (255, 228, 196) + _rgb = RGB(255, 228, 196) class NAVAJO_WHITE(Color): - _rgb = (255, 222, 173) + _rgb = RGB(255, 222, 173) class WHEAT(Color): - _rgb = (245, 222, 179) + _rgb = RGB(245, 222, 179) class BURLY_WOOD(Color): - _rgb = (222, 184, 135) + _rgb = RGB(222, 184, 135) class TAN(Color): - _rgb = (210, 180, 140) + _rgb = RGB(210, 180, 140) class ROSY_BROWN(Color): - _rgb = (188, 143, 143) + _rgb = RGB(188, 143, 143) class SANDY_BROWN(Color): - _rgb = (244, 164, 96) + _rgb = RGB(244, 164, 96) class GOLDENROD(Color): - _rgb = (218, 165, 32) + _rgb = RGB(218, 165, 32) class DARK_GOLDENROD(Color): - _rgb = (184, 134, 11) + _rgb = RGB(184, 134, 11) class PERU(Color): - _rgb = (205, 133, 63) + _rgb = RGB(205, 133, 63) class CHOCOLATE(Color): - _rgb = (210, 105, 30) + _rgb = RGB(210, 105, 30) class SADDLE_BROWN(Color): - _rgb = (139, 69, 19) + _rgb = RGB(139, 69, 19) class SIENNA(Color): - _rgb = (160, 82, 45) + _rgb = RGB(160, 82, 45) class BROWN(Color): - _rgb = (165, 42, 42) + _rgb = RGB(165, 42, 42) class MAROON(Color): - _rgb = (128, 0, 0) + _rgb = RGB(128, 0, 0) class WHITE(Color): - _rgb = (255, 255, 255) + _rgb = RGB(255, 255, 255) class SNOW(Color): - _rgb = (255, 250, 250) + _rgb = RGB(255, 250, 250) class HONEY_DEW(Color): - _rgb = (240, 255, 240) + _rgb = RGB(240, 255, 240) class MINT_CREAM(Color): - _rgb = (245, 255, 250) + _rgb = RGB(245, 255, 250) class AZURE(Color): - _rgb = (240, 255, 255) + _rgb = RGB(240, 255, 255) class ALICE_BLUE(Color): - _rgb = (240, 248, 255) + _rgb = RGB(240, 248, 255) class GHOST_WHITE(Color): - _rgb = (248, 248, 255) + _rgb = RGB(248, 248, 255) class WHITE_SMOKE(Color): - _rgb = (245, 245, 245) + _rgb = RGB(245, 245, 245) class SEA_SHELL(Color): - _rgb = (255, 245, 238) + _rgb = RGB(255, 245, 238) class BEIGE(Color): - _rgb = (245, 245, 220) + _rgb = RGB(245, 245, 220) class OLD_LACE(Color): - _rgb = (253, 245, 230) + _rgb = RGB(253, 245, 230) class FLORAL_WHITE(Color): - _rgb = (255, 250, 240) + _rgb = RGB(255, 250, 240) class IVORY(Color): - _rgb = (255, 255, 240) + _rgb = RGB(255, 255, 240) class ANTIQUE_WHITE(Color): - _rgb = (250, 235, 215) + _rgb = RGB(250, 235, 215) class LINEN(Color): - _rgb = (250, 240, 230) + _rgb = RGB(250, 240, 230) class LAVENDER_BLUSH(Color): - _rgb = (255, 240, 245) + _rgb = RGB(255, 240, 245) class MISTY_ROSE(Color): - _rgb = (255, 228, 225) + _rgb = RGB(255, 228, 225) class GAINSBORO(Color): - _rgb = (220, 220, 220) + _rgb = RGB(220, 220, 220) class LIGHT_GRAY(Color): - _rgb = (211, 211, 211) + _rgb = RGB(211, 211, 211) class SILVER(Color): - _rgb = (192, 192, 192) + _rgb = RGB(192, 192, 192) class DARK_GRAY(Color): - _rgb = (169, 169, 169) + _rgb = RGB(169, 169, 169) class GRAY(Color): - _rgb = (128, 128, 128) + _rgb = RGB(128, 128, 128) class DIM_GRAY(Color): - _rgb = (105, 105, 105) + _rgb = RGB(105, 105, 105) class LIGHT_SLATE_GRAY(Color): - _rgb = (119, 136, 153) + _rgb = RGB(119, 136, 153) class SLATE_GRAY(Color): - _rgb = (112, 128, 144) + _rgb = RGB(112, 128, 144) class DARK_SLATE_GRAY(Color): - _rgb = (47, 79, 79) + _rgb = RGB(47, 79, 79) class BLACK(Color): - _rgb = (0, 0, 0) + _rgb = RGB(0, 0, 0) From e2996be8590ebe1a4fe191bbb15041fdb9fa834d Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 29 Nov 2022 17:38:20 +0100 Subject: [PATCH 09/24] removed unneeded os functions for now --- progressbar/os_functions/__init__.py | 24 ----- progressbar/os_functions/nix.py | 15 --- progressbar/os_functions/windows.py | 144 --------------------------- 3 files changed, 183 deletions(-) delete mode 100644 progressbar/os_functions/__init__.py delete mode 100644 progressbar/os_functions/nix.py delete mode 100644 progressbar/os_functions/windows.py diff --git a/progressbar/os_functions/__init__.py b/progressbar/os_functions/__init__.py deleted file mode 100644 index 8b442283..00000000 --- a/progressbar/os_functions/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -import sys - -if sys.platform.startswith('win'): - from .windows import ( - getch as _getch, - set_console_mode as _set_console_mode, - reset_console_mode as _reset_console_mode - ) - -else: - from .nix import getch as _getch - - def _reset_console_mode(): - pass - - - def _set_console_mode(): - pass - - -getch = _getch -reset_console_mode = _reset_console_mode -set_console_mode = _set_console_mode - diff --git a/progressbar/os_functions/nix.py b/progressbar/os_functions/nix.py deleted file mode 100644 index e46fbdf0..00000000 --- a/progressbar/os_functions/nix.py +++ /dev/null @@ -1,15 +0,0 @@ -import sys -import tty -import termios - - -def getch(): - fd = sys.stdin.fileno() - old_settings = termios.tcgetattr(fd) - try: - tty.setraw(sys.stdin.fileno()) - ch = sys.stdin.read(1) - finally: - termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) - - return ch diff --git a/progressbar/os_functions/windows.py b/progressbar/os_functions/windows.py deleted file mode 100644 index edac0696..00000000 --- a/progressbar/os_functions/windows.py +++ /dev/null @@ -1,144 +0,0 @@ -import ctypes -from ctypes.wintypes import ( - DWORD as _DWORD, - HANDLE as _HANDLE, - BOOL as _BOOL, - WORD as _WORD, - UINT as _UINT, - WCHAR as _WCHAR, - CHAR as _CHAR, - SHORT as _SHORT -) - -_kernel32 = ctypes.windll.Kernel32 - -_ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 -_ENABLE_PROCESSED_OUTPUT = 0x0001 -_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 - -_STD_INPUT_HANDLE = _DWORD(-10) -_STD_OUTPUT_HANDLE = _DWORD(-11) - - -_GetConsoleMode = _kernel32.GetConsoleMode -_GetConsoleMode.restype = _BOOL - -_SetConsoleMode = _kernel32.SetConsoleMode -_SetConsoleMode.restype = _BOOL - -_GetStdHandle = _kernel32.GetStdHandle -_GetStdHandle.restype = _HANDLE - -_ReadConsoleInput = _kernel32.ReadConsoleInputA -_ReadConsoleInput.restype = _BOOL - - -_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) -_input_mode = _DWORD() -_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) - -_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) -_output_mode = _DWORD() -_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) - - -class _COORD(ctypes.Structure): - _fields_ = [ - ('X', _SHORT), - ('Y', _SHORT) - ] - - -class _FOCUS_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('bSetFocus', _BOOL) - ] - - -class _KEY_EVENT_RECORD(ctypes.Structure): - class _uchar(ctypes.Union): - _fields_ = [ - ('UnicodeChar', _WCHAR), - ('AsciiChar', _CHAR) - ] - - _fields_ = [ - ('bKeyDown', _BOOL), - ('wRepeatCount', _WORD), - ('wVirtualKeyCode', _WORD), - ('wVirtualScanCode', _WORD), - ('uChar', _uchar), - ('dwControlKeyState', _DWORD) - ] - - -class _MENU_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('dwCommandId', _UINT) - ] - - -class _MOUSE_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('dwMousePosition', _COORD), - ('dwButtonState', _DWORD), - ('dwControlKeyState', _DWORD), - ('dwEventFlags', _DWORD) - ] - - -class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): - _fields_ = [ - ('dwSize', _COORD) - ] - - -class _INPUT_RECORD(ctypes.Structure): - class _Event(ctypes.Union): - _fields_ = [ - ('KeyEvent', _KEY_EVENT_RECORD), - ('MouseEvent', _MOUSE_EVENT_RECORD), - ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), - ('MenuEvent', _MENU_EVENT_RECORD), - ('FocusEvent', _FOCUS_EVENT_RECORD) - ] - - _fields_ = [ - ('EventType', _WORD), - ('Event', _Event) - ] - - -def reset_console_mode(): - _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) - _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) - - -def set_console_mode(): - mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT - _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) - - mode = ( - _output_mode.value | - _ENABLE_PROCESSED_OUTPUT | - _ENABLE_VIRTUAL_TERMINAL_PROCESSING - ) - _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) - - -def getch(): - lpBuffer = (_INPUT_RECORD * 2)() - nLength = _DWORD(2) - lpNumberOfEventsRead = _DWORD() - - _ReadConsoleInput( - _HANDLE(_hConsoleInput), - lpBuffer, nLength, - ctypes.byref(lpNumberOfEventsRead) - ) - - char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') - if char == '\x00': - return None - - return char From d69026ac63172e7afead7e427654c78fdbf469aa Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 6 Dec 2022 02:52:54 +0100 Subject: [PATCH 10/24] Added ANSI terminal support for colors, bold, italic, underline, and many more --- progressbar/ansi.py | 1056 ------------------------------ progressbar/terminal/__init__.py | 0 progressbar/terminal/base.py | 353 ++++++++++ progressbar/terminal/colors.py | 947 +++++++++++++++++++++++++++ 4 files changed, 1300 insertions(+), 1056 deletions(-) delete mode 100644 progressbar/ansi.py create mode 100644 progressbar/terminal/__init__.py create mode 100644 progressbar/terminal/base.py create mode 100644 progressbar/terminal/colors.py diff --git a/progressbar/ansi.py b/progressbar/ansi.py deleted file mode 100644 index 2ace68d6..00000000 --- a/progressbar/ansi.py +++ /dev/null @@ -1,1056 +0,0 @@ -import collections -import threading - -from . import utils -from .os_functions import getch - -ESC = '\x1B' -CSI = ESC + '[' - -CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) - - -# Report Cursor Position (CPR), response = [row;column] as row;columnR -class _CPR(str): - _response_lock = threading.Lock() - - def __call__(self, stream): - res = '' - - with self._response_lock: - stream.write(str(self)) - stream.flush() - - while not res.endswith('R'): - char = getch() - - if char is not None: - res += char - - res = res[2:-1].split(';') - - res = tuple(int(item) if item.isdigit() else item for item in res) - - if len(res) == 1: - return res[0] - - return res - - def row(self, stream): - row, _ = self(stream) - return row - - def column(self, stream): - _, column = self(stream) - return column - - -DSR = CSI + '{n}n' # Device Status Report (DSR) -CPR = _CPR(DSR.format(n=6)) - -IL = CSI + '{n}L' # Insert n Line(s) (default = 1) - -DECRST = CSI + '?{n}l' # DEC Private Mode Reset -DECRTCEM = DECRST.format(n=25) # Hide Cursor - -DECSET = CSI + '?{n}h' # DEC Private Mode Set -DECTCEM = DECSET.format(n=25) # Show Cursor - -# possible values: -# 0 = Normal (default) -# 1 = Bold -# 2 = Faint -# 3 = Italic -# 4 = Underlined -# 5 = Slow blink (appears as Bold) -# 6 = Rapid Blink -# 7 = Inverse -# 8 = Invisible, i.e., hidden (VT300) -# 9 = Strike through -# 10 = Primary (default) font -# 20 = Gothic Font -# 21 = Double underline -# 22 = Normal intensity (neither bold nor faint) -# 23 = Not italic -# 24 = Not underlined -# 25 = Steady (not blinking) -# 26 = Proportional spacing -# 27 = Not inverse -# 28 = Visible, i.e., not hidden (VT300) -# 29 = No strike through -# 30 = Set foreground color to Black -# 31 = Set foreground color to Red -# 32 = Set foreground color to Green -# 33 = Set foreground color to Yellow -# 34 = Set foreground color to Blue -# 35 = Set foreground color to Magenta -# 36 = Set foreground color to Cyan -# 37 = Set foreground color to White -# 39 = Set foreground color to default (original) -# 40 = Set background color to Black -# 41 = Set background color to Red -# 42 = Set background color to Green -# 43 = Set background color to Yellow -# 44 = Set background color to Blue -# 45 = Set background color to Magenta -# 46 = Set background color to Cyan -# 47 = Set background color to White -# 49 = Set background color to default (original). -# 50 = Disable proportional spacing -# 51 = Framed -# 52 = Encircled -# 53 = Overlined -# 54 = Neither framed nor encircled -# 55 = Not overlined -# 58 = Set underine color (2;r;g;b) -# 59 = Default underline color -# If 16-color support is compiled, the following apply. -# Assume that xterm’s resources are set so that the ISO color codes are the -# first 8 of a set of 16. Then the aixterm colors are the bright versions of -# the ISO colors: -# 90 = Set foreground color to Black -# 91 = Set foreground color to Red -# 92 = Set foreground color to Green -# 93 = Set foreground color to Yellow -# 94 = Set foreground color to Blue -# 95 = Set foreground color to Magenta -# 96 = Set foreground color to Cyan -# 97 = Set foreground color to White -# 100 = Set background color to Black -# 101 = Set background color to Red -# 102 = Set background color to Green -# 103 = Set background color to Yellow -# 104 = Set background color to Blue -# 105 = Set background color to Magenta -# 106 = Set background color to Cyan -# 107 = Set background color to White -# -# If xterm is compiled with the 16-color support disabled, it supports the -# following, from rxvt: -# 100 = Set foreground and background color to default - -# If 88- or 256-color support is compiled, the following apply. -# 38;5;x = Set foreground color to x -# 48;5;x = Set background color to x -SGR = CSI + '{n}m' # Character Attributes - - -class ENCIRCLED(str): - ''' - Your guess is as good as mine. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=52), args[1], SGR.format(n=54)]) - return super(ENCIRCLED, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes encircled? - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=52), SGR.format(n=54)) - - -class FRAMED(str): - ''' - Your guess is as good as mine. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=51), args[1], SGR.format(n=54)]) - return super(FRAMED, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes Frame? - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=51), SGR.format(n=54)) - - -class GOTHIC(str): - ''' - Changes text font to Gothic - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=20), args[1], SGR.format(n=10)]) - return super(GOTHIC, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Makes text font normal - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=20), SGR.format(n=10)) - - -class ITALIC(str): - ''' - Makes the text italic - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=3), args[1], SGR.format(n=23)]) - return super(ITALIC, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the italic. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=3), SGR.format(n=23)) - - -class STRIKE_THROUGH(str): - ''' - Strikes through the text. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=9), args[1], SGR.format(n=29)]) - return super(STRIKE_THROUGH, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the strike through - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=9), SGR.format(n=29)) - - -class FAST_BLINK(str): - ''' - Makes the text blink fast - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=6), args[1], SGR.format(n=25)]) - return super(FAST_BLINK, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Makes the text steady - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=6), SGR.format(n=25)) - - -class SLOW_BLINK(str): - ''' - Makes the text blonk slowely. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=5), args[1], SGR.format(n=25)]) - return super(SLOW_BLINK, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Makes the text steady - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=5), SGR.format(n=25)) - - -class OVERLINE(str): - ''' - Overlines the text provided. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=53), args[1], SGR.format(n=55)]) - return super(OVERLINE, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the overline from the text - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=53), SGR.format(n=55)) - - -class UNDERLINE(str): - ''' - Underlines the text provided. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=4), args[1], SGR.format(n=24)]) - return super(UNDERLINE, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the underline from the text - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=4), SGR.format(n=24)) - - -class DOUBLE_UNDERLINE(str): - ''' - Double underlines the text provided. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=21), args[1], SGR.format(n=24)]) - return super(DOUBLE_UNDERLINE, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the double underline from the text - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=21), SGR.format(n=24)) - - -class BOLD(str): - ''' - Makes the supplied text BOLD - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=1), args[1], SGR.format(n=22)]) - return super(BOLD, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the BOLD from the text. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=1), SGR.format(n=22)) - - -class FAINT(str): - ''' - Makes the supplied text FAINT - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=2), args[1], SGR.format(n=22)]) - return super(FAINT, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the FAINT from the text. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=2), SGR.format(n=22)) - - -class INVERT_COLORS(str): - ''' - Switches the background and forground colors. - ''' - - @classmethod - def __new__(cls, *args, **kwargs): - args = list(args) - args[1] = ''.join([SGR.format(n=7), args[1], SGR.format(n=27)]) - return super(INVERT_COLORS, cls).__new__(*args, **kwargs) - - @property - def raw(self): - ''' - Removes the color inversion and returns the original text provided. - ''' - text = self.__str__() - return utils.remove_ansi(text, SGR.format(n=7), SGR.format(n=27)) - - -RGB = collections.namedtuple('RGB', ['r', 'g', 'b']) - - -class Color(str): - ''' - Color base class - - This class is a wrapper for the `str` class that adds a couple of - class methods. It makes it easier to add and remove an ansi color escape - sequence from a string of text. - - There are 141 HTML colors that have already been provided however you can - make a custom color if you would like. - - To make a custom color simply subclass this class and override the `_rgb` - class attribute supplying your own RGB value as a tuple (R, G, B) - ''' - _rgb: RGB = RGB(0, 0, 0) - - @classmethod - def fg(cls, text): - ''' - Adds the ansi escape codes to set the foreground color to this color. - ''' - return cls( - ''.join( - [ - CSI, - '38;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=39) - ] - ) - ) - - @classmethod - def bg(cls, text): - ''' - Adds the ansi escape codes to set the background color to this color. - ''' - return cls( - ''.join( - [ - CSI, - '48;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=49) - ] - ) - ) - - @classmethod - def ul(cls, text): - ''' - Adds the ansi escape codes to set the underline color to this color. - ''' - return cls( - ''.join( - [ - CSI, - '58;2;{0};{1};{2}m'.format(*cls._rgb), - text, - SGR.format(n=59) - ] - ) - ) - - @property - def raw(self): - ''' - Removes this color from the text provided - ''' - text = self.__str__() - - if text.startswith(CSI + '48;2'): - text = utils.remove_ansi( - text, - CSI + '48;2;{0};{1};{2}m'.format(*self._rgb), - SGR.format(n=49) - ) - elif text.startswith(CSI + '38;2'): - text = utils.remove_ansi( - text, - CSI + '38;2;{0};{1};{2}m'.format(*self._rgb), - SGR.format(n=39) - ) - - else: - text = utils.remove_ansi( - text, - CSI + '58;2;{0};{1};{2}m'.format(*self._rgb), - SGR.format(n=59) - ) - - return text - - -class INDIAN_RED(Color): - _rgb = RGB(205, 92, 92) - - -class LIGHT_CORAL(Color): - _rgb = RGB(240, 128, 128) - - -class SALMON(Color): - _rgb = RGB(250, 128, 114) - - -class DARK_SALMON(Color): - _rgb = RGB(233, 150, 122) - - -class LIGHT_SALMON(Color): - _rgb = RGB(255, 160, 122) - - -class CRIMSON(Color): - _rgb = RGB(220, 20, 60) - - -class RED(Color): - _rgb = RGB(255, 0, 0) - - -class FIRE_BRICK(Color): - _rgb = RGB(178, 34, 34) - - -class DARK_RED(Color): - _rgb = RGB(139, 0, 0) - - -class PINK(Color): - _rgb = RGB(255, 192, 203) - - -class LIGHT_PINK(Color): - _rgb = RGB(255, 182, 193) - - -class HOT_PINK(Color): - _rgb = RGB(255, 105, 180) - - -class DEEP_PINK(Color): - _rgb = RGB(255, 20, 147) - - -class MEDIUM_VIOLET_RED(Color): - _rgb = RGB(199, 21, 133) - - -class PALE_VIOLET_RED(Color): - _rgb = RGB(219, 112, 147) - - -class CORAL(Color): - _rgb = RGB(255, 127, 80) - - -class TOMATO(Color): - _rgb = RGB(255, 99, 71) - - -class ORANGE_RED(Color): - _rgb = RGB(255, 69, 0) - - -class DARK_ORANGE(Color): - _rgb = RGB(255, 140, 0) - - -class ORANGE(Color): - _rgb = RGB(255, 165, 0) - - -class GOLD(Color): - _rgb = RGB(255, 215, 0) - - -class YELLOW(Color): - _rgb = RGB(255, 255, 0) - - -class LIGHT_YELLOW(Color): - _rgb = RGB(255, 255, 224) - - -class LEMON_CHIFFON(Color): - _rgb = RGB(255, 250, 205) - - -class LIGHT_GOLDENROD_YELLOW(Color): - _rgb = RGB(250, 250, 210) - - -class PAPAYA_WHIP(Color): - _rgb = RGB(255, 239, 213) - - -class MOCCASIN(Color): - _rgb = RGB(255, 228, 181) - - -class PEACH_PUFF(Color): - _rgb = RGB(255, 218, 185) - - -class PALE_GOLDENROD(Color): - _rgb = RGB(238, 232, 170) - - -class KHAKI(Color): - _rgb = RGB(240, 230, 140) - - -class DARK_KHAKI(Color): - _rgb = RGB(189, 183, 107) - - -class LAVENDER(Color): - _rgb = RGB(230, 230, 250) - - -class THISTLE(Color): - _rgb = RGB(216, 191, 216) - - -class PLUM(Color): - _rgb = RGB(221, 160, 221) - - -class VIOLET(Color): - _rgb = RGB(238, 130, 238) - - -class ORCHID(Color): - _rgb = RGB(218, 112, 214) - - -class FUCHSIA(Color): - _rgb = RGB(255, 0, 255) - - -class MAGENTA(Color): - _rgb = RGB(255, 0, 255) - - -class MEDIUM_ORCHID(Color): - _rgb = RGB(186, 85, 211) - - -class MEDIUM_PURPLE(Color): - _rgb = RGB(147, 112, 219) - - -class REBECCA_PURPLE(Color): - _rgb = RGB(102, 51, 153) - - -class BLUE_VIOLET(Color): - _rgb = RGB(138, 43, 226) - - -class DARK_VIOLET(Color): - _rgb = RGB(148, 0, 211) - - -class DARK_ORCHID(Color): - _rgb = RGB(153, 50, 204) - - -class DARK_MAGENTA(Color): - _rgb = RGB(139, 0, 139) - - -class PURPLE(Color): - _rgb = RGB(128, 0, 128) - - -class INDIGO(Color): - _rgb = RGB(75, 0, 130) - - -class SLATE_BLUE(Color): - _rgb = RGB(106, 90, 205) - - -class DARK_SLATE_BLUE(Color): - _rgb = RGB(72, 61, 139) - - -class MEDIUM_SLATE_BLUE(Color): - _rgb = RGB(123, 104, 238) - - -class GREEN_YELLOW(Color): - _rgb = RGB(173, 255, 47) - - -class CHARTREUSE(Color): - _rgb = RGB(127, 255, 0) - - -class LAWN_GREEN(Color): - _rgb = RGB(124, 252, 0) - - -class LIME(Color): - _rgb = RGB(0, 255, 0) - - -class LIME_GREEN(Color): - _rgb = RGB(50, 205, 50) - - -class PALE_GREEN(Color): - _rgb = RGB(152, 251, 152) - - -class LIGHT_GREEN(Color): - _rgb = RGB(144, 238, 144) - - -class MEDIUM_SPRING_GREEN(Color): - _rgb = RGB(0, 250, 154) - - -class SPRING_GREEN(Color): - _rgb = RGB(0, 255, 127) - - -class MEDIUM_SEA_GREEN(Color): - _rgb = RGB(60, 179, 113) - - -class SEA_GREEN(Color): - _rgb = RGB(46, 139, 87) - - -class FOREST_GREEN(Color): - _rgb = RGB(34, 139, 34) - - -class GREEN(Color): - _rgb = RGB(0, 128, 0) - - -class DARK_GREEN(Color): - _rgb = RGB(0, 100, 0) - - -class YELLOW_GREEN(Color): - _rgb = RGB(154, 205, 50) - - -class OLIVE_DRAB(Color): - _rgb = RGB(107, 142, 35) - - -class OLIVE(Color): - _rgb = RGB(128, 128, 0) - - -class DARK_OLIVE_GREEN(Color): - _rgb = RGB(85, 107, 47) - - -class MEDIUM_AQUAMARINE(Color): - _rgb = RGB(102, 205, 170) - - -class DARK_SEA_GREEN(Color): - _rgb = RGB(143, 188, 139) - - -class LIGHT_SEA_GREEN(Color): - _rgb = RGB(32, 178, 170) - - -class DARK_CYAN(Color): - _rgb = RGB(0, 139, 139) - - -class TEAL(Color): - _rgb = RGB(0, 128, 128) - - -class AQUA(Color): - _rgb = RGB(0, 255, 255) - - -class CYAN(Color): - _rgb = RGB(0, 255, 255) - - -class LIGHT_CYAN(Color): - _rgb = RGB(224, 255, 255) - - -class PALE_TURQUOISE(Color): - _rgb = RGB(175, 238, 238) - - -class AQUAMARINE(Color): - _rgb = RGB(127, 255, 212) - - -class TURQUOISE(Color): - _rgb = RGB(64, 224, 208) - - -class MEDIUM_TURQUOISE(Color): - _rgb = RGB(72, 209, 204) - - -class DARK_TURQUOISE(Color): - _rgb = RGB(0, 206, 209) - - -class CADET_BLUE(Color): - _rgb = RGB(95, 158, 160) - - -class STEEL_BLUE(Color): - _rgb = RGB(70, 130, 180) - - -class LIGHT_STEEL_BLUE(Color): - _rgb = RGB(176, 196, 222) - - -class POWDER_BLUE(Color): - _rgb = RGB(176, 224, 230) - - -class LIGHT_BLUE(Color): - _rgb = RGB(173, 216, 230) - - -class SKY_BLUE(Color): - _rgb = RGB(135, 206, 235) - - -class LIGHT_SKY_BLUE(Color): - _rgb = RGB(135, 206, 250) - - -class DEEP_SKY_BLUE(Color): - _rgb = RGB(0, 191, 255) - - -class DODGER_BLUE(Color): - _rgb = RGB(30, 144, 255) - - -class CORNFLOWER_BLUE(Color): - _rgb = RGB(100, 149, 237) - - -class ROYAL_BLUE(Color): - _rgb = RGB(65, 105, 225) - - -class BLUE(Color): - _rgb = RGB(0, 0, 255) - - -class MEDIUM_BLUE(Color): - _rgb = RGB(0, 0, 205) - - -class DARK_BLUE(Color): - _rgb = RGB(0, 0, 139) - - -class NAVY(Color): - _rgb = RGB(0, 0, 128) - - -class MIDNIGHT_BLUE(Color): - _rgb = RGB(25, 25, 112) - - -class CORNSILK(Color): - _rgb = RGB(255, 248, 220) - - -class BLANCHED_ALMOND(Color): - _rgb = RGB(255, 235, 205) - - -class BISQUE(Color): - _rgb = RGB(255, 228, 196) - - -class NAVAJO_WHITE(Color): - _rgb = RGB(255, 222, 173) - - -class WHEAT(Color): - _rgb = RGB(245, 222, 179) - - -class BURLY_WOOD(Color): - _rgb = RGB(222, 184, 135) - - -class TAN(Color): - _rgb = RGB(210, 180, 140) - - -class ROSY_BROWN(Color): - _rgb = RGB(188, 143, 143) - - -class SANDY_BROWN(Color): - _rgb = RGB(244, 164, 96) - - -class GOLDENROD(Color): - _rgb = RGB(218, 165, 32) - - -class DARK_GOLDENROD(Color): - _rgb = RGB(184, 134, 11) - - -class PERU(Color): - _rgb = RGB(205, 133, 63) - - -class CHOCOLATE(Color): - _rgb = RGB(210, 105, 30) - - -class SADDLE_BROWN(Color): - _rgb = RGB(139, 69, 19) - - -class SIENNA(Color): - _rgb = RGB(160, 82, 45) - - -class BROWN(Color): - _rgb = RGB(165, 42, 42) - - -class MAROON(Color): - _rgb = RGB(128, 0, 0) - - -class WHITE(Color): - _rgb = RGB(255, 255, 255) - - -class SNOW(Color): - _rgb = RGB(255, 250, 250) - - -class HONEY_DEW(Color): - _rgb = RGB(240, 255, 240) - - -class MINT_CREAM(Color): - _rgb = RGB(245, 255, 250) - - -class AZURE(Color): - _rgb = RGB(240, 255, 255) - - -class ALICE_BLUE(Color): - _rgb = RGB(240, 248, 255) - - -class GHOST_WHITE(Color): - _rgb = RGB(248, 248, 255) - - -class WHITE_SMOKE(Color): - _rgb = RGB(245, 245, 245) - - -class SEA_SHELL(Color): - _rgb = RGB(255, 245, 238) - - -class BEIGE(Color): - _rgb = RGB(245, 245, 220) - - -class OLD_LACE(Color): - _rgb = RGB(253, 245, 230) - - -class FLORAL_WHITE(Color): - _rgb = RGB(255, 250, 240) - - -class IVORY(Color): - _rgb = RGB(255, 255, 240) - - -class ANTIQUE_WHITE(Color): - _rgb = RGB(250, 235, 215) - - -class LINEN(Color): - _rgb = RGB(250, 240, 230) - - -class LAVENDER_BLUSH(Color): - _rgb = RGB(255, 240, 245) - - -class MISTY_ROSE(Color): - _rgb = RGB(255, 228, 225) - - -class GAINSBORO(Color): - _rgb = RGB(220, 220, 220) - - -class LIGHT_GRAY(Color): - _rgb = RGB(211, 211, 211) - - -class SILVER(Color): - _rgb = RGB(192, 192, 192) - - -class DARK_GRAY(Color): - _rgb = RGB(169, 169, 169) - - -class GRAY(Color): - _rgb = RGB(128, 128, 128) - - -class DIM_GRAY(Color): - _rgb = RGB(105, 105, 105) - - -class LIGHT_SLATE_GRAY(Color): - _rgb = RGB(119, 136, 153) - - -class SLATE_GRAY(Color): - _rgb = RGB(112, 128, 144) - - -class DARK_SLATE_GRAY(Color): - _rgb = RGB(47, 79, 79) - - -class BLACK(Color): - _rgb = RGB(0, 0, 0) diff --git a/progressbar/terminal/__init__.py b/progressbar/terminal/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py new file mode 100644 index 00000000..d63a8da9 --- /dev/null +++ b/progressbar/terminal/base.py @@ -0,0 +1,353 @@ +from __future__ import annotations + +import collections +import colorsys +import enum +import os +import threading +from collections import defaultdict + +from python_utils import types + +# from .os_functions import getch +# from .. import utils + +ESC = '\x1B' +CSI = ESC + '[' + +CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) + + +class ColorSupport(enum.Enum): + '''Color support for the terminal.''' + NONE = 0 + XTERM = 1 + XTERM_256 = 2 + XTERM_TRUECOLOR = 3 + + @classmethod + def from_env(cls): + '''Get the color support from the environment.''' + if os.getenv('COLORTERM') == 'truecolor': + return cls.XTERM_TRUECOLOR + elif os.getenv('TERM') == 'xterm-256color': + return cls.XTERM_256 + elif os.getenv('TERM') == 'xterm': + return cls.XTERM + else: + return cls.NONE + + +color_support = ColorSupport.from_env() + + +# Report Cursor Position (CPR), response = [row;column] as row;columnR +class _CPR(str): + _response_lock = threading.Lock() + + def __call__(self, stream): + res = '' + + with self._response_lock: + stream.write(str(self)) + stream.flush() + + while not res.endswith('R'): + char = getch() + + if char is not None: + res += char + + res = res[2:-1].split(';') + + res = tuple(int(item) if item.isdigit() else item for item in res) + + if len(res) == 1: + return res[0] + + return res + + def row(self, stream): + row, _ = self(stream) + return row + + def column(self, stream): + _, column = self(stream) + return column + + +DSR = CSI + '{n}n' # Device Status Report (DSR) +CPR = _CPR(DSR.format(n=6)) + +IL = CSI + '{n}L' # Insert n Line(s) (default = 1) + +DECRST = CSI + '?{n}l' # DEC Private Mode Reset +DECRTCEM = DECRST.format(n=25) # Hide Cursor + +DECSET = CSI + '?{n}h' # DEC Private Mode Set +DECTCEM = DECSET.format(n=25) # Show Cursor + + +# possible values: +# 0 = Normal (default) +# 1 = Bold +# 2 = Faint +# 3 = Italic +# 4 = Underlined +# 5 = Slow blink (appears as Bold) +# 6 = Rapid Blink +# 7 = Inverse +# 8 = Invisible, i.e., hidden (VT300) +# 9 = Strike through +# 10 = Primary (default) font +# 20 = Gothic Font +# 21 = Double underline +# 22 = Normal intensity (neither bold nor faint) +# 23 = Not italic +# 24 = Not underlined +# 25 = Steady (not blinking) +# 26 = Proportional spacing +# 27 = Not inverse +# 28 = Visible, i.e., not hidden (VT300) +# 29 = No strike through +# 30 = Set foreground color to Black +# 31 = Set foreground color to Red +# 32 = Set foreground color to Green +# 33 = Set foreground color to Yellow +# 34 = Set foreground color to Blue +# 35 = Set foreground color to Magenta +# 36 = Set foreground color to Cyan +# 37 = Set foreground color to White +# 39 = Set foreground color to default (original) +# 40 = Set background color to Black +# 41 = Set background color to Red +# 42 = Set background color to Green +# 43 = Set background color to Yellow +# 44 = Set background color to Blue +# 45 = Set background color to Magenta +# 46 = Set background color to Cyan +# 47 = Set background color to White +# 49 = Set background color to default (original). +# 50 = Disable proportional spacing +# 51 = Framed +# 52 = Encircled +# 53 = Overlined +# 54 = Neither framed nor encircled +# 55 = Not overlined +# 58 = Set underine color (2;r;g;b) +# 59 = Default underline color +# If 16-color support is compiled, the following apply. +# Assume that xterm’s resources are set so that the ISO color codes are the +# first 8 of a set of 16. Then the aixterm colors are the bright versions of +# the ISO colors: +# 90 = Set foreground color to Black +# 91 = Set foreground color to Red +# 92 = Set foreground color to Green +# 93 = Set foreground color to Yellow +# 94 = Set foreground color to Blue +# 95 = Set foreground color to Magenta +# 96 = Set foreground color to Cyan +# 97 = Set foreground color to White +# 100 = Set background color to Black +# 101 = Set background color to Red +# 102 = Set background color to Green +# 103 = Set background color to Yellow +# 104 = Set background color to Blue +# 105 = Set background color to Magenta +# 106 = Set background color to Cyan +# 107 = Set background color to White +# +# If xterm is compiled with the 16-color support disabled, it supports the +# following, from rxvt: +# 100 = Set foreground and background color to default + +# If 88- or 256-color support is compiled, the following apply. +# 38;5;x = Set foreground color to x +# 48;5;x = Set background color to x + + +class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): + __slots__ = () + + def __str__(self): + return f'rgb({self.red}, {self.green}, {self.blue})' + + @property + def hex(self): + return f'#{self.red:02x}{self.green:02x}{self.blue:02x}' + + @property + def to_ansi_16(self): + # Using int instead of round because it maps slightly better + red = int(self.red / 255) + green = int(self.green / 255) + blue = int(self.blue / 255) + return (blue << 2) | (green << 1) | red + + @property + def to_ansi_256(self): + red = round(self.red / 255 * 5) + green = round(self.green / 255 * 5) + blue = round(self.blue / 255 * 5) + return 16 + 36 * red + 6 * green + blue + + +class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): + __slots__ = () + + @classmethod + def from_rgb(cls, rgb: RGB) -> HLS: + return cls( + *colorsys.rgb_to_hls(rgb.red / 255, rgb.green / 255, rgb.blue / 255) + ) + + +class Color( + collections.namedtuple( + 'Color', [ + 'rgb', + 'hls', + 'name', + 'xterm', + ] + ) +): + ''' + Color base class + + This class contains the colors in RGB (Red, Green, Blue), HLS (Hue, + Lightness, Saturation) and Xterm (8-bit) formats. It also contains the + color name. + + To make a custom color the only required arguments are the RGB values. + The other values will be automatically interpolated from that if needed, + but you can be more explicity if you wish. + ''' + __slots__ = () + + @property + def fg(self): + return SGRColor(self, 38, 39) + + @property + def bg(self): + return SGRColor(self, 48, 49) + + @property + def underline(self): + return SGRColor(self, 58, 59) + + @property + def ansi(self) -> types.Optional[str]: + if color_support is ColorSupport.XTERM_TRUECOLOR: + return f'2;{self.rgb.red};{self.rgb.green};{self.rgb.blue}' + + if self.xterm: + color = self.xterm + elif color_support is ColorSupport.XTERM_256: + color = self.rgb.to_ansi_256 + elif color_support is ColorSupport.XTERM: + color = self.rgb.to_ansi_16 + else: + return None + + return f'5;{color}' + + def __str__(self): + return self.name + + def __repr__(self): + return f'{self.__class__.__name__}({self.name!r})' + + def __hash__(self): + return hash(self.rgb) + + +class Colors: + by_name: defaultdict[str, types.List[Color]] = collections.defaultdict(list) + by_lowername: defaultdict[str, types.List[Color]] = collections.defaultdict( + list + ) + by_hex: defaultdict[str, types.List[Color]] = collections.defaultdict(list) + by_rgb: defaultdict[RGB, types.List[Color]] = collections.defaultdict(list) + by_hls: defaultdict[HLS, types.List[Color]] = collections.defaultdict(list) + by_xterm: dict[int, Color] = dict() + + @classmethod + def register( + cls, + rgb: RGB, + hls: types.Optional[HLS] = None, + name: types.Optional[str] = None, + xterm: types.Optional[int] = None, + ) -> Color: + color = Color(rgb, hls, name, xterm) + + if name: + cls.by_name[name].append(color) + cls.by_lowername[name.lower()].append(color) + + if hls is None: + hls = HLS.from_rgb(rgb) + + cls.by_hex[rgb.hex].append(color) + cls.by_rgb[rgb].append(color) + cls.by_hls[hls].append(color) + + if xterm is not None: + cls.by_xterm[xterm] = color + + return color + + +class SGR: + _start_code: int + _end_code: int + _template = CSI + '{n}m' + __slots__ = '_start_code', '_end_code' + + def __init__(self, start_code: int, end_code: int): + self._start_code = start_code + self._end_code = end_code + + @property + def _start_template(self): + return self._template.format(n=self._start_code) + + @property + def _end_template(self): + return self._template.format(n=self._end_code) + + def __call__(self, text): + return self._start_template + text + self._end_template + + +class SGRColor(SGR): + __slots__ = '_color', '_start_code', '_end_code' + _color_template = CSI + '{n};{color}m' + + def __init__(self, color: Color, start_code: int, end_code: int): + self._color = color + super().__init__(start_code, end_code) + + @property + def _start_template(self): + return self._color_template.format( + n=self._start_code, + color=self._color.ansi + ) + + +encircled = SGR(52, 54) +framed = SGR(51, 54) +overline = SGR(53, 55) +bold = SGR(1, 22) +gothic = SGR(20, 10) +italic = SGR(3, 23) +strike_through = SGR(9, 29) +fast_blink = SGR(6, 25) +slow_blink = SGR(5, 25) +underline = SGR(4, 24) +double_underline = SGR(21, 24) +faint = SGR(2, 22) +inverse = SGR(7, 27) diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py new file mode 100644 index 00000000..ed726aea --- /dev/null +++ b/progressbar/terminal/colors.py @@ -0,0 +1,947 @@ +# Based on: https://www.ditig.com/256-colors-cheat-sheet +from progressbar.terminal import base +from progressbar.terminal.base import Colors, HLS, RGB + +black = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Black', 0) +maroon = Colors.register(RGB(128, 0, 0), HLS(100, 0, 25), 'Maroon', 1) +green = Colors.register(RGB(0, 128, 0), HLS(100, 120, 25), 'Green', 2) +olive = Colors.register(RGB(128, 128, 0), HLS(100, 60, 25), 'Olive', 3) +navy = Colors.register(RGB(0, 0, 128), HLS(100, 240, 25), 'Navy', 4) +purple = Colors.register(RGB(128, 0, 128), HLS(100, 300, 25), 'Purple', 5) +teal = Colors.register(RGB(0, 128, 128), HLS(100, 180, 25), 'Teal', 6) +silver = Colors.register(RGB(192, 192, 192), HLS(0, 0, 75), 'Silver', 7) +grey = Colors.register(RGB(128, 128, 128), HLS(0, 0, 50), 'Grey', 8) +red = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red', 9) +lime = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Lime', 10) +yellow = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow', 11) +blue = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue', 12) +fuchsia = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Fuchsia', 13) +aqua = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Aqua', 14) +white = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'White', 15) +grey0 = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Grey0', 16) +navyBlue = Colors.register(RGB(0, 0, 95), HLS(100, 240, 18), 'NavyBlue', 17) +darkBlue = Colors.register(RGB(0, 0, 135), HLS(100, 240, 26), 'DarkBlue', 18) +blue3 = Colors.register(RGB(0, 0, 175), HLS(100, 240, 34), 'Blue3', 19) +blue3 = Colors.register(RGB(0, 0, 215), HLS(100, 240, 42), 'Blue3', 20) +blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) +darkGreen = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) +deepSkyBlue4 = Colors.register( + RGB(0, 95, 95), + HLS(100, 180, 18), + 'DeepSkyBlue4', + 23 +) +deepSkyBlue4 = Colors.register( + RGB(0, 95, 135), + HLS(100, 97, 26), + 'DeepSkyBlue4', + 24 +) +deepSkyBlue4 = Colors.register( + RGB(0, 95, 175), + HLS(100, 7, 34), + 'DeepSkyBlue4', + 25 +) +dodgerBlue3 = Colors.register( + RGB(0, 95, 215), + HLS(100, 13, 42), + 'DodgerBlue3', + 26 +) +dodgerBlue2 = Colors.register( + RGB(0, 95, 255), + HLS(100, 17, 50), + 'DodgerBlue2', + 27 +) +green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) +springGreen4 = Colors.register( + RGB(0, 135, 95), + HLS(100, 62, 26), + 'SpringGreen4', + 29 +) +turquoise4 = Colors.register( + RGB(0, 135, 135), + HLS(100, 180, 26), + 'Turquoise4', + 30 +) +deepSkyBlue3 = Colors.register( + RGB(0, 135, 175), + HLS(100, 93, 34), + 'DeepSkyBlue3', + 31 +) +deepSkyBlue3 = Colors.register( + RGB(0, 135, 215), + HLS(100, 2, 42), + 'DeepSkyBlue3', + 32 +) +dodgerBlue1 = Colors.register( + RGB(0, 135, 255), + HLS(100, 8, 50), + 'DodgerBlue1', + 33 +) +green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) +springGreen3 = Colors.register( + RGB(0, 175, 95), + HLS(100, 52, 34), + 'SpringGreen3', + 35 +) +darkCyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) +lightSeaGreen = Colors.register( + RGB(0, 175, 175), + HLS(100, 180, 34), + 'LightSeaGreen', + 37 +) +deepSkyBlue2 = Colors.register( + RGB(0, 175, 215), + HLS(100, 91, 42), + 'DeepSkyBlue2', + 38 +) +deepSkyBlue1 = Colors.register( + RGB(0, 175, 255), + HLS(100, 98, 50), + 'DeepSkyBlue1', + 39 +) +green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) +springGreen3 = Colors.register( + RGB(0, 215, 95), + HLS(100, 46, 42), + 'SpringGreen3', + 41 +) +springGreen2 = Colors.register( + RGB(0, 215, 135), + HLS(100, 57, 42), + 'SpringGreen2', + 42 +) +cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) +darkTurquoise = Colors.register( + RGB(0, 215, 215), + HLS(100, 180, 42), + 'DarkTurquoise', + 44 +) +turquoise2 = Colors.register( + RGB(0, 215, 255), + HLS(100, 89, 50), + 'Turquoise2', + 45 +) +green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) +springGreen2 = Colors.register( + RGB(0, 255, 95), + HLS(100, 42, 50), + 'SpringGreen2', + 47 +) +springGreen1 = Colors.register( + RGB(0, 255, 135), + HLS(100, 51, 50), + 'SpringGreen1', + 48 +) +mediumSpringGreen = Colors.register( + RGB(0, 255, 175), + HLS(100, 61, 50), + 'MediumSpringGreen', + 49 +) +cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) +cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) +darkRed = Colors.register(RGB(95, 0, 0), HLS(100, 0, 18), 'DarkRed', 52) +deepPink4 = Colors.register(RGB(95, 0, 95), HLS(100, 300, 18), 'DeepPink4', 53) +purple4 = Colors.register(RGB(95, 0, 135), HLS(100, 82, 26), 'Purple4', 54) +purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) +purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) +blueViolet = Colors.register( + RGB(95, 0, 255), + HLS(100, 62, 50), + 'BlueViolet', + 57 +) +orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) +grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) +mediumPurple4 = Colors.register( + RGB(95, 95, 135), + HLS(17, 240, 45), + 'MediumPurple4', + 60 +) +slateBlue3 = Colors.register( + RGB(95, 95, 175), + HLS(33, 240, 52), + 'SlateBlue3', + 61 +) +slateBlue3 = Colors.register( + RGB(95, 95, 215), + HLS(60, 240, 60), + 'SlateBlue3', + 62 +) +royalBlue1 = Colors.register( + RGB(95, 95, 255), + HLS(100, 240, 68), + 'RoyalBlue1', + 63 +) +chartreuse4 = Colors.register( + RGB(95, 135, 0), + HLS(100, 7, 26), + 'Chartreuse4', + 64 +) +darkSeaGreen4 = Colors.register( + RGB(95, 135, 95), + HLS(17, 120, 45), + 'DarkSeaGreen4', + 65 +) +paleTurquoise4 = Colors.register( + RGB(95, 135, 135), + HLS(17, 180, 45), + 'PaleTurquoise4', + 66 +) +steelBlue = Colors.register( + RGB(95, 135, 175), + HLS(33, 210, 52), + 'SteelBlue', + 67 +) +steelBlue3 = Colors.register( + RGB(95, 135, 215), + HLS(60, 220, 60), + 'SteelBlue3', + 68 +) +cornflowerBlue = Colors.register( + RGB(95, 135, 255), + HLS(100, 225, 68), + 'CornflowerBlue', + 69 +) +chartreuse3 = Colors.register( + RGB(95, 175, 0), + HLS(100, 7, 34), + 'Chartreuse3', + 70 +) +darkSeaGreen4 = Colors.register( + RGB(95, 175, 95), + HLS(33, 120, 52), + 'DarkSeaGreen4', + 71 +) +cadetBlue = Colors.register( + RGB(95, 175, 135), + HLS(33, 150, 52), + 'CadetBlue', + 72 +) +cadetBlue = Colors.register( + RGB(95, 175, 175), + HLS(33, 180, 52), + 'CadetBlue', + 73 +) +skyBlue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) +steelBlue1 = Colors.register( + RGB(95, 175, 255), + HLS(100, 210, 68), + 'SteelBlue1', + 75 +) +chartreuse3 = Colors.register( + RGB(95, 215, 0), + HLS(100, 3, 42), + 'Chartreuse3', + 76 +) +paleGreen3 = Colors.register( + RGB(95, 215, 95), + HLS(60, 120, 60), + 'PaleGreen3', + 77 +) +seaGreen3 = Colors.register( + RGB(95, 215, 135), + HLS(60, 140, 60), + 'SeaGreen3', + 78 +) +aquamarine3 = Colors.register( + RGB(95, 215, 175), + HLS(60, 160, 60), + 'Aquamarine3', + 79 +) +mediumTurquoise = Colors.register( + RGB(95, 215, 215), + HLS(60, 180, 60), + 'MediumTurquoise', + 80 +) +steelBlue1 = Colors.register( + RGB(95, 215, 255), + HLS(100, 195, 68), + 'SteelBlue1', + 81 +) +chartreuse2 = Colors.register( + RGB(95, 255, 0), + HLS(100, 7, 50), + 'Chartreuse2', + 82 +) +seaGreen2 = Colors.register( + RGB(95, 255, 95), + HLS(100, 120, 68), + 'SeaGreen2', + 83 +) +seaGreen1 = Colors.register( + RGB(95, 255, 135), + HLS(100, 135, 68), + 'SeaGreen1', + 84 +) +seaGreen1 = Colors.register( + RGB(95, 255, 175), + HLS(100, 150, 68), + 'SeaGreen1', + 85 +) +aquamarine1 = Colors.register( + RGB(95, 255, 215), + HLS(100, 165, 68), + 'Aquamarine1', + 86 +) +darkSlateGray2 = Colors.register( + RGB(95, 255, 255), + HLS(100, 180, 68), + 'DarkSlateGray2', + 87 +) +darkRed = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) +deepPink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) +darkMagenta = Colors.register( + RGB(135, 0, 135), + HLS(100, 300, 26), + 'DarkMagenta', + 90 +) +darkMagenta = Colors.register( + RGB(135, 0, 175), + HLS(100, 86, 34), + 'DarkMagenta', + 91 +) +darkViolet = Colors.register( + RGB(135, 0, 215), + HLS(100, 77, 42), + 'DarkViolet', + 92 +) +purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) +orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) +lightPink4 = Colors.register(RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95) +plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) +mediumPurple3 = Colors.register( + RGB(135, 95, 175), + HLS(33, 270, 52), + 'MediumPurple3', + 97 +) +mediumPurple3 = Colors.register( + RGB(135, 95, 215), + HLS(60, 260, 60), + 'MediumPurple3', + 98 +) +slateBlue1 = Colors.register( + RGB(135, 95, 255), + HLS(100, 255, 68), + 'SlateBlue1', + 99 +) +yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) +wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) +grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) +lightSlateGrey = Colors.register( + RGB(135, 135, 175), + HLS(20, 240, 60), + 'LightSlateGrey', + 103 +) +mediumPurple = Colors.register( + RGB(135, 135, 215), + HLS(50, 240, 68), + 'MediumPurple', + 104 +) +lightSlateBlue = Colors.register( + RGB(135, 135, 255), + HLS(100, 240, 76), + 'LightSlateBlue', + 105 +) +yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) +darkOliveGreen3 = Colors.register( + RGB(135, 175, 95), + HLS(33, 90, 52), + 'DarkOliveGreen3', + 107 +) +darkSeaGreen = Colors.register( + RGB(135, 175, 135), + HLS(20, 120, 60), + 'DarkSeaGreen', + 108 +) +lightSkyBlue3 = Colors.register( + RGB(135, 175, 175), + HLS(20, 180, 60), + 'LightSkyBlue3', + 109 +) +lightSkyBlue3 = Colors.register( + RGB(135, 175, 215), + HLS(50, 210, 68), + 'LightSkyBlue3', + 110 +) +skyBlue2 = Colors.register( + RGB(135, 175, 255), + HLS(100, 220, 76), + 'SkyBlue2', + 111 +) +chartreuse2 = Colors.register( + RGB(135, 215, 0), + HLS(100, 2, 42), + 'Chartreuse2', + 112 +) +darkOliveGreen3 = Colors.register( + RGB(135, 215, 95), + HLS(60, 100, 60), + 'DarkOliveGreen3', + 113 +) +paleGreen3 = Colors.register( + RGB(135, 215, 135), + HLS(50, 120, 68), + 'PaleGreen3', + 114 +) +darkSeaGreen3 = Colors.register( + RGB(135, 215, 175), + HLS(50, 150, 68), + 'DarkSeaGreen3', + 115 +) +darkSlateGray3 = Colors.register( + RGB(135, 215, 215), + HLS(50, 180, 68), + 'DarkSlateGray3', + 116 +) +skyBlue1 = Colors.register( + RGB(135, 215, 255), + HLS(100, 200, 76), + 'SkyBlue1', + 117 +) +chartreuse1 = Colors.register( + RGB(135, 255, 0), + HLS(100, 8, 50), + 'Chartreuse1', + 118 +) +lightGreen = Colors.register( + RGB(135, 255, 95), + HLS(100, 105, 68), + 'LightGreen', + 119 +) +lightGreen = Colors.register( + RGB(135, 255, 135), + HLS(100, 120, 76), + 'LightGreen', + 120 +) +paleGreen1 = Colors.register( + RGB(135, 255, 175), + HLS(100, 140, 76), + 'PaleGreen1', + 121 +) +aquamarine1 = Colors.register( + RGB(135, 255, 215), + HLS(100, 160, 76), + 'Aquamarine1', + 122 +) +darkSlateGray1 = Colors.register( + RGB(135, 255, 255), + HLS(100, 180, 76), + 'DarkSlateGray1', + 123 +) +red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) +deepPink4 = Colors.register(RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125) +mediumVioletRed = Colors.register( + RGB(175, 0, 135), + HLS(100, 13, 34), + 'MediumVioletRed', + 126 +) +magenta3 = Colors.register(RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127) +darkViolet = Colors.register( + RGB(175, 0, 215), + HLS(100, 88, 42), + 'DarkViolet', + 128 +) +purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) +darkOrange3 = Colors.register( + RGB(175, 95, 0), + HLS(100, 2, 34), + 'DarkOrange3', + 130 +) +indianRed = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) +hotPink3 = Colors.register(RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132) +mediumOrchid3 = Colors.register( + RGB(175, 95, 175), + HLS(33, 300, 52), + 'MediumOrchid3', + 133 +) +mediumOrchid = Colors.register( + RGB(175, 95, 215), + HLS(60, 280, 60), + 'MediumOrchid', + 134 +) +mediumPurple2 = Colors.register( + RGB(175, 95, 255), + HLS(100, 270, 68), + 'MediumPurple2', + 135 +) +darkGoldenrod = Colors.register( + RGB(175, 135, 0), + HLS(100, 6, 34), + 'DarkGoldenrod', + 136 +) +lightSalmon3 = Colors.register( + RGB(175, 135, 95), + HLS(33, 30, 52), + 'LightSalmon3', + 137 +) +rosyBrown = Colors.register( + RGB(175, 135, 135), + HLS(20, 0, 60), + 'RosyBrown', + 138 +) +grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) +mediumPurple2 = Colors.register( + RGB(175, 135, 215), + HLS(50, 270, 68), + 'MediumPurple2', + 140 +) +mediumPurple1 = Colors.register( + RGB(175, 135, 255), + HLS(100, 260, 76), + 'MediumPurple1', + 141 +) +gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) +darkKhaki = Colors.register( + RGB(175, 175, 95), + HLS(33, 60, 52), + 'DarkKhaki', + 143 +) +navajoWhite3 = Colors.register( + RGB(175, 175, 135), + HLS(20, 60, 60), + 'NavajoWhite3', + 144 +) +grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) +lightSteelBlue3 = Colors.register( + RGB(175, 175, 215), + HLS(33, 240, 76), + 'LightSteelBlue3', + 146 +) +lightSteelBlue = Colors.register( + RGB(175, 175, 255), + HLS(100, 240, 84), + 'LightSteelBlue', + 147 +) +yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) +darkOliveGreen3 = Colors.register( + RGB(175, 215, 95), + HLS(60, 80, 60), + 'DarkOliveGreen3', + 149 +) +darkSeaGreen3 = Colors.register( + RGB(175, 215, 135), + HLS(50, 90, 68), + 'DarkSeaGreen3', + 150 +) +darkSeaGreen2 = Colors.register( + RGB(175, 215, 175), + HLS(33, 120, 76), + 'DarkSeaGreen2', + 151 +) +lightCyan3 = Colors.register( + RGB(175, 215, 215), + HLS(33, 180, 76), + 'LightCyan3', + 152 +) +lightSkyBlue1 = Colors.register( + RGB(175, 215, 255), + HLS(100, 210, 84), + 'LightSkyBlue1', + 153 +) +greenYellow = Colors.register( + RGB(175, 255, 0), + HLS(100, 8, 50), + 'GreenYellow', + 154 +) +darkOliveGreen2 = Colors.register( + RGB(175, 255, 95), + HLS(100, 90, 68), + 'DarkOliveGreen2', + 155 +) +paleGreen1 = Colors.register( + RGB(175, 255, 135), + HLS(100, 100, 76), + 'PaleGreen1', + 156 +) +darkSeaGreen2 = Colors.register( + RGB(175, 255, 175), + HLS(100, 120, 84), + 'DarkSeaGreen2', + 157 +) +darkSeaGreen1 = Colors.register( + RGB(175, 255, 215), + HLS(100, 150, 84), + 'DarkSeaGreen1', + 158 +) +paleTurquoise1 = Colors.register( + RGB(175, 255, 255), + HLS(100, 180, 84), + 'PaleTurquoise1', + 159 +) +red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) +deepPink3 = Colors.register(RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161) +deepPink3 = Colors.register( + RGB(215, 0, 135), + HLS(100, 22, 42), + 'DeepPink3', + 162 +) +magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) +magenta3 = Colors.register(RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164) +magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) +darkOrange3 = Colors.register( + RGB(215, 95, 0), + HLS(100, 6, 42), + 'DarkOrange3', + 166 +) +indianRed = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) +hotPink3 = Colors.register(RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168) +hotPink2 = Colors.register(RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169) +orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) +mediumOrchid1 = Colors.register( + RGB(215, 95, 255), + HLS(100, 285, 68), + 'MediumOrchid1', + 171 +) +orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) +lightSalmon3 = Colors.register( + RGB(215, 135, 95), + HLS(60, 20, 60), + 'LightSalmon3', + 173 +) +lightPink3 = Colors.register( + RGB(215, 135, 135), + HLS(50, 0, 68), + 'LightPink3', + 174 +) +pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) +plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) +violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) +gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) +lightGoldenrod3 = Colors.register( + RGB(215, 175, 95), + HLS(60, 40, 60), + 'LightGoldenrod3', + 179 +) +tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) +mistyRose3 = Colors.register( + RGB(215, 175, 175), + HLS(33, 0, 76), + 'MistyRose3', + 181 +) +thistle3 = Colors.register( + RGB(215, 175, 215), + HLS(33, 300, 76), + 'Thistle3', + 182 +) +plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) +yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) +khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) +lightGoldenrod2 = Colors.register( + RGB(215, 215, 135), + HLS(50, 60, 68), + 'LightGoldenrod2', + 186 +) +lightYellow3 = Colors.register( + RGB(215, 215, 175), + HLS(33, 60, 76), + 'LightYellow3', + 187 +) +grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) +lightSteelBlue1 = Colors.register( + RGB(215, 215, 255), + HLS(100, 240, 92), + 'LightSteelBlue1', + 189 +) +yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) +darkOliveGreen1 = Colors.register( + RGB(215, 255, 95), + HLS(100, 75, 68), + 'DarkOliveGreen1', + 191 +) +darkOliveGreen1 = Colors.register( + RGB(215, 255, 135), + HLS(100, 80, 76), + 'DarkOliveGreen1', + 192 +) +darkSeaGreen1 = Colors.register( + RGB(215, 255, 175), + HLS(100, 90, 84), + 'DarkSeaGreen1', + 193 +) +honeydew2 = Colors.register( + RGB(215, 255, 215), + HLS(100, 120, 92), + 'Honeydew2', + 194 +) +lightCyan1 = Colors.register( + RGB(215, 255, 255), + HLS(100, 180, 92), + 'LightCyan1', + 195 +) +red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) +deepPink2 = Colors.register(RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197) +deepPink1 = Colors.register( + RGB(255, 0, 135), + HLS(100, 28, 50), + 'DeepPink1', + 198 +) +deepPink1 = Colors.register( + RGB(255, 0, 175), + HLS(100, 18, 50), + 'DeepPink1', + 199 +) +magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) +magenta1 = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201) +orangeRed1 = Colors.register( + RGB(255, 95, 0), + HLS(100, 2, 50), + 'OrangeRed1', + 202 +) +indianRed1 = Colors.register( + RGB(255, 95, 95), + HLS(100, 0, 68), + 'IndianRed1', + 203 +) +indianRed1 = Colors.register( + RGB(255, 95, 135), + HLS(100, 345, 68), + 'IndianRed1', + 204 +) +hotPink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) +hotPink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) +mediumOrchid1 = Colors.register( + RGB(255, 95, 255), + HLS(100, 300, 68), + 'MediumOrchid1', + 207 +) +darkOrange = Colors.register( + RGB(255, 135, 0), + HLS(100, 1, 50), + 'DarkOrange', + 208 +) +salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) +lightCoral = Colors.register( + RGB(255, 135, 135), + HLS(100, 0, 76), + 'LightCoral', + 210 +) +paleVioletRed1 = Colors.register( + RGB(255, 135, 175), + HLS(100, 340, 76), + 'PaleVioletRed1', + 211 +) +orchid2 = Colors.register(RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212) +orchid1 = Colors.register(RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213) +orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) +sandyBrown = Colors.register( + RGB(255, 175, 95), + HLS(100, 30, 68), + 'SandyBrown', + 215 +) +lightSalmon1 = Colors.register( + RGB(255, 175, 135), + HLS(100, 20, 76), + 'LightSalmon1', + 216 +) +lightPink1 = Colors.register( + RGB(255, 175, 175), + HLS(100, 0, 84), + 'LightPink1', + 217 +) +pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) +plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) +gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) +lightGoldenrod2 = Colors.register( + RGB(255, 215, 95), + HLS(100, 45, 68), + 'LightGoldenrod2', + 221 +) +lightGoldenrod2 = Colors.register( + RGB(255, 215, 135), + HLS(100, 40, 76), + 'LightGoldenrod2', + 222 +) +navajoWhite1 = Colors.register( + RGB(255, 215, 175), + HLS(100, 30, 84), + 'NavajoWhite1', + 223 +) +mistyRose1 = Colors.register( + RGB(255, 215, 215), + HLS(100, 0, 92), + 'MistyRose1', + 224 +) +thistle1 = Colors.register( + RGB(255, 215, 255), + HLS(100, 300, 92), + 'Thistle1', + 225 +) +yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) +lightGoldenrod1 = Colors.register( + RGB(255, 255, 95), + HLS(100, 60, 68), + 'LightGoldenrod1', + 227 +) +khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) +wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) +cornsilk1 = Colors.register( + RGB(255, 255, 215), + HLS(100, 60, 92), + 'Cornsilk1', + 230 +) +grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) +grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) +grey7 = Colors.register(RGB(18, 18, 18), HLS(0, 0, 7), 'Grey7', 233) +grey11 = Colors.register(RGB(28, 28, 28), HLS(0, 0, 10), 'Grey11', 234) +grey15 = Colors.register(RGB(38, 38, 38), HLS(0, 0, 14), 'Grey15', 235) +grey19 = Colors.register(RGB(48, 48, 48), HLS(0, 0, 18), 'Grey19', 236) +grey23 = Colors.register(RGB(58, 58, 58), HLS(0, 0, 22), 'Grey23', 237) +grey27 = Colors.register(RGB(68, 68, 68), HLS(0, 0, 26), 'Grey27', 238) +grey30 = Colors.register(RGB(78, 78, 78), HLS(0, 0, 30), 'Grey30', 239) +grey35 = Colors.register(RGB(88, 88, 88), HLS(0, 0, 34), 'Grey35', 240) +grey39 = Colors.register(RGB(98, 98, 98), HLS(0, 0, 37), 'Grey39', 241) +grey42 = Colors.register(RGB(108, 108, 108), HLS(0, 0, 40), 'Grey42', 242) +grey46 = Colors.register(RGB(118, 118, 118), HLS(0, 0, 46), 'Grey46', 243) +grey50 = Colors.register(RGB(128, 128, 128), HLS(0, 0, 50), 'Grey50', 244) +grey54 = Colors.register(RGB(138, 138, 138), HLS(0, 0, 54), 'Grey54', 245) +grey58 = Colors.register(RGB(148, 148, 148), HLS(0, 0, 58), 'Grey58', 246) +grey62 = Colors.register(RGB(158, 158, 158), HLS(0, 0, 61), 'Grey62', 247) +grey66 = Colors.register(RGB(168, 168, 168), HLS(0, 0, 65), 'Grey66', 248) +grey70 = Colors.register(RGB(178, 178, 178), HLS(0, 0, 69), 'Grey70', 249) +grey74 = Colors.register(RGB(188, 188, 188), HLS(0, 0, 73), 'Grey74', 250) +grey78 = Colors.register(RGB(198, 198, 198), HLS(0, 0, 77), 'Grey78', 251) +grey82 = Colors.register(RGB(208, 208, 208), HLS(0, 0, 81), 'Grey82', 252) +grey85 = Colors.register(RGB(218, 218, 218), HLS(0, 0, 85), 'Grey85', 253) +grey89 = Colors.register(RGB(228, 228, 228), HLS(0, 0, 89), 'Grey89', 254) +grey93 = Colors.register(RGB(238, 238, 238), HLS(0, 0, 93), 'Grey93', 255) + +if __name__ == '__main__': + red = Colors.register(RGB(255, 128, 128)) + # red = Colors.register(RGB(255, 100, 100)) + for i in base.ColorSupport: + base.color_support = i + print(i, red.fg('RED!'), red.bg('RED!'), red.underline('RED!')) From 761a5397e2653828a8d7ff15f35e455a8965a49a Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Tue, 6 Dec 2022 03:11:14 +0100 Subject: [PATCH 11/24] Revert "removed unneeded os functions for now" This reverts commit e2996be8590ebe1a4fe191bbb15041fdb9fa834d. --- progressbar/os_functions/__init__.py | 24 +++++ progressbar/os_functions/nix.py | 15 +++ progressbar/os_functions/windows.py | 144 +++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 progressbar/os_functions/__init__.py create mode 100644 progressbar/os_functions/nix.py create mode 100644 progressbar/os_functions/windows.py diff --git a/progressbar/os_functions/__init__.py b/progressbar/os_functions/__init__.py new file mode 100644 index 00000000..8b442283 --- /dev/null +++ b/progressbar/os_functions/__init__.py @@ -0,0 +1,24 @@ +import sys + +if sys.platform.startswith('win'): + from .windows import ( + getch as _getch, + set_console_mode as _set_console_mode, + reset_console_mode as _reset_console_mode + ) + +else: + from .nix import getch as _getch + + def _reset_console_mode(): + pass + + + def _set_console_mode(): + pass + + +getch = _getch +reset_console_mode = _reset_console_mode +set_console_mode = _set_console_mode + diff --git a/progressbar/os_functions/nix.py b/progressbar/os_functions/nix.py new file mode 100644 index 00000000..e46fbdf0 --- /dev/null +++ b/progressbar/os_functions/nix.py @@ -0,0 +1,15 @@ +import sys +import tty +import termios + + +def getch(): + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + try: + tty.setraw(sys.stdin.fileno()) + ch = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + + return ch diff --git a/progressbar/os_functions/windows.py b/progressbar/os_functions/windows.py new file mode 100644 index 00000000..edac0696 --- /dev/null +++ b/progressbar/os_functions/windows.py @@ -0,0 +1,144 @@ +import ctypes +from ctypes.wintypes import ( + DWORD as _DWORD, + HANDLE as _HANDLE, + BOOL as _BOOL, + WORD as _WORD, + UINT as _UINT, + WCHAR as _WCHAR, + CHAR as _CHAR, + SHORT as _SHORT +) + +_kernel32 = ctypes.windll.Kernel32 + +_ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 +_ENABLE_PROCESSED_OUTPUT = 0x0001 +_ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + +_STD_INPUT_HANDLE = _DWORD(-10) +_STD_OUTPUT_HANDLE = _DWORD(-11) + + +_GetConsoleMode = _kernel32.GetConsoleMode +_GetConsoleMode.restype = _BOOL + +_SetConsoleMode = _kernel32.SetConsoleMode +_SetConsoleMode.restype = _BOOL + +_GetStdHandle = _kernel32.GetStdHandle +_GetStdHandle.restype = _HANDLE + +_ReadConsoleInput = _kernel32.ReadConsoleInputA +_ReadConsoleInput.restype = _BOOL + + +_hConsoleInput = _GetStdHandle(_STD_INPUT_HANDLE) +_input_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleInput), ctypes.byref(_input_mode)) + +_hConsoleOutput = _GetStdHandle(_STD_OUTPUT_HANDLE) +_output_mode = _DWORD() +_GetConsoleMode(_HANDLE(_hConsoleOutput), ctypes.byref(_output_mode)) + + +class _COORD(ctypes.Structure): + _fields_ = [ + ('X', _SHORT), + ('Y', _SHORT) + ] + + +class _FOCUS_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('bSetFocus', _BOOL) + ] + + +class _KEY_EVENT_RECORD(ctypes.Structure): + class _uchar(ctypes.Union): + _fields_ = [ + ('UnicodeChar', _WCHAR), + ('AsciiChar', _CHAR) + ] + + _fields_ = [ + ('bKeyDown', _BOOL), + ('wRepeatCount', _WORD), + ('wVirtualKeyCode', _WORD), + ('wVirtualScanCode', _WORD), + ('uChar', _uchar), + ('dwControlKeyState', _DWORD) + ] + + +class _MENU_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwCommandId', _UINT) + ] + + +class _MOUSE_EVENT_RECORD(ctypes.Structure): + _fields_ = [ + ('dwMousePosition', _COORD), + ('dwButtonState', _DWORD), + ('dwControlKeyState', _DWORD), + ('dwEventFlags', _DWORD) + ] + + +class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): + _fields_ = [ + ('dwSize', _COORD) + ] + + +class _INPUT_RECORD(ctypes.Structure): + class _Event(ctypes.Union): + _fields_ = [ + ('KeyEvent', _KEY_EVENT_RECORD), + ('MouseEvent', _MOUSE_EVENT_RECORD), + ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), + ('MenuEvent', _MENU_EVENT_RECORD), + ('FocusEvent', _FOCUS_EVENT_RECORD) + ] + + _fields_ = [ + ('EventType', _WORD), + ('Event', _Event) + ] + + +def reset_console_mode(): + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(_input_mode.value)) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(_output_mode.value)) + + +def set_console_mode(): + mode = _input_mode.value | _ENABLE_VIRTUAL_TERMINAL_INPUT + _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) + + mode = ( + _output_mode.value | + _ENABLE_PROCESSED_OUTPUT | + _ENABLE_VIRTUAL_TERMINAL_PROCESSING + ) + _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) + + +def getch(): + lpBuffer = (_INPUT_RECORD * 2)() + nLength = _DWORD(2) + lpNumberOfEventsRead = _DWORD() + + _ReadConsoleInput( + _HANDLE(_hConsoleInput), + lpBuffer, nLength, + ctypes.byref(lpNumberOfEventsRead) + ) + + char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') + if char == '\x00': + return None + + return char From a1bfee72cd6f09c4b780f00769e839ff8184f9f6 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 8 Dec 2022 23:32:42 +0100 Subject: [PATCH 12/24] moved os specific code --- progressbar/terminal/base.py | 3 +-- progressbar/{os_functions => terminal/os_specific}/__init__.py | 2 +- .../{os_functions/nix.py => terminal/os_specific/posix.py} | 0 progressbar/{os_functions => terminal/os_specific}/windows.py | 0 pytest.ini | 1 + 5 files changed, 3 insertions(+), 3 deletions(-) rename progressbar/{os_functions => terminal/os_specific}/__init__.py (90%) rename progressbar/{os_functions/nix.py => terminal/os_specific/posix.py} (100%) rename progressbar/{os_functions => terminal/os_specific}/windows.py (100%) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index d63a8da9..77373794 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -9,8 +9,7 @@ from python_utils import types -# from .os_functions import getch -# from .. import utils +from .os_specific import getch ESC = '\x1B' CSI = ESC + '[' diff --git a/progressbar/os_functions/__init__.py b/progressbar/terminal/os_specific/__init__.py similarity index 90% rename from progressbar/os_functions/__init__.py rename to progressbar/terminal/os_specific/__init__.py index 8b442283..782ca9cd 100644 --- a/progressbar/os_functions/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -8,7 +8,7 @@ ) else: - from .nix import getch as _getch + from .posix import getch as _getch def _reset_console_mode(): pass diff --git a/progressbar/os_functions/nix.py b/progressbar/terminal/os_specific/posix.py similarity index 100% rename from progressbar/os_functions/nix.py rename to progressbar/terminal/os_specific/posix.py diff --git a/progressbar/os_functions/windows.py b/progressbar/terminal/os_specific/windows.py similarity index 100% rename from progressbar/os_functions/windows.py rename to progressbar/terminal/os_specific/windows.py diff --git a/pytest.ini b/pytest.ini index bdfd4dec..d6a47d53 100644 --- a/pytest.ini +++ b/pytest.ini @@ -19,6 +19,7 @@ norecursedirs = dist .ropeproject .tox + progressbar/terminal/os_specific filterwarnings = ignore::DeprecationWarning From f5a1ea2a8ca2009a639dc4d32031a93f47243769 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 12 Jan 2023 13:40:13 +0100 Subject: [PATCH 13/24] Added basic multiple progressbar support --- README.rst | 85 +++++++++++++++++++++++++---------------- progressbar/__init__.py | 2 + progressbar/bar.py | 8 ++++ progressbar/multi.py | 25 ++++++++++++ 4 files changed, 87 insertions(+), 33 deletions(-) create mode 100644 progressbar/multi.py diff --git a/README.rst b/README.rst index 94b33333..26695e96 100644 --- a/README.rst +++ b/README.rst @@ -238,55 +238,72 @@ Bar with wide Chinese (or other multibyte) characters for i in bar(range(10)): time.sleep(0.1) -Showing multiple (threaded) independent progress bars in parallel +Showing multiple independent progress bars in parallel ============================================================================== -While this method works fine and will continue to work fine, a smarter and -fully automatic version of this is currently being made: -https://github.com/WoLpH/python-progressbar/issues/176 - .. code:: python import random import sys - import threading import time import progressbar - output_lock = threading.Lock() + BARS = 5 + N = 100 + # Construct the list of progress bars with the `line_offset` so they draw + # below each other + bars = [] + for i in range(BARS): + bars.append( + progressbar.ProgressBar( + max_value=N, + # We add 1 to the line offset to account for the `print_fd` + line_offset=i + 1, + max_error=False, + ) + ) - class LineOffsetStreamWrapper: - UP = '\033[F' - DOWN = '\033[B' + # Create a file descriptor for regular printing as well + print_fd = progressbar.LineOffsetStreamWrapper(sys.stdout, 0) - def __init__(self, lines=0, stream=sys.stderr): - self.stream = stream - self.lines = lines + # The progress bar updates, normally you would do something useful here + for i in range(N * BARS): + time.sleep(0.005) - def write(self, data): - with output_lock: - self.stream.write(self.UP * self.lines) - self.stream.write(data) - self.stream.write(self.DOWN * self.lines) - self.stream.flush() + # Increment one of the progress bars at random + bars[random.randrange(0, BARS)].increment() - def __getattr__(self, name): - return getattr(self.stream, name) + # Print a status message to the `print_fd` below the progress bars + print(f'Hi, we are at update {i+1} of {N * BARS}', file=print_fd) + # Cleanup the bars + for bar in bars: + bar.finish() - bars = [] - for i in range(5): - bars.append( - progressbar.ProgressBar( - fd=LineOffsetStreamWrapper(i), - max_value=1000, - ) - ) + # Add a newline to make sure the next print starts on a new line + print() - if i: - print('Reserve a line for the progressbar') +****************************************************************************** + +Naturally we can do this from separate threads as well: + +.. code:: python + + import random + import threading + import time + + import progressbar + + BARS = 5 + N = 100 + + # Create the bars with the given line offset + bars = [] + for line_offset in range(BARS): + bars.append(progressbar.ProgressBar(line_offset=line_offset, max_value=N)) class Worker(threading.Thread): @@ -295,10 +312,12 @@ https://github.com/WoLpH/python-progressbar/issues/176 self.bar = bar def run(self): - for i in range(1000): - time.sleep(random.random() / 100) + for i in range(N): + time.sleep(random.random() / 25) self.bar.update(i) for bar in bars: Worker(bar).start() + + print() diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 33d7c719..b47d0541 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,6 +35,7 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin +from .multi import LineOffsetStreamWrapper __date__ = str(date.today()) __all__ = [ @@ -73,4 +74,5 @@ 'NullBar', '__author__', '__version__', + 'LineOffsetStreamWrapper', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index b1a5c96b..7ebc08f0 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -16,6 +16,7 @@ from . import ( base, + multi, utils, widgets, widgets as widgets_module, # Avoid name collision @@ -143,6 +144,7 @@ def __init__( is_terminal: bool | None = None, line_breaks: bool | None = None, enable_colors: bool | None = None, + line_offset: int = 0, **kwargs, ): if fd is sys.stdout: @@ -151,6 +153,9 @@ def __init__( elif fd is sys.stderr: fd = utils.streams.original_stderr + if line_offset: + fd = multi.LineOffsetStreamWrapper(line_offset, fd) + self.fd = fd self.is_ansi_terminal = utils.is_ansi_terminal(fd) @@ -385,6 +390,9 @@ class ProgressBar( from a label using `format='{variables.my_var}'`. These values can be updated using `bar.update(my_var='newValue')` This can also be used to set initial values for variables' widgets + line_offset (int): The number of lines to offset the progressbar from + your current line. This is useful if you have other output or + multiple progressbars A common way of using it is like: diff --git a/progressbar/multi.py b/progressbar/multi.py new file mode 100644 index 00000000..59f2db8e --- /dev/null +++ b/progressbar/multi.py @@ -0,0 +1,25 @@ +import sys + + +class LineOffsetStreamWrapper: + UP = '\033[F' + DOWN = '\033[B' + + def __init__(self, lines=0, stream=sys.stderr): + self.stream = stream + self.lines = lines + + def write(self, data): + # Move the cursor up + self.stream.write(self.UP * self.lines) + # Print a carriage return to reset the cursor position + self.stream.write('\r') + # Print the data without newlines so we don't change the position + self.stream.write(data.rstrip('\n')) + # Move the cursor down + self.stream.write(self.DOWN * self.lines) + + self.stream.flush() + + def __getattr__(self, name): + return getattr(self.stream, name) From 8c0ac1d2e99338f345965a1e5effe83f143dd82e Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 23 Jan 2023 02:44:53 +0100 Subject: [PATCH 14/24] Added fully functional multiple threaded progressbar support with print support --- progressbar/__init__.py | 5 +- progressbar/bar.py | 32 ++- progressbar/base.py | 4 +- progressbar/multi.py | 353 +++++++++++++++++++++++++++++-- progressbar/terminal/__init__.py | 1 + progressbar/terminal/base.py | 225 +++++++++++--------- progressbar/terminal/stream.py | 129 +++++++++++ 7 files changed, 623 insertions(+), 126 deletions(-) create mode 100644 progressbar/terminal/stream.py diff --git a/progressbar/__init__.py b/progressbar/__init__.py index b47d0541..117124c2 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,7 +35,8 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin -from .multi import LineOffsetStreamWrapper +from .multi import MultiBar +from .terminal.stream import LineOffsetStreamWrapper __date__ = str(date.today()) __all__ = [ @@ -74,5 +75,5 @@ 'NullBar', '__author__', '__version__', - 'LineOffsetStreamWrapper', + 'MultiBar', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index 7ebc08f0..b2826652 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -1,6 +1,7 @@ from __future__ import annotations import abc +import itertools import logging import math import os @@ -14,9 +15,9 @@ from python_utils import converters, types +import progressbar.terminal.stream from . import ( base, - multi, utils, widgets, widgets as widgets_module, # Avoid name collision @@ -120,9 +121,25 @@ def __getstate__(self): def data(self) -> types.Dict[str, types.Any]: raise NotImplementedError() + def started(self) -> bool: + return self._started or self._finished + + def finished(self) -> bool: + return self._finished + class ProgressBarBase(types.Iterable, ProgressBarMixinBase): - pass + _index_counter = itertools.count() + index: int = -1 + label: str = '' + + def __init__(self, **kwargs): + self.index = next(self._index_counter) + super().__init__(**kwargs) + + def __repr__(self): + label = f': {self.label}' if self.label else '' + return f'<{self.__class__.__name__}#{self.index}{label}>' class DefaultFdMixin(ProgressBarMixinBase): @@ -154,7 +171,10 @@ def __init__( fd = utils.streams.original_stderr if line_offset: - fd = multi.LineOffsetStreamWrapper(line_offset, fd) + fd = progressbar.terminal.stream.LineOffsetStreamWrapper( + line_offset, + fd + ) self.fd = fd self.is_ansi_terminal = utils.is_ansi_terminal(fd) @@ -183,6 +203,9 @@ def __init__( ProgressBarMixinBase.__init__(self, **kwargs) + def print(self, *args, **kwargs): + print(*args, file=self.fd, **kwargs) + def update(self, *args, **kwargs): ProgressBarMixinBase.update(self, *args, **kwargs) @@ -435,6 +458,7 @@ class ProgressBar( # update every 50 milliseconds (up to a 20 times per second) _MINIMUM_UPDATE_INTERVAL: float = 0.050 _last_update_time: types.Optional[float] = None + paused: bool = False def __init__( self, @@ -757,6 +781,8 @@ def increment(self, value=1, *args, **kwargs): def _needs_update(self): 'Returns whether the ProgressBar should redraw the line.' + if self.paused: + return False delta = timeit.default_timer() - self._last_update_timer if delta < self.min_poll_interval: # Prevent updating too often diff --git a/progressbar/base.py b/progressbar/base.py index 8639f557..32a95783 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -26,5 +26,5 @@ class Undefined(metaclass=FalseMeta): except AttributeError: from typing.io import IO, TextIO # type: ignore -assert IO -assert TextIO +assert IO is not None +assert TextIO is not None diff --git a/progressbar/multi.py b/progressbar/multi.py index 59f2db8e..d4d13979 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -1,25 +1,342 @@ +from __future__ import annotations + +import enum +import io +import itertools +import operator import sys +import threading +import time +import timeit +import typing +from datetime import timedelta + +import python_utils + +from . import bar, terminal +from .terminal import stream + +SortKeyFunc = typing.Callable[[bar.ProgressBar], typing.Any] + + +class SortKey(str, enum.Enum): + ''' + Sort keys for the MultiBar + + This is a string enum, so you can use any + progressbar attribute or property as a sort key. + + Note that the multibar defaults to lazily rendering only the changed + progressbars. This means that sorting by dynamic attributes such as + `value` might result in more rendering which can have a small performance + impact. + ''' + CREATED = 'index' + LABEL = 'label' + VALUE = 'value' + PERCENTAGE = 'percentage' + + +class MultiBar(dict[str, bar.ProgressBar]): + fd: typing.TextIO + _buffer: io.StringIO + + #: The format for the label to append/prepend to the progressbar + label_format: str + #: Automatically prepend the label to the progressbars + prepend_label: bool + #: Automatically append the label to the progressbars + append_label: bool + #: If `initial_format` is `None`, the progressbar rendering is used + # which will *start* the progressbar. That means the progressbar will + # have no knowledge of your data and will run as an infinite progressbar. + initial_format: str | None + #: If `finished_format` is `None`, the progressbar rendering is used. + finished_format: str | None + + #: The multibar updates at a fixed interval regardless of the progressbar + # updates + update_interval: float + remove_finished: float | None + + #: The kwargs passed to the progressbar constructor + progressbar_kwargs: typing.Dict[str, typing.Any] + + #: The progressbar sorting key function + sort_keyfunc: SortKeyFunc + + _previous_output: list[str] + _finished_at: dict[bar.ProgressBar, float] + _labeled: set[bar.ProgressBar] + _print_lock: threading.RLock = threading.RLock() + _thread: threading.Thread | None = None + _thread_finished: threading.Event = threading.Event() + _thread_closed: threading.Event = threading.Event() + + def __init__( + self, + bars: typing.Iterable[tuple[str, bar.ProgressBar]] | None = None, + fd=sys.stderr, + prepend_label: bool = True, + append_label: bool = False, + label_format='{label:20.20} ', + initial_format: str | None = '{label:20.20} Not yet started', + finished_format: str | None = None, + update_interval: float = 1 / 60.0, # 60fps + show_initial: bool = True, + show_finished: bool = True, + remove_finished: timedelta | float = timedelta(seconds=3600), + sort_key: str | SortKey = SortKey.CREATED, + sort_reverse: bool = True, + sort_keyfunc: SortKeyFunc | None = None, + **progressbar_kwargs, + ): + self.fd = fd + + self.prepend_label = prepend_label + self.append_label = append_label + self.label_format = label_format + self.initial_format = initial_format + self.finished_format = finished_format + + self.update_interval = update_interval + + self.show_initial = show_initial + self.show_finished = show_finished + self.remove_finished = python_utils.delta_to_seconds_or_none( + remove_finished + ) + + self.progressbar_kwargs = progressbar_kwargs + + if sort_keyfunc is None: + sort_keyfunc = operator.attrgetter(sort_key) + + self.sort_keyfunc = sort_keyfunc + self.sort_reverse = sort_reverse + + self._labeled = set() + self._finished_at = {} + self._previous_output = [] + self._buffer = io.StringIO() + + super().__init__(bars or {}) + + def __setitem__(self, key: str, value: bar.ProgressBar): + '''Add a progressbar to the multibar''' + if value.label != key: + value.label = key + value.fd = stream.LastLineStream(self.fd) + value.paused = True + value.print = self.print + + # Just in case someone is using a progressbar with a custom + # constructor and forgot to call the super constructor + if value.index == -1: + value.index = next(value._index_counter) + + super().__setitem__(key, value) + + def __delitem__(self, key): + '''Remove a progressbar from the multibar''' + super().__delitem__(key) + self._finished_at.pop(key, None) + self._labeled.discard(key) + + def __getitem__(self, item): + '''Get (and create if needed) a progressbar from the multibar''' + try: + return super().__getitem__(item) + except KeyError: + progress = bar.ProgressBar(**self.progressbar_kwargs) + self[item] = progress + return progress + + def _label_bar(self, bar: bar.ProgressBar): + if bar in self._labeled: + return + + assert bar.widgets, 'Cannot prepend label to empty progressbar' + self._labeled.add(bar) + + if self.prepend_label: + bar.widgets.insert(0, self.label_format.format(label=bar.label)) + + if self.append_label and bar not in self._labeled: + bar.widgets.append(self.label_format.format(label=bar.label)) + + def render(self, flush: bool = True, force: bool = False): + '''Render the multibar to the given stream''' + now = timeit.default_timer() + expired = now - self.remove_finished if self.remove_finished else None + output = [] + for bar in self.get_sorted_bars(): + if not bar.started() and not self.show_initial: + continue + + def update(force=True, write=True): + self._label_bar(bar) + bar.update(force=force) + if write: + output.append(bar.fd.line) + + if bar.finished(): + if bar not in self._finished_at: + self._finished_at[bar] = now + # Force update to get the finished format + update(write=False) + + if self.remove_finished: + if expired >= self._finished_at[bar]: + del self[bar.label] + continue + + if not self.show_finished: + continue + + if bar.finished(): + if self.finished_format is None: + update(force=False) + else: + output.append(self.finished_format.format(label=bar.label)) + elif bar.started(): + update() + else: + if self.initial_format is None: + bar.start() + update() + else: + output.append(self.initial_format.format(label=bar.label)) + + with self._print_lock: + # Clear the previous output if progressbars have been removed + for i in range(len(output), len(self._previous_output)): + self._buffer.write(terminal.clear_line(i + 1)) + + # Add empty lines to the end of the output if progressbars have been + # added + for i in range(len(self._previous_output), len(output)): + # Adding a new line so we don't overwrite previous output + self._buffer.write('\n') + + for i, (previous, current) in enumerate( + itertools.zip_longest( + self._previous_output, + output, + fillvalue='' + ) + ): + if previous != current or force: + self.print( + '\r' + current.strip(), + offset=i + 1, + end='', + clear=False, + flush=False, + ) + + self._previous_output = output + + if flush: + self.flush() + + def print( + self, + *args, + end='\n', + offset=None, + flush=True, + clear=True, + **kwargs + ): + ''' + Print to the progressbar stream without overwriting the progressbars + + Args: + end: The string to append to the end of the output + offset: The number of lines to offset the output by. If None, the + output will be printed above the progressbars + flush: Whether to flush the output to the stream + clear: If True, the line will be cleared before printing. + **kwargs: Additional keyword arguments to pass to print + ''' + with self._print_lock: + if offset is None: + offset = len(self._previous_output) + + if not clear: + self._buffer.write(terminal.PREVIOUS_LINE(offset)) + + if clear: + self._buffer.write(terminal.PREVIOUS_LINE(offset)) + self._buffer.write(terminal.CLEAR_LINE_ALL()) + + print(*args, **kwargs, file=self._buffer, end=end) + + if clear: + self._buffer.write(terminal.CLEAR_SCREEN_TILL_END()) + for line in self._previous_output: + self._buffer.write(line.strip()) + self._buffer.write('\n') + + else: + self._buffer.write(terminal.NEXT_LINE(offset)) + + if flush: + self.flush() + + def flush(self): + self.fd.write(self._buffer.getvalue()) + self._buffer.truncate(0) + self.fd.flush() + + def run(self, join=True): + ''' + Start the multibar render loop and run the progressbars until they + have force _thread_finished + ''' + while not self._thread_finished.is_set(): + self.render() + time.sleep(self.update_interval) + + if join or self._thread_closed.is_set(): + # If the thread is closed, we need to check if force progressbars + # have finished. If they have, we can exit the loop + for bar_ in self.values(): + if not bar_.finished(): + break + else: + # Render one last time to make sure the progressbars are + # correctly finished + self.render(force=True) + return + def start(self): + assert not self._thread, 'Multibar already started' + self._thread_closed.set() + self._thread = threading.Thread(target=self.run, args=(False,)) + self._thread.start() -class LineOffsetStreamWrapper: - UP = '\033[F' - DOWN = '\033[B' + def join(self, timeout=None): + if self._thread is not None: + self._thread_closed.set() + self._thread.join(timeout=timeout) + self._thread = None - def __init__(self, lines=0, stream=sys.stderr): - self.stream = stream - self.lines = lines + def stop(self, timeout: float | None = None): + self._thread_finished.set() + self.join(timeout=timeout) - def write(self, data): - # Move the cursor up - self.stream.write(self.UP * self.lines) - # Print a carriage return to reset the cursor position - self.stream.write('\r') - # Print the data without newlines so we don't change the position - self.stream.write(data.rstrip('\n')) - # Move the cursor down - self.stream.write(self.DOWN * self.lines) + def get_sorted_bars(self): + return sorted( + self.values(), + key=self.sort_keyfunc, + reverse=self.sort_reverse, + ) - self.stream.flush() + def __enter__(self): + self.start() + return self - def __getattr__(self, name): - return getattr(self.stream, name) + def __exit__(self, exc_type, exc_val, exc_tb): + self.join() diff --git a/progressbar/terminal/__init__.py b/progressbar/terminal/__init__.py index e69de29b..4b40b38c 100644 --- a/progressbar/terminal/__init__.py +++ b/progressbar/terminal/__init__.py @@ -0,0 +1 @@ +from .base import * # noqa diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 77373794..95c46307 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -12,9 +12,126 @@ from .os_specific import getch ESC = '\x1B' -CSI = ESC + '[' -CUP = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [1,1]) + +class CSI: + _code: str + _template = ESC + '[{args}{code}' + + def __init__(self, code, *default_args): + self._code = code + self._default_args = default_args + + def __call__(self, *args): + return self._template.format( + args=';'.join(map(str, args or self._default_args)), + code=self._code + ) + + def __str__(self): + return self() + + +class CSINoArg(CSI): + + def __call__(self): + return super().__call__() + + +#: Cursor Position [row;column] (default = [1,1]) +CUP = CSI('H', 1, 1) + +#: Cursor Up Ps Times (default = 1) (CUU) +UP = CSI('A', 1) + +#: Cursor Down Ps Times (default = 1) (CUD) +DOWN = CSI('B', 1) + +#: Cursor Forward Ps Times (default = 1) (CUF) +RIGHT = CSI('C', 1) + +#: Cursor Backward Ps Times (default = 1) (CUB) +LEFT = CSI('D', 1) + +#: Cursor Next Line Ps Times (default = 1) (CNL) +#: Same as Cursor Down Ps Times +NEXT_LINE = CSI('E', 1) + +#: Cursor Preceding Line Ps Times (default = 1) (CPL) +#: Same as Cursor Up Ps Times +PREVIOUS_LINE = CSI('F', 1) + +#: Cursor Character Absolute [column] (default = [row,1]) (CHA) +COLUMN = CSI('G', 1) + +#: Erase in Display (ED) +CLEAR_SCREEN = CSI('J', 0) + +#: Erase till end of screen +CLEAR_SCREEN_TILL_END = CSINoArg('0J') + +#: Erase till start of screen +CLEAR_SCREEN_TILL_START = CSINoArg('1J') + +#: Erase whole screen +CLEAR_SCREEN_ALL = CSINoArg('2J') + +#: Erase whole screen and history +CLEAR_SCREEN_ALL_AND_HISTORY = CSINoArg('3J') + +#: Erase in Line (EL) +CLEAR_LINE_ALL = CSI('K') + +#: Erase in Line from Cursor to End of Line (default) +CLEAR_LINE_RIGHT = CSINoArg('0K') + +#: Erase in Line from Cursor to Beginning of Line +CLEAR_LINE_LEFT = CSINoArg('1K') + +#: Erase Line containing Cursor +CLEAR_LINE = CSINoArg('2K') + +#: Scroll up Ps lines (default = 1) (SU) +#: Scroll down Ps lines (default = 1) (SD) +SCROLL_UP = CSI('S') +SCROLL_DOWN = CSI('T') + +#: Save Cursor Position (SCP) +SAVE_CURSOR = CSINoArg('s') + +#: Restore Cursor Position (RCP) +RESTORE_CURSOR = CSINoArg('u') + +#: Cursor Visibility (DECTCEM) +HIDE_CURSOR = CSINoArg('?25l') +SHOW_CURSOR = CSINoArg('?25h') + + +# +# UP = CSI + '{n}A' # Cursor Up +# DOWN = CSI + '{n}B' # Cursor Down +# RIGHT = CSI + '{n}C' # Cursor Forward +# LEFT = CSI + '{n}D' # Cursor Backward +# NEXT = CSI + '{n}E' # Cursor Next Line +# PREV = CSI + '{n}F' # Cursor Previous Line +# MOVE_COLUMN = CSI + '{n}G' # Cursor Horizontal Absolute +# MOVE = CSI + '{row};{column}H' # Cursor Position [row;column] (default = [ +# 1,1]) +# +# CLEAR = CSI + '{n}J' # Clear (part of) the screen +# CLEAR_BOTTOM = CLEAR.format(n=0) # Clear from cursor to end of screen +# CLEAR_TOP = CLEAR.format(n=1) # Clear from cursor to beginning of screen +# CLEAR_SCREEN = CLEAR.format(n=2) # Clear Screen +# CLEAR_WIPE = CLEAR.format(n=3) # Clear Screen and scrollback buffer +# +# CLEAR_LINE = CSI + '{n}K' # Erase in Line +# CLEAR_LINE_RIGHT = CLEAR_LINE.format(n=0) # Clear from cursor to end of line +# CLEAR_LINE_LEFT = CLEAR_LINE.format(n=1) # Clear from cursor to beginning +# of line +# CLEAR_LINE_ALL = CLEAR_LINE.format(n=2) # Clear Line + +def clear_line(n): + return UP(n) + CLEAR_LINE_ALL() + DOWN(n) class ColorSupport(enum.Enum): @@ -75,96 +192,6 @@ def column(self, stream): return column -DSR = CSI + '{n}n' # Device Status Report (DSR) -CPR = _CPR(DSR.format(n=6)) - -IL = CSI + '{n}L' # Insert n Line(s) (default = 1) - -DECRST = CSI + '?{n}l' # DEC Private Mode Reset -DECRTCEM = DECRST.format(n=25) # Hide Cursor - -DECSET = CSI + '?{n}h' # DEC Private Mode Set -DECTCEM = DECSET.format(n=25) # Show Cursor - - -# possible values: -# 0 = Normal (default) -# 1 = Bold -# 2 = Faint -# 3 = Italic -# 4 = Underlined -# 5 = Slow blink (appears as Bold) -# 6 = Rapid Blink -# 7 = Inverse -# 8 = Invisible, i.e., hidden (VT300) -# 9 = Strike through -# 10 = Primary (default) font -# 20 = Gothic Font -# 21 = Double underline -# 22 = Normal intensity (neither bold nor faint) -# 23 = Not italic -# 24 = Not underlined -# 25 = Steady (not blinking) -# 26 = Proportional spacing -# 27 = Not inverse -# 28 = Visible, i.e., not hidden (VT300) -# 29 = No strike through -# 30 = Set foreground color to Black -# 31 = Set foreground color to Red -# 32 = Set foreground color to Green -# 33 = Set foreground color to Yellow -# 34 = Set foreground color to Blue -# 35 = Set foreground color to Magenta -# 36 = Set foreground color to Cyan -# 37 = Set foreground color to White -# 39 = Set foreground color to default (original) -# 40 = Set background color to Black -# 41 = Set background color to Red -# 42 = Set background color to Green -# 43 = Set background color to Yellow -# 44 = Set background color to Blue -# 45 = Set background color to Magenta -# 46 = Set background color to Cyan -# 47 = Set background color to White -# 49 = Set background color to default (original). -# 50 = Disable proportional spacing -# 51 = Framed -# 52 = Encircled -# 53 = Overlined -# 54 = Neither framed nor encircled -# 55 = Not overlined -# 58 = Set underine color (2;r;g;b) -# 59 = Default underline color -# If 16-color support is compiled, the following apply. -# Assume that xterm’s resources are set so that the ISO color codes are the -# first 8 of a set of 16. Then the aixterm colors are the bright versions of -# the ISO colors: -# 90 = Set foreground color to Black -# 91 = Set foreground color to Red -# 92 = Set foreground color to Green -# 93 = Set foreground color to Yellow -# 94 = Set foreground color to Blue -# 95 = Set foreground color to Magenta -# 96 = Set foreground color to Cyan -# 97 = Set foreground color to White -# 100 = Set background color to Black -# 101 = Set background color to Red -# 102 = Set background color to Green -# 103 = Set background color to Yellow -# 104 = Set background color to Blue -# 105 = Set background color to Magenta -# 106 = Set background color to Cyan -# 107 = Set background color to White -# -# If xterm is compiled with the 16-color support disabled, it supports the -# following, from rxvt: -# 100 = Set foreground and background color to default - -# If 88- or 256-color support is compiled, the following apply. -# 38;5;x = Set foreground color to x -# 48;5;x = Set background color to x - - class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): __slots__ = () @@ -299,10 +326,10 @@ def register( return color -class SGR: +class SGR(CSI): _start_code: int _end_code: int - _template = CSI + '{n}m' + _code = 'm' __slots__ = '_start_code', '_end_code' def __init__(self, start_code: int, end_code: int): @@ -311,11 +338,11 @@ def __init__(self, start_code: int, end_code: int): @property def _start_template(self): - return self._template.format(n=self._start_code) + return super().__call__(self._start_code) @property def _end_template(self): - return self._template.format(n=self._end_code) + return super().__call__(self._end_code) def __call__(self, text): return self._start_template + text + self._end_template @@ -323,7 +350,6 @@ def __call__(self, text): class SGRColor(SGR): __slots__ = '_color', '_start_code', '_end_code' - _color_template = CSI + '{n};{color}m' def __init__(self, color: Color, start_code: int, end_code: int): self._color = color @@ -331,10 +357,7 @@ def __init__(self, color: Color, start_code: int, end_code: int): @property def _start_template(self): - return self._color_template.format( - n=self._start_code, - color=self._color.ansi - ) + return CSI.__call__(self, self._start_code, self._color.ansi) encircled = SGR(52, 54) diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py new file mode 100644 index 00000000..c0ccff4c --- /dev/null +++ b/progressbar/terminal/stream.py @@ -0,0 +1,129 @@ +from __future__ import annotations + +import sys +from types import TracebackType +from typing import Iterable, Iterator, Type + +from progressbar import base + + +class TextIOOutputWrapper(base.TextIO): + + def __init__(self, stream: base.TextIO): + self.stream = stream + + def close(self) -> None: + self.stream.close() + + def fileno(self) -> int: + return self.stream.fileno() + + def flush(self) -> None: + pass + + def isatty(self) -> bool: + return self.stream.isatty() + + def read(self, __n: int = -1) -> str: + return self.stream.read(__n) + + def readable(self) -> bool: + return self.stream.readable() + + def readline(self, __limit: int = -1) -> str: + return self.stream.readline(__limit) + + def readlines(self, __hint: int = ...) -> list[str]: + return self.stream.readlines(__hint) + + def seek(self, __offset: int, __whence: int = ...) -> int: + return self.stream.seek(__offset, __whence) + + def seekable(self) -> bool: + return self.stream.seekable() + + def tell(self) -> int: + return self.stream.tell() + + def truncate(self, __size: int | None = ...) -> int: + return self.stream.truncate(__size) + + def writable(self) -> bool: + return self.stream.writable() + + def writelines(self, __lines: Iterable[str]) -> None: + return self.stream.writelines(__lines) + + def __next__(self) -> str: + return self.stream.__next__() + + def __iter__(self) -> Iterator[str]: + return self.stream.__iter__() + + def __exit__( + self, + __t: Type[BaseException] | None, + __value: BaseException | None, + __traceback: TracebackType | None + ) -> None: + return self.stream.__exit__(__t, __value, __traceback) + + def __enter__(self) -> base.TextIO: + return self.stream.__enter__() + + +class LineOffsetStreamWrapper(TextIOOutputWrapper): + UP = '\033[F' + DOWN = '\033[B' + + def __init__(self, lines=0, stream=sys.stderr): + self.lines = lines + super().__init__(stream) + + def write(self, data): + # Move the cursor up + self.stream.write(self.UP * self.lines) + # Print a carriage return to reset the cursor position + self.stream.write('\r') + # Print the data without newlines so we don't change the position + self.stream.write(data.rstrip('\n')) + # Move the cursor down + self.stream.write(self.DOWN * self.lines) + + self.flush() + + +class LastLineStream(TextIOOutputWrapper): + + line: str = '' + + def seekable(self) -> bool: + return False + + def readable(self) -> bool: + return True + + def read(self, __n: int = -1) -> str: + return self.line[:__n] + + def readline(self, __limit: int = -1) -> str: + return self.line[:__limit] + + def write(self, data): + self.line = data + + def truncate(self, __size: int | None = None) -> int: + if __size is None: + self.line = '' + else: + self.line = self.line[:__size] + + return len(self.line) + + def writelines(self, __lines: Iterable[str]) -> None: + line = '' + # Walk through the lines and take the last one + for line in __lines: + pass + + self.line = line From 874c2f2678afda1dfee9f14c015b59a04dcec346 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 23 Jan 2023 15:40:55 +0100 Subject: [PATCH 15/24] added example for new multithreaded parallel progressbars --- README.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.rst b/README.rst index 26695e96..6c01f2a8 100644 --- a/README.rst +++ b/README.rst @@ -152,6 +152,38 @@ In most cases the following will work as well, as long as you initialize the logging.error('Got %d', i) time.sleep(0.2) +Multiple (threaded) progressbars +============================================================================== + +.. code:: python + + import random + import threading + import time + + import progressbar + + BARS = 5 + N = 50 + + + def do_something(bar): + for i in bar(range(N)): + # Sleep up to 0.1 seconds + time.sleep(random.random() * 0.1) + + # print messages at random intervals to show how extra output works + if random.random() > 0.9: + bar.print('random message for bar', bar, i) + + + with progressbar.MultiBar() as multibar: + for i in range(BARS): + # Get a progressbar + bar = multibar[f'Thread label here {i}'] + # Create a thread and pass the progressbar + threading.Thread(target=do_something, args=(bar,)).start() + Context wrapper ============================================================================== .. code:: python From 8d25f44c55ba61c13ef048eb4f92b88f2f0d2b39 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 28 Jan 2023 03:25:10 +0100 Subject: [PATCH 16/24] Adding a splash of colour --- progressbar/bar.py | 39 +++++-- progressbar/terminal/base.py | 180 ++++++++++++++++++++++++++++++--- progressbar/terminal/colors.py | 40 +++++++- progressbar/widgets.py | 60 +++++++++-- tox.ini | 7 +- 5 files changed, 291 insertions(+), 35 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index b2826652..126dff97 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -18,7 +18,7 @@ import progressbar.terminal.stream from . import ( base, - utils, + terminal, utils, widgets, widgets as widgets_module, # Avoid name collision ) @@ -152,15 +152,23 @@ class DefaultFdMixin(ProgressBarMixinBase): #: Whether to print line breaks. This is useful for logging the #: progressbar. When disabled the current line is overwritten. line_breaks: bool = True - #: Enable or disable colors. Defaults to auto detection - enable_colors: bool = False + # : Specify the type and number of colors to support. Defaults to auto + # detection based on the file descriptor type (i.e. interactive terminal) + # environment variables such as `COLORTERM` and `TERM`. Color output can + # be forced in non-interactive terminals using the + # `PROGRESSBAR_ENABLE_COLORS` environment variable which can also be used + # to force a specific number of colors by specifying `24bit`, `256` or `16`. + # For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. + # For 256 color support you can use `TERM=xterm-256color`. + # For 16 colorsupport you can use `TERM=xterm`. + enable_colors: terminal.ColorSupport | bool | None = terminal.color_support def __init__( self, fd: base.IO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, - enable_colors: bool | None = None, + enable_colors: terminal.ColorSupport | None = None, line_offset: int = 0, **kwargs, ): @@ -195,11 +203,28 @@ def __init__( # Check if ANSI escape characters are enabled (suitable for iteractive # terminals), or should be stripped off (suitable for log files) if enable_colors is None: - enable_colors = utils.env_flag( - 'PROGRESSBAR_ENABLE_COLORS', self.is_ansi_terminal + colors = ( + utils.env_flag('PROGRESSBAR_ENABLE_COLORS'), + utils.env_flag('FORCE_COLOR'), + self.is_ansi_terminal, ) - self.enable_colors = bool(enable_colors) + for color_enabled in colors: + if color_enabled is not None: + if color_enabled: + enable_colors = terminal.color_support + else: + enable_colors = terminal.ColorSupport.NONE + break + + elif enable_colors is True: + enable_colors = terminal.color_support + elif enable_colors is False: + enable_colors = terminal.ColorSupport.NONE + elif enable_colors not in terminal.ColorSupport: + raise ValueError(f'Invalid color support value: {enable_colors}') + + self.enable_colors = enable_colors ProgressBarMixinBase.__init__(self, **kwargs) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 95c46307..dacf404c 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -1,5 +1,6 @@ from __future__ import annotations +import abc import collections import colorsys import enum @@ -7,7 +8,7 @@ import threading from collections import defaultdict -from python_utils import types +from python_utils import converters, types from .os_specific import getch @@ -134,24 +135,54 @@ def clear_line(n): return UP(n) + CLEAR_LINE_ALL() + DOWN(n) -class ColorSupport(enum.Enum): +class ColorSupport(enum.IntEnum): '''Color support for the terminal.''' NONE = 0 - XTERM = 1 - XTERM_256 = 2 - XTERM_TRUECOLOR = 3 + XTERM = 16 + XTERM_256 = 256 + XTERM_TRUECOLOR = 16777216 @classmethod def from_env(cls): - '''Get the color support from the environment.''' - if os.getenv('COLORTERM') == 'truecolor': + '''Get the color support from the environment. + + If any of the environment variables contain `24bit` or `truecolor`, + we will enable true color/24 bit support. If they contain `256`, we + will enable 256 color/8 bit support. If they contain `xterm`, we will + enable 16 color support. Otherwise, we will assume no color support. + + If `JUPYTER_COLUMNS` or `JUPYTER_LINES` is set, we will assume true + color support. + + Note that the highest available value will be used! Having + `COLORTERM=truecolor` will override `TERM=xterm-256color`. + ''' + variables = ( + 'FORCE_COLOR', + 'PROGRESSBAR_ENABLE_COLORS', + 'COLORTERM', + 'TERM', + ) + + if os.environ.get('JUPYTER_COLUMNS') or os.environ.get('JUPYTER_LINES'): + # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR - elif os.getenv('TERM') == 'xterm-256color': - return cls.XTERM_256 - elif os.getenv('TERM') == 'xterm': - return cls.XTERM - else: - return cls.NONE + + support = cls.NONE + for variable in variables: + value = os.environ.get(variable) + if value is None: + continue + elif value in {'truecolor', '24bit'}: + # Truecolor support, we don't need to check anything else. + support = cls.XTERM_TRUECOLOR + break + elif '256' in value: + support = max(cls.XTERM_256, support) + elif value == 'xterm': + support = max(cls.XTERM, support) + + return support color_support = ColorSupport.from_env() @@ -196,6 +227,10 @@ class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): __slots__ = () def __str__(self): + return self.rgb + + @property + def rgb(self): return f'rgb({self.red}, {self.green}, {self.blue})' @property @@ -217,6 +252,13 @@ def to_ansi_256(self): blue = round(self.blue / 255 * 5) return 16 + 36 * red + 6 * green + blue + def interpolate(self, end: RGB, step: float) -> RGB: + return RGB( + int(self.red + (end.red - self.red) * step), + int(self.green + (end.green - self.green) * step), + int(self.blue + (end.blue - self.blue) * step), + ) + class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): __slots__ = () @@ -227,6 +269,18 @@ def from_rgb(cls, rgb: RGB) -> HLS: *colorsys.rgb_to_hls(rgb.red / 255, rgb.green / 255, rgb.blue / 255) ) + def interpolate(self, end: HLS, step: float) -> HLS: + return HLS( + self.hue + (end.hue - self.hue) * step, + self.lightness + (end.lightness - self.lightness) * step, + self.saturation + (end.saturation - self.saturation) * step, + ) + + +class ColorBase(abc.ABC): + + def get_color(self, value: float) -> Color: + raise NotImplementedError() class Color( collections.namedtuple( @@ -236,7 +290,8 @@ class Color( 'name', 'xterm', ] - ) + ), + ColorBase, ): ''' Color base class @@ -251,6 +306,9 @@ class Color( ''' __slots__ = () + def __call__(self, value: str) -> str: + return self.fg(value) + @property def fg(self): return SGRColor(self, 38, 39) @@ -279,6 +337,14 @@ def ansi(self) -> types.Optional[str]: return f'5;{color}' + def interpolate(self, end: Color, step: float) -> Color: + return Color( + self.rgb.interpolate(end.rgb, step), + self.hls.interpolate(end.hls, step), + self.name if step < 0.5 else end.name, + self.xterm if step < 0.5 else end.xterm, + ) + def __str__(self): return self.name @@ -325,6 +391,92 @@ def register( return color + @classmethod + def interpolate(cls, color_a: Color, color_b: Color, step: float) -> Color: + return color_a.interpolate(color_b, step) + + +class ColorGradient(ColorBase): + def __init__( + self, + *colors: Color, + interpolate=Colors.interpolate + ): + assert colors + self.colors = colors + self.interpolate = interpolate + + def __call__(self, value: float): + return self.get_color(value) + + def get_color(self, value: float) -> Color: + 'Map a value from 0 to 1 to a color' + if value <= 0: + return self.colors[0] + elif value >= 1: + return self.colors[-1] + + max_color_idx = len(self.colors) - 1 + if max_color_idx == 0: + return self.colors[0] + elif self.interpolate: + index = round(converters.remap(value, 0, 1, 0, max_color_idx - 1)) + step = converters.remap( + value, + index / (max_color_idx), + (index + 1) / (max_color_idx), + 0, + 1, + ) + color = self.interpolate( + self.colors[index], + self.colors[index + 1], + float(step), + ) + else: + index = round(converters.remap(value, 0, 1, 0, max_color_idx)) + color = self.colors[index] + + return color + + +OptionalColor = Color | ColorGradient | None + + +def get_color(value: float, color: OptionalColor) -> Color | None: + if isinstance(color, ColorGradient): + color = color(value) + return color + + +def apply_colors( + text: str, + value: float | None = None, + *, + fg: OptionalColor = None, + bg: OptionalColor = None, + fg_none: Color | None = None, + bg_none: Color | None = None, +) -> str: + if fg is None and bg is None: + return text + + if value is None: + if fg_none is not None: + text = fg_none.fg(text) + if bg_none is not None: + text = bg_none.bg(text) + else: + fg = get_color(value, fg) + bg = get_color(value, bg) + + if fg is not None: + text = fg.fg(text) + if bg is not None: + text = bg.bg(text) + + return text + class SGR(CSI): _start_code: int diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index ed726aea..dacbac28 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -1,6 +1,7 @@ # Based on: https://www.ditig.com/256-colors-cheat-sheet -from progressbar.terminal import base -from progressbar.terminal.base import Colors, HLS, RGB +import os + +from progressbar.terminal.base import ColorGradient, Colors, HLS, RGB black = Colors.register(RGB(0, 0, 0), HLS(0, 0, 0), 'Black', 0) maroon = Colors.register(RGB(128, 0, 0), HLS(100, 0, 25), 'Maroon', 1) @@ -939,9 +940,44 @@ grey89 = Colors.register(RGB(228, 228, 228), HLS(0, 0, 89), 'Grey89', 254) grey93 = Colors.register(RGB(238, 238, 238), HLS(0, 0, 93), 'Grey93', 255) +dark_gradient = ColorGradient( + red1, + orangeRed1, + darkOrange, + orange1, + yellow1, + yellow2, + greenYellow, + green1, +) +light_gradient = ColorGradient( + red1, + orangeRed1, + darkOrange, + orange1, + gold3, + darkOliveGreen3, + yellow4, + green3, +) +bg_gradient = ColorGradient(black) + +# Check if the background is light or dark. This is by no means a foolproof +# method, but there is no reliable way to detect this. +if os.environ.get('COLORFGBG', '15;0').split(';')[-1] == str(white.xterm): + print('light background') + # Light background + gradient = light_gradient +else: + print('dark background') + # Default, expect a dark background + gradient = dark_gradient + if __name__ == '__main__': red = Colors.register(RGB(255, 128, 128)) # red = Colors.register(RGB(255, 100, 100)) + from progressbar.terminal import base + for i in base.ColorSupport: base.color_support = i print(i, red.fg('RED!'), red.bg('RED!'), red.underline('RED!')) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index b13d9f33..c4897e17 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -10,7 +10,8 @@ from python_utils import converters, types -from . import base, utils +from . import base, terminal, utils +from .terminal import colors if types.TYPE_CHECKING: from .bar import ProgressBarMixinBase @@ -353,8 +354,9 @@ def __init__( ): self.samples = samples self.key_prefix = ( - key_prefix if key_prefix else self.__class__.__name__ - ) + '_' + key_prefix if key_prefix else + self.__class__.__name__ + ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): @@ -671,7 +673,6 @@ class AnimatedMarker(TimeSensitiveWidgetBase): '''An animated marker for the progress bar which defaults to appear as if it were rotating. ''' - def __init__( self, markers='|/-\\', @@ -739,6 +740,10 @@ def __call__( class Percentage(FormatWidgetMixin, WidgetBase): '''Displays the current percentage as a number with a percent sign.''' + fg_na: terminal.Color | None = colors.yellow + fg_value: terminal.OptionalColor | None = colors.gradient + bg_na: terminal.Color | None = colors.black + bg_value: terminal.OptionalColor | None = None def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): self.na = na @@ -751,13 +756,28 @@ def get_format( # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: - return self.na - - return FormatWidgetMixin.get_format(self, progress, data, format) + output = self.na + value = None + else: + value = percentage / 100 + output = FormatWidgetMixin.get_format(self, progress, data, format) + + return terminal.apply_colors( + output, + value, + fg=self.fg_value, + bg=self.bg_value, + fg_none=self.fg_na, + bg_none=self.bg_na, + ) class SimpleProgress(FormatWidgetMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' + fg_na: terminal.Color | None = colors.yellow + fg_value: terminal.OptionalColor | None = colors.gradient + bg_na: terminal.Color | None = colors.black + bg_value: terminal.OptionalColor | None = None max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], @@ -777,9 +797,11 @@ def __call__( ): # If max_value is not available, display N/A if data.get('max_value'): - data['max_value_s'] = data.get('max_value') + data['max_value_s'] = data['max_value'] + value = data.get('value', 0) / data['max_value'] else: data['max_value_s'] = 'N/A' + value = None # if value is not available it's the zeroth iteration if data.get('value'): @@ -817,11 +839,20 @@ def __call__( if max_width: # pragma: no branch formatted = formatted.rjust(max_width) - return formatted + return terminal.apply_colors( + formatted, + value, + fg=self.fg_value, + bg=self.bg_value, + fg_none=self.fg_na, + bg_none=self.bg_na, + ) class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' + fg_value: terminal.OptionalColor | None = colors.gradient + bg_value: terminal.OptionalColor | None = None def __init__( self, @@ -869,11 +900,22 @@ def __call__( # Make sure we ignore invisible characters when filling width += len(marker) - progress.custom_len(marker) + if marker and width > 0: + value = len(marker) / width + else: + value = 0 + if self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) + marker = terminal.apply_colors( + marker, + value, + fg=self.fg_value, + bg=self.bg_value, + ) return left + marker + right diff --git a/tox.ini b/tox.ini index 99be8934..3472275b 100644 --- a/tox.ini +++ b/tox.ini @@ -39,9 +39,11 @@ deps = black commands = black --skip-string-normalization --line-length 79 {toxinidir}/progressbar [testenv:docs] -changedir = basepython = python3 deps = -r{toxinidir}/docs/requirements.txt +allowlist_externals = + rm + mkdir whitelist_externals = rm cd @@ -51,8 +53,7 @@ commands = mkdir -p docs/_static sphinx-apidoc -e -o docs/ progressbar rm -f docs/modules.rst - rm -f docs/progressbar.rst - sphinx-build -W -b html -d docs/_build/doctrees docs docs/_build/html {posargs} + sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html {posargs} [flake8] ignore = W391, W504, E741, W503, E131 From cf40aa0e4bfc419155aa4198bc387e1cb2cc9eb7 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 28 Jan 2023 03:31:21 +0100 Subject: [PATCH 17/24] fixed docs build --- tox.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/tox.ini b/tox.ini index 3472275b..3ffed050 100644 --- a/tox.ini +++ b/tox.ini @@ -39,6 +39,7 @@ deps = black commands = black --skip-string-normalization --line-length 79 {toxinidir}/progressbar [testenv:docs] +changedir = basepython = python3 deps = -r{toxinidir}/docs/requirements.txt allowlist_externals = From 2513c2745bf6aa4f0d509f01f376ba15f471f045 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 28 Jan 2023 22:23:03 +0100 Subject: [PATCH 18/24] Improved colour support --- progressbar/bar.py | 2 +- progressbar/terminal/base.py | 11 ++++--- progressbar/terminal/colors.py | 4 +-- progressbar/widgets.py | 60 ++++++++++++++++++++-------------- 4 files changed, 44 insertions(+), 33 deletions(-) diff --git a/progressbar/bar.py b/progressbar/bar.py index 126dff97..e1aa2bc6 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -610,7 +610,7 @@ def init(self): self._last_update_timer = timeit.default_timer() @property - def percentage(self): + def percentage(self) -> float | None: '''Return current percentage, returns None if no max_value is given >>> progress = ProgressBar() diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index dacf404c..afa5c8dd 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -11,6 +11,7 @@ from python_utils import converters, types from .os_specific import getch +from .. import base ESC = '\x1B' @@ -411,7 +412,7 @@ def __call__(self, value: float): def get_color(self, value: float) -> Color: 'Map a value from 0 to 1 to a color' - if value <= 0: + if value is base.Undefined or value is base.UnknownLength or value <= 0: return self.colors[0] elif value >= 1: return self.colors[-1] @@ -451,7 +452,7 @@ def get_color(value: float, color: OptionalColor) -> Color | None: def apply_colors( text: str, - value: float | None = None, + percentage: float | None = None, *, fg: OptionalColor = None, bg: OptionalColor = None, @@ -461,14 +462,14 @@ def apply_colors( if fg is None and bg is None: return text - if value is None: + if percentage is None: if fg_none is not None: text = fg_none.fg(text) if bg_none is not None: text = bg_none.bg(text) else: - fg = get_color(value, fg) - bg = get_color(value, bg) + fg = get_color(percentage * 0.01, fg) + bg = get_color(percentage * 0.01, bg) if fg is not None: text = fg.fg(text) diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index dacbac28..5024a3bc 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -965,13 +965,13 @@ # Check if the background is light or dark. This is by no means a foolproof # method, but there is no reliable way to detect this. if os.environ.get('COLORFGBG', '15;0').split(';')[-1] == str(white.xterm): - print('light background') # Light background gradient = light_gradient + primary = black else: - print('dark background') # Default, expect a dark background gradient = dark_gradient + primary = white if __name__ == '__main__': red = Colors.register(RGB(255, 128, 128)) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index c4897e17..5227744c 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -46,7 +46,7 @@ def create_wrapper(wrapper): >>> print(create_wrapper(('a', 'b'))) a{}b ''' - if isinstance(wrapper, tuple) and len(wrapper) == 2: + if isinstance(wrapper, tuple) and utils.len_color(wrapper) == 2: a, b = wrapper wrapper = (a or '') + '{}' + (b or '') elif not wrapper: @@ -392,7 +392,7 @@ def __call__( sample_times.pop(0) sample_values.pop(0) else: - if len(sample_times) > self.samples: + if progress.custom_len(sample_times) > self.samples: sample_times.pop(0) sample_values.pop(0) @@ -673,6 +673,7 @@ class AnimatedMarker(TimeSensitiveWidgetBase): '''An animated marker for the progress bar which defaults to appear as if it were rotating. ''' + def __init__( self, markers='|/-\\', @@ -696,7 +697,7 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): if progress.end_time: return self.default - marker = self.markers[data['updates'] % len(self.markers)] + marker = self.markers[data['updates'] % utils.len_color(self.markers)] if self.marker_wrap: marker = self.marker_wrap.format(marker) @@ -745,6 +746,9 @@ class Percentage(FormatWidgetMixin, WidgetBase): bg_na: terminal.Color | None = colors.black bg_value: terminal.OptionalColor | None = None + def _uses_colors(self): + return self.fg_na or self.fg_value or self.bg_na or self.bg_value + def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): self.na = na FormatWidgetMixin.__init__(self, format=format, **kwargs) @@ -757,14 +761,12 @@ def get_format( percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: output = self.na - value = None else: - value = percentage / 100 output = FormatWidgetMixin.get_format(self, progress, data, format) return terminal.apply_colors( output, - value, + data.get('percentage'), fg=self.fg_value, bg=self.bg_value, fg_none=self.fg_na, @@ -798,10 +800,8 @@ def __call__( # If max_value is not available, display N/A if data.get('max_value'): data['max_value_s'] = data['max_value'] - value = data.get('value', 0) / data['max_value'] else: data['max_value_s'] = 'N/A' - value = None # if value is not available it's the zeroth iteration if data.get('value'): @@ -841,7 +841,7 @@ def __call__( return terminal.apply_colors( formatted, - value, + data.get('percentage'), fg=self.fg_value, bg=self.bg_value, fg_none=self.fg_na, @@ -898,25 +898,28 @@ def __call__( fill = converters.to_unicode(self.fill(progress, data, width)) # Make sure we ignore invisible characters when filling - width += len(marker) - progress.custom_len(marker) - - if marker and width > 0: - value = len(marker) / width - else: - value = 0 + width += utils.len_color(marker) - progress.custom_len(marker) if self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) - marker = terminal.apply_colors( - marker, - value, + marker = self.apply_colors(progress, data, marker) + return left + marker + right + + def apply_colors( + self, + progress: ProgressBarMixinBase, + data: Data, output: str + ): + output = terminal.apply_colors( + output, + percentage=data.get('percentage'), fg=self.fg_value, bg=self.bg_value, ) - return left + marker + right + return output class ReverseBar(Bar): @@ -1023,7 +1026,7 @@ class VariableMixin: def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError('Variable(): argument must be a string') - if len(name.split()) > 1: + if utils.len_color(name.split()) > 1: raise ValueError('Variable(): argument must be single word') self.name = name @@ -1103,7 +1106,7 @@ def __init__( ) def get_values(self, progress: ProgressBarMixinBase, data: Data): - ranges = [0.0] * len(self.markers) + ranges = [0.0] * progress.custom_len(self.markers) for value in data['variables'][self.name] or []: if not isinstance(value, (int, float)): # Progress is (value, max) @@ -1116,7 +1119,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): % value ) - range_ = value * (len(ranges) - 1) + range_ = value * (progress.custom_len(ranges) - 1) pos = int(range_) frac = range_ % 1 ranges[pos] += 1 - frac @@ -1200,14 +1203,16 @@ def __call__( marker = self.markers[-1] * int(num_chars) - marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) + marker_idx = int( + (num_chars % 1) * (progress.custom_len(self.markers) - 1) + ) if marker_idx: marker += self.markers[marker_idx] marker = converters.to_unicode(marker) # Make sure we ignore invisible characters when filling - width += len(marker) - progress.custom_len(marker) + width += progress.custom_len(marker) - progress.custom_len(marker) marker = marker.ljust(width, self.markers[0]) return left + marker + right @@ -1234,7 +1239,12 @@ def __call__( # type: ignore center_len = progress.custom_len(center) center_left = int((width - center_len) / 2) center_right = center_left + center_len - return bar[:center_left] + center + bar[center_right:] + + return bar[:center_left] + center + self.apply_colors( + progress, + data, + bar[center_right:] + ) class PercentageLabelBar(Percentage, FormatLabelBar): From 3d2e9a6cae0d20b81a836a09904ea9cc76a983af Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sun, 29 Jan 2023 15:57:56 +0100 Subject: [PATCH 19/24] Updated readme to add new PyCharm details --- README.rst | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 6c01f2a8..d76756c1 100644 --- a/README.rst +++ b/README.rst @@ -75,10 +75,8 @@ automatically enable features like auto-resizing when the system supports it. Known issues ****************************************************************************** -Due to limitations in both the IDLE shell and the Jetbrains (Pycharm) shells this progressbar cannot function properly within those. - +- The Jetbrains (PyCharm, etc) editors work out of the box, but for more advanced features such as the `MultiBar` support you will need to enable the "Enable terminal in output console" checkbox in the Run dialog. - The IDLE editor doesn't support these types of progress bars at all: https://bugs.python.org/issue23220 -- The Jetbrains (Pycharm) editors partially work but break with fast output. As a workaround make sure you only write to either `sys.stdout` (regular print) or `sys.stderr` at the same time. If you do plan to use both, make sure you wait about ~200 milliseconds for the next output or it will break regularly. Linked issue: https://github.com/WoLpH/python-progressbar/issues/115 - Jupyter notebooks buffer `sys.stdout` which can cause mixed output. This issue can be resolved easily using: `import sys; sys.stdout.flush()`. Linked issue: https://github.com/WoLpH/python-progressbar/issues/173 ****************************************************************************** From 4fbf6582576949d5fdd5a8de23c0b7f02cc8af6b Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Thu, 2 Feb 2023 09:22:04 +0100 Subject: [PATCH 20/24] Fixed several tests and a few bugs --- progressbar/__init__.py | 3 +- progressbar/bar.py | 13 ++-- progressbar/base.py | 2 +- progressbar/multi.py | 11 +-- progressbar/terminal/base.py | 1 + progressbar/utils.py | 4 +- progressbar/widgets.py | 145 ++++++++++++++++++++--------------- tests/test_color.py | 39 ++++++++++ tests/test_flush.py | 1 + tests/test_multibar.py | 84 ++++++++++++++++++++ tests/test_progressbar.py | 3 + 11 files changed, 231 insertions(+), 75 deletions(-) create mode 100644 tests/test_color.py diff --git a/progressbar/__init__.py b/progressbar/__init__.py index 117124c2..e43c4cf6 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -35,8 +35,8 @@ from .widgets import Timer from .widgets import Variable from .widgets import VariableMixin -from .multi import MultiBar from .terminal.stream import LineOffsetStreamWrapper +from .multi import SortKey, MultiBar __date__ = str(date.today()) __all__ = [ @@ -76,4 +76,5 @@ '__author__', '__version__', 'MultiBar', + 'SortKey', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index e1aa2bc6..9208333f 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -118,11 +118,11 @@ def __del__(self): def __getstate__(self): return self.__dict__ - def data(self) -> types.Dict[str, types.Any]: + def data(self) -> types.Dict[str, types.Any]: # pragma: no cover raise NotImplementedError() def started(self) -> bool: - return self._started or self._finished + return self._finished or self._started def finished(self) -> bool: return self._finished @@ -209,7 +209,7 @@ def __init__( self.is_ansi_terminal, ) - for color_enabled in colors: + for color_enabled in colors: # pragma: no branch if color_enabled is not None: if color_enabled: enable_colors = terminal.color_support @@ -218,10 +218,13 @@ def __init__( break elif enable_colors is True: - enable_colors = terminal.color_support + enable_colors = terminal.ColorSupport.XTERM_256 elif enable_colors is False: enable_colors = terminal.ColorSupport.NONE - elif enable_colors not in terminal.ColorSupport: + elif isinstance(enable_colors, terminal.ColorSupport): + # `enable_colors` is already a valid value + pass + else: raise ValueError(f'Invalid color support value: {enable_colors}') self.enable_colors = enable_colors diff --git a/progressbar/base.py b/progressbar/base.py index 32a95783..8e007914 100644 --- a/progressbar/base.py +++ b/progressbar/base.py @@ -23,7 +23,7 @@ class Undefined(metaclass=FalseMeta): try: # pragma: no cover IO = types.IO # type: ignore TextIO = types.TextIO # type: ignore -except AttributeError: +except AttributeError: # pragma: no cover from typing.io import IO, TextIO # type: ignore assert IO is not None diff --git a/progressbar/multi.py b/progressbar/multi.py index d4d13979..3e4a93de 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -124,7 +124,7 @@ def __init__( def __setitem__(self, key: str, value: bar.ProgressBar): '''Add a progressbar to the multibar''' - if value.label != key: + if value.label != key: # pragma: no branch value.label = key value.fd = stream.LastLineStream(self.fd) value.paused = True @@ -153,16 +153,16 @@ def __getitem__(self, item): return progress def _label_bar(self, bar: bar.ProgressBar): - if bar in self._labeled: + if bar in self._labeled: # pragma: no branch return assert bar.widgets, 'Cannot prepend label to empty progressbar' self._labeled.add(bar) - if self.prepend_label: + if self.prepend_label: # pragma: no branch bar.widgets.insert(0, self.label_format.format(label=bar.label)) - if self.append_label and bar not in self._labeled: + if self.append_label and bar not in self._labeled: # pragma: no branch bar.widgets.append(self.label_format.format(label=bar.label)) def render(self, flush: bool = True, force: bool = False): @@ -300,7 +300,8 @@ def run(self, join=True): time.sleep(self.update_interval) if join or self._thread_closed.is_set(): - # If the thread is closed, we need to check if force progressbars + # If the thread is closed, we need to check if force + # progressbars # have finished. If they have, we can exit the loop for bar_ in self.values(): if not bar_.finished(): diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index afa5c8dd..366373ee 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -458,6 +458,7 @@ def apply_colors( bg: OptionalColor = None, fg_none: Color | None = None, bg_none: Color | None = None, + **kwargs: types.Any, ) -> str: if fg is None and bg is None: return text diff --git a/progressbar/utils.py b/progressbar/utils.py index 7f84a91d..256dd98c 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -157,8 +157,10 @@ def no_color(value: StringT) -> StringT: if isinstance(value, bytes): pattern: bytes = '\\\u001b\\[.*?[@-~]'.encode() return re.sub(pattern, b'', value) # type: ignore - else: + elif isinstance(value, str): return re.sub(u'\x1b\\[.*?[@-~]', '', value) # type: ignore + else: + raise TypeError('`value` must be a string or bytes, got %r' % value) def len_color(value: types.StringTypes) -> int: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 5227744c..57264ed5 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -7,6 +7,7 @@ import pprint import sys import typing +from typing import Callable from python_utils import converters, types @@ -46,7 +47,7 @@ def create_wrapper(wrapper): >>> print(create_wrapper(('a', 'b'))) a{}b ''' - if isinstance(wrapper, tuple) and utils.len_color(wrapper) == 2: + if isinstance(wrapper, tuple) and len(wrapper) == 2: a, b = wrapper wrapper = (a or '') + '{}' + (b or '') elif not wrapper: @@ -223,6 +224,51 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: progress - a reference to the calling ProgressBar ''' + _fixed_colors: dict[str, terminal.Color | None] = dict() + _gradient_colors: dict[str, terminal.OptionalColor | None] = dict() + _len: Callable[[str | bytes], int] = len + + @functools.cached_property + def uses_colors(self): + for key, value in self._gradient_colors.items(): + if value is not None: + return True + + for key, value in self._fixed_colors.items(): + if value is not None: + return True + + return False + + def _apply_colors(self, text: str, data: Data) -> str: + if self.uses_colors: + return terminal.apply_colors( + text, + data.get('percentage'), + **self._gradient_colors, + **self._fixed_colors, + ) + else: + return text + + def __init__( + self, + *args, + fixed_colors=None, + gradient_colors=None, + **kwargs + ): + if fixed_colors is not None: + self._fixed_colors.update(fixed_colors) + + if gradient_colors is not None: + self._gradient_colors.update(gradient_colors) + + if self.uses_colors: + self._len = utils.len_color + + super().__init__(*args, **kwargs) + class AutoWidthWidgetBase(WidgetBase, metaclass=abc.ABCMeta): '''The base class for all variable width widgets. @@ -392,7 +438,7 @@ def __call__( sample_times.pop(0) sample_values.pop(0) else: - if progress.custom_len(sample_times) > self.samples: + if len(sample_times) > self.samples: sample_times.pop(0) sample_values.pop(0) @@ -697,7 +743,7 @@ def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): if progress.end_time: return self.default - marker = self.markers[data['updates'] % utils.len_color(self.markers)] + marker = self.markers[data['updates'] % len(self.markers)] if self.marker_wrap: marker = self.marker_wrap.format(marker) @@ -739,15 +785,19 @@ def __call__( return FormatWidgetMixin.__call__(self, progress, data, format) -class Percentage(FormatWidgetMixin, WidgetBase): - '''Displays the current percentage as a number with a percent sign.''' - fg_na: terminal.Color | None = colors.yellow - fg_value: terminal.OptionalColor | None = colors.gradient - bg_na: terminal.Color | None = colors.black - bg_value: terminal.OptionalColor | None = None +class ColoredMixin: + _fixed_colors: dict[str, terminal.Color | None] = dict( + fg_none=colors.yellow, + bg_none=None, + ) + _gradient_colors: dict[str, terminal.OptionalColor | None] = dict( + fg=colors.gradient, + bg=None, + ) - def _uses_colors(self): - return self.fg_na or self.fg_value or self.bg_na or self.bg_value + +class Percentage(FormatWidgetMixin, ColoredMixin, WidgetBase): + '''Displays the current percentage as a number with a percent sign.''' def __init__(self, format='%(percentage)3d%%', na='N/A%%', **kwargs): self.na = na @@ -764,23 +814,11 @@ def get_format( else: output = FormatWidgetMixin.get_format(self, progress, data, format) - return terminal.apply_colors( - output, - data.get('percentage'), - fg=self.fg_value, - bg=self.bg_value, - fg_none=self.fg_na, - bg_none=self.bg_na, - ) + return self._apply_colors(output, data) -class SimpleProgress(FormatWidgetMixin, WidgetBase): +class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' - fg_na: terminal.Color | None = colors.yellow - fg_value: terminal.OptionalColor | None = colors.gradient - bg_na: terminal.Color | None = colors.black - bg_value: terminal.OptionalColor | None = None - max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], types.Optional[int], @@ -839,20 +877,13 @@ def __call__( if max_width: # pragma: no branch formatted = formatted.rjust(max_width) - return terminal.apply_colors( - formatted, - data.get('percentage'), - fg=self.fg_value, - bg=self.bg_value, - fg_none=self.fg_na, - bg_none=self.bg_na, - ) + return self._apply_colors(formatted, data) class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' - fg_value: terminal.OptionalColor | None = colors.gradient - bg_value: terminal.OptionalColor | None = None + fg: terminal.OptionalColor | None = colors.gradient + bg: terminal.OptionalColor | None = None def __init__( self, @@ -888,6 +919,7 @@ def __call__( progress: ProgressBarMixinBase, data: Data, width: int = 0, + color=True, ): '''Updates the progress bar and its subcomponents''' @@ -898,28 +930,17 @@ def __call__( fill = converters.to_unicode(self.fill(progress, data, width)) # Make sure we ignore invisible characters when filling - width += utils.len_color(marker) - progress.custom_len(marker) + width += len(marker) - progress.custom_len(marker) if self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) - marker = self.apply_colors(progress, data, marker) - return left + marker + right + if color: + marker = self._apply_colors(marker, data) - def apply_colors( - self, - progress: ProgressBarMixinBase, - data: Data, output: str - ): - output = terminal.apply_colors( - output, - percentage=data.get('percentage'), - fg=self.fg_value, - bg=self.bg_value, - ) - return output + return left + marker + right class ReverseBar(Bar): @@ -1026,7 +1047,7 @@ class VariableMixin: def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError('Variable(): argument must be a string') - if utils.len_color(name.split()) > 1: + if len(name.split()) > 1: raise ValueError('Variable(): argument must be single word') self.name = name @@ -1106,7 +1127,7 @@ def __init__( ) def get_values(self, progress: ProgressBarMixinBase, data: Data): - ranges = [0.0] * progress.custom_len(self.markers) + ranges = [0.0] * len(self.markers) for value in data['variables'][self.name] or []: if not isinstance(value, (int, float)): # Progress is (value, max) @@ -1119,7 +1140,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): % value ) - range_ = value * (progress.custom_len(ranges) - 1) + range_ = value * (len(ranges) - 1) pos = int(range_) frac = range_ % 1 ranges[pos] += 1 - frac @@ -1203,16 +1224,14 @@ def __call__( marker = self.markers[-1] * int(num_chars) - marker_idx = int( - (num_chars % 1) * (progress.custom_len(self.markers) - 1) - ) + marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) if marker_idx: marker += self.markers[marker_idx] marker = converters.to_unicode(marker) # Make sure we ignore invisible characters when filling - width += progress.custom_len(marker) - progress.custom_len(marker) + width += len(marker) - progress.custom_len(marker) marker = marker.ljust(width, self.markers[0]) return left + marker + right @@ -1233,17 +1252,19 @@ def __call__( # type: ignore format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) - bar = Bar.__call__(self, progress, data, width) + bar = Bar.__call__(self, progress, data, width, color=False) # Aligns the center of the label to the center of the bar center_len = progress.custom_len(center) center_left = int((width - center_len) / 2) center_right = center_left + center_len - return bar[:center_left] + center + self.apply_colors( - progress, - data, - bar[center_right:] + return self._apply_colors( + bar[:center_left], data, + ) + self._apply_colors( + center, data, + ) + self._apply_colors( + bar[center_right:], data, ) diff --git a/tests/test_color.py b/tests/test_color.py new file mode 100644 index 00000000..3b5f5a15 --- /dev/null +++ b/tests/test_color.py @@ -0,0 +1,39 @@ +import pytest + +import progressbar +from progressbar import terminal + + +@pytest.mark.parametrize( + 'variable', [ + 'PROGRESSBAR_ENABLE_COLORS', + 'FORCE_COLOR', + ] +) +def test_color_environment_variables(monkeypatch, variable): + monkeypatch.setattr( + terminal, + 'color_support', + terminal.ColorSupport.XTERM_256, + ) + + monkeypatch.setenv(variable, '1') + bar = progressbar.ProgressBar() + assert bar.enable_colors + + monkeypatch.setenv(variable, '0') + bar = progressbar.ProgressBar() + assert not bar.enable_colors + +def test_enable_colors_flags(): + bar = progressbar.ProgressBar(enable_colors=True) + assert bar.enable_colors + + bar = progressbar.ProgressBar(enable_colors=False) + assert not bar.enable_colors + + bar = progressbar.ProgressBar(enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR) + assert bar.enable_colors + + with pytest.raises(ValueError): + progressbar.ProgressBar(enable_colors=12345) diff --git a/tests/test_flush.py b/tests/test_flush.py index 69dc4e30..f6336d8d 100644 --- a/tests/test_flush.py +++ b/tests/test_flush.py @@ -5,6 +5,7 @@ def test_flush(): '''Left justify using the terminal width''' p = progressbar.ProgressBar(poll_interval=0.001) + p.print('hello') for i in range(10): print('pre-updates', p.updates) diff --git a/tests/test_multibar.py b/tests/test_multibar.py index fe1c569f..4865ae3e 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -1,4 +1,8 @@ +import threading +import time + import pytest + import progressbar @@ -18,3 +22,83 @@ def test_multi_progress_bar_out_of_range(): def test_multi_progress_bar_fill_left(): import examples return examples.multi_progress_bar_example(False) + + +def test_multibar(): + bars = 3 + N = 10 + multibar = progressbar.MultiBar(sort_keyfunc=lambda bar: bar.label) + multibar.start() + multibar.append_label = False + multibar.prepend_label = True + + # Test handling of progressbars that don't call the super constructors + bar = progressbar.ProgressBar(max_value=N) + bar.index = -1 + multibar['x'] = bar + bar.start() + # Test twice for other code paths + multibar['x'] = bar + multibar._label_bar(bar) + multibar._label_bar(bar) + bar.finish() + del multibar['x'] + + multibar.append_label = True + + def do_something(bar): + for j in bar(range(N)): + time.sleep(0.01) + bar.update(j) + + for i in range(bars): + thread = threading.Thread( + target=do_something, + args=(multibar['bar {}'.format(i)],) + ) + thread.start() + + for bar in multibar.values(): + for j in range(N): + bar.update(j) + time.sleep(0.002) + + multibar.join(0.1) + multibar.stop(0.1) + + +@pytest.mark.parametrize( + 'sort_key', [ + None, + 'index', + 'label', + 'value', + 'percentage', + progressbar.SortKey.CREATED, + progressbar.SortKey.LABEL, + progressbar.SortKey.VALUE, + progressbar.SortKey.PERCENTAGE, + ] +) +def test_multibar_sorting(sort_key): + bars = 3 + N = 10 + + with progressbar.MultiBar() as multibar: + for i in range(bars): + label = 'bar {}'.format(i) + multibar[label] = progressbar.ProgressBar(max_value=N) + + for bar in multibar.values(): + for j in bar(range(N)): + assert bar.started() + time.sleep(0.002) + + for bar in multibar.values(): + assert bar.finished() + + +def test_offset_bar(): + with progressbar.ProgressBar(line_offset=2) as bar: + for i in range(100): + bar.update(i) diff --git a/tests/test_progressbar.py b/tests/test_progressbar.py index 32083eb0..3e20ab63 100644 --- a/tests/test_progressbar.py +++ b/tests/test_progressbar.py @@ -63,6 +63,9 @@ def test_dirty(): bar = progressbar.ProgressBar() bar.start() + assert bar.started() for i in range(10): bar.update(i) bar.finish(dirty=True) + assert bar.finished() + assert bar.started() From 8aa6d2da9e3c9b451817066abc313d9b9facb3e7 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Mon, 13 Mar 2023 22:14:01 +0100 Subject: [PATCH 21/24] added security contact information --- README.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.rst b/README.rst index d76756c1..434b5756 100644 --- a/README.rst +++ b/README.rst @@ -71,6 +71,14 @@ of widgets: The progressbar module is very easy to use, yet very powerful. It will also automatically enable features like auto-resizing when the system supports it. +****************************************************************************** +Security contact information +****************************************************************************** + +To report a security vulnerability, please use the +`Tidelift security contact `_. +Tidelift will coordinate the fix and disclosure. + ****************************************************************************** Known issues ****************************************************************************** From 409d2a070b8382b5f00f1045e2f27fa376cd2209 Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Fri, 17 Mar 2023 22:05:08 +0100 Subject: [PATCH 22/24] flake8 compliance --- progressbar/__init__.py | 1 + progressbar/bar.py | 37 +- progressbar/multi.py | 51 +- progressbar/terminal/base.py | 39 +- progressbar/terminal/colors.py | 728 +++++-------------- progressbar/terminal/os_specific/__init__.py | 4 +- progressbar/terminal/os_specific/windows.py | 46 +- progressbar/terminal/stream.py | 4 +- progressbar/widgets.py | 33 +- tests/test_color.py | 5 +- 10 files changed, 281 insertions(+), 667 deletions(-) diff --git a/progressbar/__init__.py b/progressbar/__init__.py index e43c4cf6..55525543 100644 --- a/progressbar/__init__.py +++ b/progressbar/__init__.py @@ -75,6 +75,7 @@ 'NullBar', '__author__', '__version__', + 'LineOffsetStreamWrapper', 'MultiBar', 'SortKey', ] diff --git a/progressbar/bar.py b/progressbar/bar.py index 9208333f..d9616f68 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -18,7 +18,8 @@ import progressbar.terminal.stream from . import ( base, - terminal, utils, + terminal, + utils, widgets, widgets as widgets_module, # Avoid name collision ) @@ -152,15 +153,16 @@ class DefaultFdMixin(ProgressBarMixinBase): #: Whether to print line breaks. This is useful for logging the #: progressbar. When disabled the current line is overwritten. line_breaks: bool = True - # : Specify the type and number of colors to support. Defaults to auto - # detection based on the file descriptor type (i.e. interactive terminal) - # environment variables such as `COLORTERM` and `TERM`. Color output can - # be forced in non-interactive terminals using the - # `PROGRESSBAR_ENABLE_COLORS` environment variable which can also be used - # to force a specific number of colors by specifying `24bit`, `256` or `16`. - # For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. - # For 256 color support you can use `TERM=xterm-256color`. - # For 16 colorsupport you can use `TERM=xterm`. + #: Specify the type and number of colors to support. Defaults to auto + #: detection based on the file descriptor type (i.e. interactive terminal) + #: environment variables such as `COLORTERM` and `TERM`. Color output can + #: be forced in non-interactive terminals using the + #: `PROGRESSBAR_ENABLE_COLORS` environment variable which can also be used + #: to force a specific number of colors by specifying `24bit`, `256` or + #: `16`. + #: For true (24 bit/16M) color support you can use `COLORTERM=truecolor`. + #: For 256 color support you can use `TERM=xterm-256color`. + #: For 16 colorsupport you can use `TERM=xterm`. enable_colors: terminal.ColorSupport | bool | None = terminal.color_support def __init__( @@ -180,8 +182,7 @@ def __init__( if line_offset: fd = progressbar.terminal.stream.LineOffsetStreamWrapper( - line_offset, - fd + line_offset, fd ) self.fd = fd @@ -493,7 +494,8 @@ def __init__( min_value: T = 0, max_value: T | types.Type[base.UnknownLength] | None = None, widgets: types.Optional[ - types.Sequence[widgets_module.WidgetBase | str]] = None, + types.Sequence[widgets_module.WidgetBase | str] + ] = None, left_justify: bool = True, initial_value: T = 0, poll_interval: types.Optional[float] = None, @@ -708,7 +710,7 @@ def data(self) -> types.Dict[str, types.Any]: total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) - + (elapsed.microseconds / 1000000.0), + + (elapsed.microseconds / 1000000.0), # The minutes since the bar started modulo 60 minutes_elapsed=(elapsed.seconds / 60) % 60, # The hours since the bar started modulo 24 @@ -841,9 +843,10 @@ def update(self, value=None, force=False, **kwargs): self.start() return self.update(value, force=force, **kwargs) - if value is not None and value is not base.UnknownLength and isinstance( - value, - int + if ( + value is not None + and value is not base.UnknownLength + and isinstance(value, int) ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update diff --git a/progressbar/multi.py b/progressbar/multi.py index 3e4a93de..dff82d60 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -31,6 +31,7 @@ class SortKey(str, enum.Enum): `value` might result in more rendering which can have a small performance impact. ''' + CREATED = 'index' LABEL = 'label' VALUE = 'value' @@ -170,60 +171,62 @@ def render(self, flush: bool = True, force: bool = False): now = timeit.default_timer() expired = now - self.remove_finished if self.remove_finished else None output = [] - for bar in self.get_sorted_bars(): - if not bar.started() and not self.show_initial: + for bar_ in self.get_sorted_bars(): + if not bar_.started() and not self.show_initial: continue def update(force=True, write=True): - self._label_bar(bar) - bar.update(force=force) + self._label_bar(bar_) + bar_.update(force=force) if write: - output.append(bar.fd.line) + output.append(bar_.fd.line) - if bar.finished(): - if bar not in self._finished_at: - self._finished_at[bar] = now + if bar_.finished(): + if bar_ not in self._finished_at: + self._finished_at[bar_] = now # Force update to get the finished format update(write=False) if self.remove_finished: - if expired >= self._finished_at[bar]: - del self[bar.label] + if expired >= self._finished_at[bar_]: + del self[bar_.label] continue if not self.show_finished: continue - if bar.finished(): + if bar_.finished(): if self.finished_format is None: update(force=False) else: - output.append(self.finished_format.format(label=bar.label)) - elif bar.started(): + output.append( + self.finished_format.format( + label=bar_.label + ) + ) + elif bar_.started(): update() else: if self.initial_format is None: - bar.start() + bar_.start() update() else: - output.append(self.initial_format.format(label=bar.label)) + output.append(self.initial_format.format(label=bar_.label)) with self._print_lock: # Clear the previous output if progressbars have been removed for i in range(len(output), len(self._previous_output)): self._buffer.write(terminal.clear_line(i + 1)) - # Add empty lines to the end of the output if progressbars have been - # added + # Add empty lines to the end of the output if progressbars have + # been added for i in range(len(self._previous_output), len(output)): # Adding a new line so we don't overwrite previous output self._buffer.write('\n') for i, (previous, current) in enumerate( itertools.zip_longest( - self._previous_output, - output, - fillvalue='' + self._previous_output, output, fillvalue='' ) ): if previous != current or force: @@ -241,13 +244,7 @@ def update(force=True, write=True): self.flush() def print( - self, - *args, - end='\n', - offset=None, - flush=True, - clear=True, - **kwargs + self, *args, end='\n', offset=None, flush=True, clear=True, **kwargs ): ''' Print to the progressbar stream without overwriting the progressbars diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 366373ee..b31e902e 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -27,7 +27,7 @@ def __init__(self, code, *default_args): def __call__(self, *args): return self._template.format( args=';'.join(map(str, args or self._default_args)), - code=self._code + code=self._code, ) def __str__(self): @@ -35,7 +35,6 @@ def __str__(self): class CSINoArg(CSI): - def __call__(self): return super().__call__() @@ -132,12 +131,14 @@ def __call__(self): # of line # CLEAR_LINE_ALL = CLEAR_LINE.format(n=2) # Clear Line + def clear_line(n): return UP(n) + CLEAR_LINE_ALL() + DOWN(n) class ColorSupport(enum.IntEnum): '''Color support for the terminal.''' + NONE = 0 XTERM = 16 XTERM_256 = 256 @@ -165,7 +166,9 @@ def from_env(cls): 'TERM', ) - if os.environ.get('JUPYTER_COLUMNS') or os.environ.get('JUPYTER_LINES'): + if os.environ.get('JUPYTER_COLUMNS') or os.environ.get( + 'JUPYTER_LINES' + ): # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR @@ -267,7 +270,9 @@ class HLS(collections.namedtuple('HLS', ['hue', 'lightness', 'saturation'])): @classmethod def from_rgb(cls, rgb: RGB) -> HLS: return cls( - *colorsys.rgb_to_hls(rgb.red / 255, rgb.green / 255, rgb.blue / 255) + *colorsys.rgb_to_hls( + rgb.red / 255, rgb.green / 255, rgb.blue / 255 + ) ) def interpolate(self, end: HLS, step: float) -> HLS: @@ -279,18 +284,19 @@ def interpolate(self, end: HLS, step: float) -> HLS: class ColorBase(abc.ABC): - def get_color(self, value: float) -> Color: raise NotImplementedError() + class Color( collections.namedtuple( - 'Color', [ + 'Color', + [ 'rgb', 'hls', 'name', 'xterm', - ] + ], ), ColorBase, ): @@ -305,6 +311,7 @@ class Color( The other values will be automatically interpolated from that if needed, but you can be more explicity if you wish. ''' + __slots__ = () def __call__(self, value: str) -> str: @@ -357,10 +364,12 @@ def __hash__(self): class Colors: - by_name: defaultdict[str, types.List[Color]] = collections.defaultdict(list) - by_lowername: defaultdict[str, types.List[Color]] = collections.defaultdict( + by_name: defaultdict[str, types.List[Color]] = collections.defaultdict( list ) + by_lowername: defaultdict[ + str, types.List[Color] + ] = collections.defaultdict(list) by_hex: defaultdict[str, types.List[Color]] = collections.defaultdict(list) by_rgb: defaultdict[RGB, types.List[Color]] = collections.defaultdict(list) by_hls: defaultdict[HLS, types.List[Color]] = collections.defaultdict(list) @@ -398,11 +407,7 @@ def interpolate(cls, color_a: Color, color_b: Color, step: float) -> Color: class ColorGradient(ColorBase): - def __init__( - self, - *colors: Color, - interpolate=Colors.interpolate - ): + def __init__(self, *colors: Color, interpolate=Colors.interpolate): assert colors self.colors = colors self.interpolate = interpolate @@ -412,7 +417,11 @@ def __call__(self, value: float): def get_color(self, value: float) -> Color: 'Map a value from 0 to 1 to a color' - if value is base.Undefined or value is base.UnknownLength or value <= 0: + if ( + value is base.Undefined + or value is base.UnknownLength + or value <= 0 + ): return self.colors[0] elif value >= 1: return self.colors[-1] diff --git a/progressbar/terminal/colors.py b/progressbar/terminal/colors.py index 5024a3bc..f05328a6 100644 --- a/progressbar/terminal/colors.py +++ b/progressbar/terminal/colors.py @@ -27,136 +27,73 @@ blue1 = Colors.register(RGB(0, 0, 255), HLS(100, 240, 50), 'Blue1', 21) darkGreen = Colors.register(RGB(0, 95, 0), HLS(100, 120, 18), 'DarkGreen', 22) deepSkyBlue4 = Colors.register( - RGB(0, 95, 95), - HLS(100, 180, 18), - 'DeepSkyBlue4', - 23 + RGB(0, 95, 95), HLS(100, 180, 18), 'DeepSkyBlue4', 23 ) deepSkyBlue4 = Colors.register( - RGB(0, 95, 135), - HLS(100, 97, 26), - 'DeepSkyBlue4', - 24 + RGB(0, 95, 135), HLS(100, 97, 26), 'DeepSkyBlue4', 24 ) deepSkyBlue4 = Colors.register( - RGB(0, 95, 175), - HLS(100, 7, 34), - 'DeepSkyBlue4', - 25 + RGB(0, 95, 175), HLS(100, 7, 34), 'DeepSkyBlue4', 25 ) dodgerBlue3 = Colors.register( - RGB(0, 95, 215), - HLS(100, 13, 42), - 'DodgerBlue3', - 26 + RGB(0, 95, 215), HLS(100, 13, 42), 'DodgerBlue3', 26 ) dodgerBlue2 = Colors.register( - RGB(0, 95, 255), - HLS(100, 17, 50), - 'DodgerBlue2', - 27 + RGB(0, 95, 255), HLS(100, 17, 50), 'DodgerBlue2', 27 ) green4 = Colors.register(RGB(0, 135, 0), HLS(100, 120, 26), 'Green4', 28) springGreen4 = Colors.register( - RGB(0, 135, 95), - HLS(100, 62, 26), - 'SpringGreen4', - 29 + RGB(0, 135, 95), HLS(100, 62, 26), 'SpringGreen4', 29 ) turquoise4 = Colors.register( - RGB(0, 135, 135), - HLS(100, 180, 26), - 'Turquoise4', - 30 + RGB(0, 135, 135), HLS(100, 180, 26), 'Turquoise4', 30 ) deepSkyBlue3 = Colors.register( - RGB(0, 135, 175), - HLS(100, 93, 34), - 'DeepSkyBlue3', - 31 + RGB(0, 135, 175), HLS(100, 93, 34), 'DeepSkyBlue3', 31 ) deepSkyBlue3 = Colors.register( - RGB(0, 135, 215), - HLS(100, 2, 42), - 'DeepSkyBlue3', - 32 + RGB(0, 135, 215), HLS(100, 2, 42), 'DeepSkyBlue3', 32 ) dodgerBlue1 = Colors.register( - RGB(0, 135, 255), - HLS(100, 8, 50), - 'DodgerBlue1', - 33 + RGB(0, 135, 255), HLS(100, 8, 50), 'DodgerBlue1', 33 ) green3 = Colors.register(RGB(0, 175, 0), HLS(100, 120, 34), 'Green3', 34) springGreen3 = Colors.register( - RGB(0, 175, 95), - HLS(100, 52, 34), - 'SpringGreen3', - 35 + RGB(0, 175, 95), HLS(100, 52, 34), 'SpringGreen3', 35 ) darkCyan = Colors.register(RGB(0, 175, 135), HLS(100, 66, 34), 'DarkCyan', 36) lightSeaGreen = Colors.register( - RGB(0, 175, 175), - HLS(100, 180, 34), - 'LightSeaGreen', - 37 + RGB(0, 175, 175), HLS(100, 180, 34), 'LightSeaGreen', 37 ) deepSkyBlue2 = Colors.register( - RGB(0, 175, 215), - HLS(100, 91, 42), - 'DeepSkyBlue2', - 38 + RGB(0, 175, 215), HLS(100, 91, 42), 'DeepSkyBlue2', 38 ) deepSkyBlue1 = Colors.register( - RGB(0, 175, 255), - HLS(100, 98, 50), - 'DeepSkyBlue1', - 39 + RGB(0, 175, 255), HLS(100, 98, 50), 'DeepSkyBlue1', 39 ) green3 = Colors.register(RGB(0, 215, 0), HLS(100, 120, 42), 'Green3', 40) springGreen3 = Colors.register( - RGB(0, 215, 95), - HLS(100, 46, 42), - 'SpringGreen3', - 41 + RGB(0, 215, 95), HLS(100, 46, 42), 'SpringGreen3', 41 ) springGreen2 = Colors.register( - RGB(0, 215, 135), - HLS(100, 57, 42), - 'SpringGreen2', - 42 + RGB(0, 215, 135), HLS(100, 57, 42), 'SpringGreen2', 42 ) cyan3 = Colors.register(RGB(0, 215, 175), HLS(100, 68, 42), 'Cyan3', 43) darkTurquoise = Colors.register( - RGB(0, 215, 215), - HLS(100, 180, 42), - 'DarkTurquoise', - 44 + RGB(0, 215, 215), HLS(100, 180, 42), 'DarkTurquoise', 44 ) turquoise2 = Colors.register( - RGB(0, 215, 255), - HLS(100, 89, 50), - 'Turquoise2', - 45 + RGB(0, 215, 255), HLS(100, 89, 50), 'Turquoise2', 45 ) green1 = Colors.register(RGB(0, 255, 0), HLS(100, 120, 50), 'Green1', 46) springGreen2 = Colors.register( - RGB(0, 255, 95), - HLS(100, 42, 50), - 'SpringGreen2', - 47 + RGB(0, 255, 95), HLS(100, 42, 50), 'SpringGreen2', 47 ) springGreen1 = Colors.register( - RGB(0, 255, 135), - HLS(100, 51, 50), - 'SpringGreen1', - 48 + RGB(0, 255, 135), HLS(100, 51, 50), 'SpringGreen1', 48 ) mediumSpringGreen = Colors.register( - RGB(0, 255, 175), - HLS(100, 61, 50), - 'MediumSpringGreen', - 49 + RGB(0, 255, 175), HLS(100, 61, 50), 'MediumSpringGreen', 49 ) cyan2 = Colors.register(RGB(0, 255, 215), HLS(100, 70, 50), 'Cyan2', 50) cyan1 = Colors.register(RGB(0, 255, 255), HLS(100, 180, 50), 'Cyan1', 51) @@ -166,753 +103,432 @@ purple4 = Colors.register(RGB(95, 0, 175), HLS(100, 72, 34), 'Purple4', 55) purple3 = Colors.register(RGB(95, 0, 215), HLS(100, 66, 42), 'Purple3', 56) blueViolet = Colors.register( - RGB(95, 0, 255), - HLS(100, 62, 50), - 'BlueViolet', - 57 + RGB(95, 0, 255), HLS(100, 62, 50), 'BlueViolet', 57 ) orange4 = Colors.register(RGB(95, 95, 0), HLS(100, 60, 18), 'Orange4', 58) grey37 = Colors.register(RGB(95, 95, 95), HLS(0, 0, 37), 'Grey37', 59) mediumPurple4 = Colors.register( - RGB(95, 95, 135), - HLS(17, 240, 45), - 'MediumPurple4', - 60 + RGB(95, 95, 135), HLS(17, 240, 45), 'MediumPurple4', 60 ) slateBlue3 = Colors.register( - RGB(95, 95, 175), - HLS(33, 240, 52), - 'SlateBlue3', - 61 + RGB(95, 95, 175), HLS(33, 240, 52), 'SlateBlue3', 61 ) slateBlue3 = Colors.register( - RGB(95, 95, 215), - HLS(60, 240, 60), - 'SlateBlue3', - 62 + RGB(95, 95, 215), HLS(60, 240, 60), 'SlateBlue3', 62 ) royalBlue1 = Colors.register( - RGB(95, 95, 255), - HLS(100, 240, 68), - 'RoyalBlue1', - 63 + RGB(95, 95, 255), HLS(100, 240, 68), 'RoyalBlue1', 63 ) chartreuse4 = Colors.register( - RGB(95, 135, 0), - HLS(100, 7, 26), - 'Chartreuse4', - 64 + RGB(95, 135, 0), HLS(100, 7, 26), 'Chartreuse4', 64 ) darkSeaGreen4 = Colors.register( - RGB(95, 135, 95), - HLS(17, 120, 45), - 'DarkSeaGreen4', - 65 + RGB(95, 135, 95), HLS(17, 120, 45), 'DarkSeaGreen4', 65 ) paleTurquoise4 = Colors.register( - RGB(95, 135, 135), - HLS(17, 180, 45), - 'PaleTurquoise4', - 66 + RGB(95, 135, 135), HLS(17, 180, 45), 'PaleTurquoise4', 66 ) steelBlue = Colors.register( - RGB(95, 135, 175), - HLS(33, 210, 52), - 'SteelBlue', - 67 + RGB(95, 135, 175), HLS(33, 210, 52), 'SteelBlue', 67 ) steelBlue3 = Colors.register( - RGB(95, 135, 215), - HLS(60, 220, 60), - 'SteelBlue3', - 68 + RGB(95, 135, 215), HLS(60, 220, 60), 'SteelBlue3', 68 ) cornflowerBlue = Colors.register( - RGB(95, 135, 255), - HLS(100, 225, 68), - 'CornflowerBlue', - 69 + RGB(95, 135, 255), HLS(100, 225, 68), 'CornflowerBlue', 69 ) chartreuse3 = Colors.register( - RGB(95, 175, 0), - HLS(100, 7, 34), - 'Chartreuse3', - 70 + RGB(95, 175, 0), HLS(100, 7, 34), 'Chartreuse3', 70 ) darkSeaGreen4 = Colors.register( - RGB(95, 175, 95), - HLS(33, 120, 52), - 'DarkSeaGreen4', - 71 + RGB(95, 175, 95), HLS(33, 120, 52), 'DarkSeaGreen4', 71 ) cadetBlue = Colors.register( - RGB(95, 175, 135), - HLS(33, 150, 52), - 'CadetBlue', - 72 + RGB(95, 175, 135), HLS(33, 150, 52), 'CadetBlue', 72 ) cadetBlue = Colors.register( - RGB(95, 175, 175), - HLS(33, 180, 52), - 'CadetBlue', - 73 + RGB(95, 175, 175), HLS(33, 180, 52), 'CadetBlue', 73 ) skyBlue3 = Colors.register(RGB(95, 175, 215), HLS(60, 200, 60), 'SkyBlue3', 74) steelBlue1 = Colors.register( - RGB(95, 175, 255), - HLS(100, 210, 68), - 'SteelBlue1', - 75 + RGB(95, 175, 255), HLS(100, 210, 68), 'SteelBlue1', 75 ) chartreuse3 = Colors.register( - RGB(95, 215, 0), - HLS(100, 3, 42), - 'Chartreuse3', - 76 + RGB(95, 215, 0), HLS(100, 3, 42), 'Chartreuse3', 76 ) paleGreen3 = Colors.register( - RGB(95, 215, 95), - HLS(60, 120, 60), - 'PaleGreen3', - 77 + RGB(95, 215, 95), HLS(60, 120, 60), 'PaleGreen3', 77 ) seaGreen3 = Colors.register( - RGB(95, 215, 135), - HLS(60, 140, 60), - 'SeaGreen3', - 78 + RGB(95, 215, 135), HLS(60, 140, 60), 'SeaGreen3', 78 ) aquamarine3 = Colors.register( - RGB(95, 215, 175), - HLS(60, 160, 60), - 'Aquamarine3', - 79 + RGB(95, 215, 175), HLS(60, 160, 60), 'Aquamarine3', 79 ) mediumTurquoise = Colors.register( - RGB(95, 215, 215), - HLS(60, 180, 60), - 'MediumTurquoise', - 80 + RGB(95, 215, 215), HLS(60, 180, 60), 'MediumTurquoise', 80 ) steelBlue1 = Colors.register( - RGB(95, 215, 255), - HLS(100, 195, 68), - 'SteelBlue1', - 81 + RGB(95, 215, 255), HLS(100, 195, 68), 'SteelBlue1', 81 ) chartreuse2 = Colors.register( - RGB(95, 255, 0), - HLS(100, 7, 50), - 'Chartreuse2', - 82 + RGB(95, 255, 0), HLS(100, 7, 50), 'Chartreuse2', 82 ) seaGreen2 = Colors.register( - RGB(95, 255, 95), - HLS(100, 120, 68), - 'SeaGreen2', - 83 + RGB(95, 255, 95), HLS(100, 120, 68), 'SeaGreen2', 83 ) seaGreen1 = Colors.register( - RGB(95, 255, 135), - HLS(100, 135, 68), - 'SeaGreen1', - 84 + RGB(95, 255, 135), HLS(100, 135, 68), 'SeaGreen1', 84 ) seaGreen1 = Colors.register( - RGB(95, 255, 175), - HLS(100, 150, 68), - 'SeaGreen1', - 85 + RGB(95, 255, 175), HLS(100, 150, 68), 'SeaGreen1', 85 ) aquamarine1 = Colors.register( - RGB(95, 255, 215), - HLS(100, 165, 68), - 'Aquamarine1', - 86 + RGB(95, 255, 215), HLS(100, 165, 68), 'Aquamarine1', 86 ) darkSlateGray2 = Colors.register( - RGB(95, 255, 255), - HLS(100, 180, 68), - 'DarkSlateGray2', - 87 + RGB(95, 255, 255), HLS(100, 180, 68), 'DarkSlateGray2', 87 ) darkRed = Colors.register(RGB(135, 0, 0), HLS(100, 0, 26), 'DarkRed', 88) deepPink4 = Colors.register(RGB(135, 0, 95), HLS(100, 17, 26), 'DeepPink4', 89) darkMagenta = Colors.register( - RGB(135, 0, 135), - HLS(100, 300, 26), - 'DarkMagenta', - 90 + RGB(135, 0, 135), HLS(100, 300, 26), 'DarkMagenta', 90 ) darkMagenta = Colors.register( - RGB(135, 0, 175), - HLS(100, 86, 34), - 'DarkMagenta', - 91 + RGB(135, 0, 175), HLS(100, 86, 34), 'DarkMagenta', 91 ) darkViolet = Colors.register( - RGB(135, 0, 215), - HLS(100, 77, 42), - 'DarkViolet', - 92 + RGB(135, 0, 215), HLS(100, 77, 42), 'DarkViolet', 92 ) purple = Colors.register(RGB(135, 0, 255), HLS(100, 71, 50), 'Purple', 93) orange4 = Colors.register(RGB(135, 95, 0), HLS(100, 2, 26), 'Orange4', 94) -lightPink4 = Colors.register(RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95) +lightPink4 = Colors.register( + RGB(135, 95, 95), HLS(17, 0, 45), 'LightPink4', 95 +) plum4 = Colors.register(RGB(135, 95, 135), HLS(17, 300, 45), 'Plum4', 96) mediumPurple3 = Colors.register( - RGB(135, 95, 175), - HLS(33, 270, 52), - 'MediumPurple3', - 97 + RGB(135, 95, 175), HLS(33, 270, 52), 'MediumPurple3', 97 ) mediumPurple3 = Colors.register( - RGB(135, 95, 215), - HLS(60, 260, 60), - 'MediumPurple3', - 98 + RGB(135, 95, 215), HLS(60, 260, 60), 'MediumPurple3', 98 ) slateBlue1 = Colors.register( - RGB(135, 95, 255), - HLS(100, 255, 68), - 'SlateBlue1', - 99 + RGB(135, 95, 255), HLS(100, 255, 68), 'SlateBlue1', 99 ) yellow4 = Colors.register(RGB(135, 135, 0), HLS(100, 60, 26), 'Yellow4', 100) wheat4 = Colors.register(RGB(135, 135, 95), HLS(17, 60, 45), 'Wheat4', 101) grey53 = Colors.register(RGB(135, 135, 135), HLS(0, 0, 52), 'Grey53', 102) lightSlateGrey = Colors.register( - RGB(135, 135, 175), - HLS(20, 240, 60), - 'LightSlateGrey', - 103 + RGB(135, 135, 175), HLS(20, 240, 60), 'LightSlateGrey', 103 ) mediumPurple = Colors.register( - RGB(135, 135, 215), - HLS(50, 240, 68), - 'MediumPurple', - 104 + RGB(135, 135, 215), HLS(50, 240, 68), 'MediumPurple', 104 ) lightSlateBlue = Colors.register( - RGB(135, 135, 255), - HLS(100, 240, 76), - 'LightSlateBlue', - 105 + RGB(135, 135, 255), HLS(100, 240, 76), 'LightSlateBlue', 105 ) yellow4 = Colors.register(RGB(135, 175, 0), HLS(100, 3, 34), 'Yellow4', 106) darkOliveGreen3 = Colors.register( - RGB(135, 175, 95), - HLS(33, 90, 52), - 'DarkOliveGreen3', - 107 + RGB(135, 175, 95), HLS(33, 90, 52), 'DarkOliveGreen3', 107 ) darkSeaGreen = Colors.register( - RGB(135, 175, 135), - HLS(20, 120, 60), - 'DarkSeaGreen', - 108 + RGB(135, 175, 135), HLS(20, 120, 60), 'DarkSeaGreen', 108 ) lightSkyBlue3 = Colors.register( - RGB(135, 175, 175), - HLS(20, 180, 60), - 'LightSkyBlue3', - 109 + RGB(135, 175, 175), HLS(20, 180, 60), 'LightSkyBlue3', 109 ) lightSkyBlue3 = Colors.register( - RGB(135, 175, 215), - HLS(50, 210, 68), - 'LightSkyBlue3', - 110 + RGB(135, 175, 215), HLS(50, 210, 68), 'LightSkyBlue3', 110 ) skyBlue2 = Colors.register( - RGB(135, 175, 255), - HLS(100, 220, 76), - 'SkyBlue2', - 111 + RGB(135, 175, 255), HLS(100, 220, 76), 'SkyBlue2', 111 ) chartreuse2 = Colors.register( - RGB(135, 215, 0), - HLS(100, 2, 42), - 'Chartreuse2', - 112 + RGB(135, 215, 0), HLS(100, 2, 42), 'Chartreuse2', 112 ) darkOliveGreen3 = Colors.register( - RGB(135, 215, 95), - HLS(60, 100, 60), - 'DarkOliveGreen3', - 113 + RGB(135, 215, 95), HLS(60, 100, 60), 'DarkOliveGreen3', 113 ) paleGreen3 = Colors.register( - RGB(135, 215, 135), - HLS(50, 120, 68), - 'PaleGreen3', - 114 + RGB(135, 215, 135), HLS(50, 120, 68), 'PaleGreen3', 114 ) darkSeaGreen3 = Colors.register( - RGB(135, 215, 175), - HLS(50, 150, 68), - 'DarkSeaGreen3', - 115 + RGB(135, 215, 175), HLS(50, 150, 68), 'DarkSeaGreen3', 115 ) darkSlateGray3 = Colors.register( - RGB(135, 215, 215), - HLS(50, 180, 68), - 'DarkSlateGray3', - 116 + RGB(135, 215, 215), HLS(50, 180, 68), 'DarkSlateGray3', 116 ) skyBlue1 = Colors.register( - RGB(135, 215, 255), - HLS(100, 200, 76), - 'SkyBlue1', - 117 + RGB(135, 215, 255), HLS(100, 200, 76), 'SkyBlue1', 117 ) chartreuse1 = Colors.register( - RGB(135, 255, 0), - HLS(100, 8, 50), - 'Chartreuse1', - 118 + RGB(135, 255, 0), HLS(100, 8, 50), 'Chartreuse1', 118 ) lightGreen = Colors.register( - RGB(135, 255, 95), - HLS(100, 105, 68), - 'LightGreen', - 119 + RGB(135, 255, 95), HLS(100, 105, 68), 'LightGreen', 119 ) lightGreen = Colors.register( - RGB(135, 255, 135), - HLS(100, 120, 76), - 'LightGreen', - 120 + RGB(135, 255, 135), HLS(100, 120, 76), 'LightGreen', 120 ) paleGreen1 = Colors.register( - RGB(135, 255, 175), - HLS(100, 140, 76), - 'PaleGreen1', - 121 + RGB(135, 255, 175), HLS(100, 140, 76), 'PaleGreen1', 121 ) aquamarine1 = Colors.register( - RGB(135, 255, 215), - HLS(100, 160, 76), - 'Aquamarine1', - 122 + RGB(135, 255, 215), HLS(100, 160, 76), 'Aquamarine1', 122 ) darkSlateGray1 = Colors.register( - RGB(135, 255, 255), - HLS(100, 180, 76), - 'DarkSlateGray1', - 123 + RGB(135, 255, 255), HLS(100, 180, 76), 'DarkSlateGray1', 123 ) red3 = Colors.register(RGB(175, 0, 0), HLS(100, 0, 34), 'Red3', 124) -deepPink4 = Colors.register(RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125) +deepPink4 = Colors.register( + RGB(175, 0, 95), HLS(100, 27, 34), 'DeepPink4', 125 +) mediumVioletRed = Colors.register( - RGB(175, 0, 135), - HLS(100, 13, 34), - 'MediumVioletRed', - 126 + RGB(175, 0, 135), HLS(100, 13, 34), 'MediumVioletRed', 126 +) +magenta3 = Colors.register( + RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127 ) -magenta3 = Colors.register(RGB(175, 0, 175), HLS(100, 300, 34), 'Magenta3', 127) darkViolet = Colors.register( - RGB(175, 0, 215), - HLS(100, 88, 42), - 'DarkViolet', - 128 + RGB(175, 0, 215), HLS(100, 88, 42), 'DarkViolet', 128 ) purple = Colors.register(RGB(175, 0, 255), HLS(100, 81, 50), 'Purple', 129) darkOrange3 = Colors.register( - RGB(175, 95, 0), - HLS(100, 2, 34), - 'DarkOrange3', - 130 + RGB(175, 95, 0), HLS(100, 2, 34), 'DarkOrange3', 130 ) indianRed = Colors.register(RGB(175, 95, 95), HLS(33, 0, 52), 'IndianRed', 131) -hotPink3 = Colors.register(RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132) +hotPink3 = Colors.register( + RGB(175, 95, 135), HLS(33, 330, 52), 'HotPink3', 132 +) mediumOrchid3 = Colors.register( - RGB(175, 95, 175), - HLS(33, 300, 52), - 'MediumOrchid3', - 133 + RGB(175, 95, 175), HLS(33, 300, 52), 'MediumOrchid3', 133 ) mediumOrchid = Colors.register( - RGB(175, 95, 215), - HLS(60, 280, 60), - 'MediumOrchid', - 134 + RGB(175, 95, 215), HLS(60, 280, 60), 'MediumOrchid', 134 ) mediumPurple2 = Colors.register( - RGB(175, 95, 255), - HLS(100, 270, 68), - 'MediumPurple2', - 135 + RGB(175, 95, 255), HLS(100, 270, 68), 'MediumPurple2', 135 ) darkGoldenrod = Colors.register( - RGB(175, 135, 0), - HLS(100, 6, 34), - 'DarkGoldenrod', - 136 + RGB(175, 135, 0), HLS(100, 6, 34), 'DarkGoldenrod', 136 ) lightSalmon3 = Colors.register( - RGB(175, 135, 95), - HLS(33, 30, 52), - 'LightSalmon3', - 137 + RGB(175, 135, 95), HLS(33, 30, 52), 'LightSalmon3', 137 ) rosyBrown = Colors.register( - RGB(175, 135, 135), - HLS(20, 0, 60), - 'RosyBrown', - 138 + RGB(175, 135, 135), HLS(20, 0, 60), 'RosyBrown', 138 ) grey63 = Colors.register(RGB(175, 135, 175), HLS(20, 300, 60), 'Grey63', 139) mediumPurple2 = Colors.register( - RGB(175, 135, 215), - HLS(50, 270, 68), - 'MediumPurple2', - 140 + RGB(175, 135, 215), HLS(50, 270, 68), 'MediumPurple2', 140 ) mediumPurple1 = Colors.register( - RGB(175, 135, 255), - HLS(100, 260, 76), - 'MediumPurple1', - 141 + RGB(175, 135, 255), HLS(100, 260, 76), 'MediumPurple1', 141 ) gold3 = Colors.register(RGB(175, 175, 0), HLS(100, 60, 34), 'Gold3', 142) darkKhaki = Colors.register( - RGB(175, 175, 95), - HLS(33, 60, 52), - 'DarkKhaki', - 143 + RGB(175, 175, 95), HLS(33, 60, 52), 'DarkKhaki', 143 ) navajoWhite3 = Colors.register( - RGB(175, 175, 135), - HLS(20, 60, 60), - 'NavajoWhite3', - 144 + RGB(175, 175, 135), HLS(20, 60, 60), 'NavajoWhite3', 144 ) grey69 = Colors.register(RGB(175, 175, 175), HLS(0, 0, 68), 'Grey69', 145) lightSteelBlue3 = Colors.register( - RGB(175, 175, 215), - HLS(33, 240, 76), - 'LightSteelBlue3', - 146 + RGB(175, 175, 215), HLS(33, 240, 76), 'LightSteelBlue3', 146 ) lightSteelBlue = Colors.register( - RGB(175, 175, 255), - HLS(100, 240, 84), - 'LightSteelBlue', - 147 + RGB(175, 175, 255), HLS(100, 240, 84), 'LightSteelBlue', 147 ) yellow3 = Colors.register(RGB(175, 215, 0), HLS(100, 1, 42), 'Yellow3', 148) darkOliveGreen3 = Colors.register( - RGB(175, 215, 95), - HLS(60, 80, 60), - 'DarkOliveGreen3', - 149 + RGB(175, 215, 95), HLS(60, 80, 60), 'DarkOliveGreen3', 149 ) darkSeaGreen3 = Colors.register( - RGB(175, 215, 135), - HLS(50, 90, 68), - 'DarkSeaGreen3', - 150 + RGB(175, 215, 135), HLS(50, 90, 68), 'DarkSeaGreen3', 150 ) darkSeaGreen2 = Colors.register( - RGB(175, 215, 175), - HLS(33, 120, 76), - 'DarkSeaGreen2', - 151 + RGB(175, 215, 175), HLS(33, 120, 76), 'DarkSeaGreen2', 151 ) lightCyan3 = Colors.register( - RGB(175, 215, 215), - HLS(33, 180, 76), - 'LightCyan3', - 152 + RGB(175, 215, 215), HLS(33, 180, 76), 'LightCyan3', 152 ) lightSkyBlue1 = Colors.register( - RGB(175, 215, 255), - HLS(100, 210, 84), - 'LightSkyBlue1', - 153 + RGB(175, 215, 255), HLS(100, 210, 84), 'LightSkyBlue1', 153 ) greenYellow = Colors.register( - RGB(175, 255, 0), - HLS(100, 8, 50), - 'GreenYellow', - 154 + RGB(175, 255, 0), HLS(100, 8, 50), 'GreenYellow', 154 ) darkOliveGreen2 = Colors.register( - RGB(175, 255, 95), - HLS(100, 90, 68), - 'DarkOliveGreen2', - 155 + RGB(175, 255, 95), HLS(100, 90, 68), 'DarkOliveGreen2', 155 ) paleGreen1 = Colors.register( - RGB(175, 255, 135), - HLS(100, 100, 76), - 'PaleGreen1', - 156 + RGB(175, 255, 135), HLS(100, 100, 76), 'PaleGreen1', 156 ) darkSeaGreen2 = Colors.register( - RGB(175, 255, 175), - HLS(100, 120, 84), - 'DarkSeaGreen2', - 157 + RGB(175, 255, 175), HLS(100, 120, 84), 'DarkSeaGreen2', 157 ) darkSeaGreen1 = Colors.register( - RGB(175, 255, 215), - HLS(100, 150, 84), - 'DarkSeaGreen1', - 158 + RGB(175, 255, 215), HLS(100, 150, 84), 'DarkSeaGreen1', 158 ) paleTurquoise1 = Colors.register( - RGB(175, 255, 255), - HLS(100, 180, 84), - 'PaleTurquoise1', - 159 + RGB(175, 255, 255), HLS(100, 180, 84), 'PaleTurquoise1', 159 ) red3 = Colors.register(RGB(215, 0, 0), HLS(100, 0, 42), 'Red3', 160) -deepPink3 = Colors.register(RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161) deepPink3 = Colors.register( - RGB(215, 0, 135), - HLS(100, 22, 42), - 'DeepPink3', - 162 + RGB(215, 0, 95), HLS(100, 33, 42), 'DeepPink3', 161 +) +deepPink3 = Colors.register( + RGB(215, 0, 135), HLS(100, 22, 42), 'DeepPink3', 162 ) magenta3 = Colors.register(RGB(215, 0, 175), HLS(100, 11, 42), 'Magenta3', 163) -magenta3 = Colors.register(RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164) +magenta3 = Colors.register( + RGB(215, 0, 215), HLS(100, 300, 42), 'Magenta3', 164 +) magenta2 = Colors.register(RGB(215, 0, 255), HLS(100, 90, 50), 'Magenta2', 165) darkOrange3 = Colors.register( - RGB(215, 95, 0), - HLS(100, 6, 42), - 'DarkOrange3', - 166 + RGB(215, 95, 0), HLS(100, 6, 42), 'DarkOrange3', 166 ) indianRed = Colors.register(RGB(215, 95, 95), HLS(60, 0, 60), 'IndianRed', 167) -hotPink3 = Colors.register(RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168) -hotPink2 = Colors.register(RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169) +hotPink3 = Colors.register( + RGB(215, 95, 135), HLS(60, 340, 60), 'HotPink3', 168 +) +hotPink2 = Colors.register( + RGB(215, 95, 175), HLS(60, 320, 60), 'HotPink2', 169 +) orchid = Colors.register(RGB(215, 95, 215), HLS(60, 300, 60), 'Orchid', 170) mediumOrchid1 = Colors.register( - RGB(215, 95, 255), - HLS(100, 285, 68), - 'MediumOrchid1', - 171 + RGB(215, 95, 255), HLS(100, 285, 68), 'MediumOrchid1', 171 ) orange3 = Colors.register(RGB(215, 135, 0), HLS(100, 7, 42), 'Orange3', 172) lightSalmon3 = Colors.register( - RGB(215, 135, 95), - HLS(60, 20, 60), - 'LightSalmon3', - 173 + RGB(215, 135, 95), HLS(60, 20, 60), 'LightSalmon3', 173 ) lightPink3 = Colors.register( - RGB(215, 135, 135), - HLS(50, 0, 68), - 'LightPink3', - 174 + RGB(215, 135, 135), HLS(50, 0, 68), 'LightPink3', 174 ) pink3 = Colors.register(RGB(215, 135, 175), HLS(50, 330, 68), 'Pink3', 175) plum3 = Colors.register(RGB(215, 135, 215), HLS(50, 300, 68), 'Plum3', 176) violet = Colors.register(RGB(215, 135, 255), HLS(100, 280, 76), 'Violet', 177) gold3 = Colors.register(RGB(215, 175, 0), HLS(100, 8, 42), 'Gold3', 178) lightGoldenrod3 = Colors.register( - RGB(215, 175, 95), - HLS(60, 40, 60), - 'LightGoldenrod3', - 179 + RGB(215, 175, 95), HLS(60, 40, 60), 'LightGoldenrod3', 179 ) tan = Colors.register(RGB(215, 175, 135), HLS(50, 30, 68), 'Tan', 180) mistyRose3 = Colors.register( - RGB(215, 175, 175), - HLS(33, 0, 76), - 'MistyRose3', - 181 + RGB(215, 175, 175), HLS(33, 0, 76), 'MistyRose3', 181 ) thistle3 = Colors.register( - RGB(215, 175, 215), - HLS(33, 300, 76), - 'Thistle3', - 182 + RGB(215, 175, 215), HLS(33, 300, 76), 'Thistle3', 182 ) plum2 = Colors.register(RGB(215, 175, 255), HLS(100, 270, 84), 'Plum2', 183) yellow3 = Colors.register(RGB(215, 215, 0), HLS(100, 60, 42), 'Yellow3', 184) khaki3 = Colors.register(RGB(215, 215, 95), HLS(60, 60, 60), 'Khaki3', 185) lightGoldenrod2 = Colors.register( - RGB(215, 215, 135), - HLS(50, 60, 68), - 'LightGoldenrod2', - 186 + RGB(215, 215, 135), HLS(50, 60, 68), 'LightGoldenrod2', 186 ) lightYellow3 = Colors.register( - RGB(215, 215, 175), - HLS(33, 60, 76), - 'LightYellow3', - 187 + RGB(215, 215, 175), HLS(33, 60, 76), 'LightYellow3', 187 ) grey84 = Colors.register(RGB(215, 215, 215), HLS(0, 0, 84), 'Grey84', 188) lightSteelBlue1 = Colors.register( - RGB(215, 215, 255), - HLS(100, 240, 92), - 'LightSteelBlue1', - 189 + RGB(215, 215, 255), HLS(100, 240, 92), 'LightSteelBlue1', 189 ) yellow2 = Colors.register(RGB(215, 255, 0), HLS(100, 9, 50), 'Yellow2', 190) darkOliveGreen1 = Colors.register( - RGB(215, 255, 95), - HLS(100, 75, 68), - 'DarkOliveGreen1', - 191 + RGB(215, 255, 95), HLS(100, 75, 68), 'DarkOliveGreen1', 191 ) darkOliveGreen1 = Colors.register( - RGB(215, 255, 135), - HLS(100, 80, 76), - 'DarkOliveGreen1', - 192 + RGB(215, 255, 135), HLS(100, 80, 76), 'DarkOliveGreen1', 192 ) darkSeaGreen1 = Colors.register( - RGB(215, 255, 175), - HLS(100, 90, 84), - 'DarkSeaGreen1', - 193 + RGB(215, 255, 175), HLS(100, 90, 84), 'DarkSeaGreen1', 193 ) honeydew2 = Colors.register( - RGB(215, 255, 215), - HLS(100, 120, 92), - 'Honeydew2', - 194 + RGB(215, 255, 215), HLS(100, 120, 92), 'Honeydew2', 194 ) lightCyan1 = Colors.register( - RGB(215, 255, 255), - HLS(100, 180, 92), - 'LightCyan1', - 195 + RGB(215, 255, 255), HLS(100, 180, 92), 'LightCyan1', 195 ) red1 = Colors.register(RGB(255, 0, 0), HLS(100, 0, 50), 'Red1', 196) -deepPink2 = Colors.register(RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197) +deepPink2 = Colors.register( + RGB(255, 0, 95), HLS(100, 37, 50), 'DeepPink2', 197 +) deepPink1 = Colors.register( - RGB(255, 0, 135), - HLS(100, 28, 50), - 'DeepPink1', - 198 + RGB(255, 0, 135), HLS(100, 28, 50), 'DeepPink1', 198 ) deepPink1 = Colors.register( - RGB(255, 0, 175), - HLS(100, 18, 50), - 'DeepPink1', - 199 + RGB(255, 0, 175), HLS(100, 18, 50), 'DeepPink1', 199 ) magenta2 = Colors.register(RGB(255, 0, 215), HLS(100, 9, 50), 'Magenta2', 200) -magenta1 = Colors.register(RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201) +magenta1 = Colors.register( + RGB(255, 0, 255), HLS(100, 300, 50), 'Magenta1', 201 +) orangeRed1 = Colors.register( - RGB(255, 95, 0), - HLS(100, 2, 50), - 'OrangeRed1', - 202 + RGB(255, 95, 0), HLS(100, 2, 50), 'OrangeRed1', 202 ) indianRed1 = Colors.register( - RGB(255, 95, 95), - HLS(100, 0, 68), - 'IndianRed1', - 203 + RGB(255, 95, 95), HLS(100, 0, 68), 'IndianRed1', 203 ) indianRed1 = Colors.register( - RGB(255, 95, 135), - HLS(100, 345, 68), - 'IndianRed1', - 204 + RGB(255, 95, 135), HLS(100, 345, 68), 'IndianRed1', 204 ) hotPink = Colors.register(RGB(255, 95, 175), HLS(100, 330, 68), 'HotPink', 205) hotPink = Colors.register(RGB(255, 95, 215), HLS(100, 315, 68), 'HotPink', 206) mediumOrchid1 = Colors.register( - RGB(255, 95, 255), - HLS(100, 300, 68), - 'MediumOrchid1', - 207 + RGB(255, 95, 255), HLS(100, 300, 68), 'MediumOrchid1', 207 ) darkOrange = Colors.register( - RGB(255, 135, 0), - HLS(100, 1, 50), - 'DarkOrange', - 208 + RGB(255, 135, 0), HLS(100, 1, 50), 'DarkOrange', 208 ) salmon1 = Colors.register(RGB(255, 135, 95), HLS(100, 15, 68), 'Salmon1', 209) lightCoral = Colors.register( - RGB(255, 135, 135), - HLS(100, 0, 76), - 'LightCoral', - 210 + RGB(255, 135, 135), HLS(100, 0, 76), 'LightCoral', 210 ) paleVioletRed1 = Colors.register( - RGB(255, 135, 175), - HLS(100, 340, 76), - 'PaleVioletRed1', - 211 + RGB(255, 135, 175), HLS(100, 340, 76), 'PaleVioletRed1', 211 +) +orchid2 = Colors.register( + RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212 +) +orchid1 = Colors.register( + RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213 ) -orchid2 = Colors.register(RGB(255, 135, 215), HLS(100, 320, 76), 'Orchid2', 212) -orchid1 = Colors.register(RGB(255, 135, 255), HLS(100, 300, 76), 'Orchid1', 213) orange1 = Colors.register(RGB(255, 175, 0), HLS(100, 1, 50), 'Orange1', 214) sandyBrown = Colors.register( - RGB(255, 175, 95), - HLS(100, 30, 68), - 'SandyBrown', - 215 + RGB(255, 175, 95), HLS(100, 30, 68), 'SandyBrown', 215 ) lightSalmon1 = Colors.register( - RGB(255, 175, 135), - HLS(100, 20, 76), - 'LightSalmon1', - 216 + RGB(255, 175, 135), HLS(100, 20, 76), 'LightSalmon1', 216 ) lightPink1 = Colors.register( - RGB(255, 175, 175), - HLS(100, 0, 84), - 'LightPink1', - 217 + RGB(255, 175, 175), HLS(100, 0, 84), 'LightPink1', 217 ) pink1 = Colors.register(RGB(255, 175, 215), HLS(100, 330, 84), 'Pink1', 218) plum1 = Colors.register(RGB(255, 175, 255), HLS(100, 300, 84), 'Plum1', 219) gold1 = Colors.register(RGB(255, 215, 0), HLS(100, 0, 50), 'Gold1', 220) lightGoldenrod2 = Colors.register( - RGB(255, 215, 95), - HLS(100, 45, 68), - 'LightGoldenrod2', - 221 + RGB(255, 215, 95), HLS(100, 45, 68), 'LightGoldenrod2', 221 ) lightGoldenrod2 = Colors.register( - RGB(255, 215, 135), - HLS(100, 40, 76), - 'LightGoldenrod2', - 222 + RGB(255, 215, 135), HLS(100, 40, 76), 'LightGoldenrod2', 222 ) navajoWhite1 = Colors.register( - RGB(255, 215, 175), - HLS(100, 30, 84), - 'NavajoWhite1', - 223 + RGB(255, 215, 175), HLS(100, 30, 84), 'NavajoWhite1', 223 ) mistyRose1 = Colors.register( - RGB(255, 215, 215), - HLS(100, 0, 92), - 'MistyRose1', - 224 + RGB(255, 215, 215), HLS(100, 0, 92), 'MistyRose1', 224 ) thistle1 = Colors.register( - RGB(255, 215, 255), - HLS(100, 300, 92), - 'Thistle1', - 225 + RGB(255, 215, 255), HLS(100, 300, 92), 'Thistle1', 225 ) yellow1 = Colors.register(RGB(255, 255, 0), HLS(100, 60, 50), 'Yellow1', 226) lightGoldenrod1 = Colors.register( - RGB(255, 255, 95), - HLS(100, 60, 68), - 'LightGoldenrod1', - 227 + RGB(255, 255, 95), HLS(100, 60, 68), 'LightGoldenrod1', 227 ) khaki1 = Colors.register(RGB(255, 255, 135), HLS(100, 60, 76), 'Khaki1', 228) wheat1 = Colors.register(RGB(255, 255, 175), HLS(100, 60, 84), 'Wheat1', 229) cornsilk1 = Colors.register( - RGB(255, 255, 215), - HLS(100, 60, 92), - 'Cornsilk1', - 230 + RGB(255, 255, 215), HLS(100, 60, 92), 'Cornsilk1', 230 ) grey100 = Colors.register(RGB(255, 255, 255), HLS(0, 0, 100), 'Grey100', 231) grey3 = Colors.register(RGB(8, 8, 8), HLS(0, 0, 3), 'Grey3', 232) diff --git a/progressbar/terminal/os_specific/__init__.py b/progressbar/terminal/os_specific/__init__.py index 782ca9cd..4cff9feb 100644 --- a/progressbar/terminal/os_specific/__init__.py +++ b/progressbar/terminal/os_specific/__init__.py @@ -4,7 +4,7 @@ from .windows import ( getch as _getch, set_console_mode as _set_console_mode, - reset_console_mode as _reset_console_mode + reset_console_mode as _reset_console_mode, ) else: @@ -13,7 +13,6 @@ def _reset_console_mode(): pass - def _set_console_mode(): pass @@ -21,4 +20,3 @@ def _set_console_mode(): getch = _getch reset_console_mode = _reset_console_mode set_console_mode = _set_console_mode - diff --git a/progressbar/terminal/os_specific/windows.py b/progressbar/terminal/os_specific/windows.py index edac0696..6084f3b1 100644 --- a/progressbar/terminal/os_specific/windows.py +++ b/progressbar/terminal/os_specific/windows.py @@ -7,7 +7,7 @@ UINT as _UINT, WCHAR as _WCHAR, CHAR as _CHAR, - SHORT as _SHORT + SHORT as _SHORT, ) _kernel32 = ctypes.windll.Kernel32 @@ -43,24 +43,16 @@ class _COORD(ctypes.Structure): - _fields_ = [ - ('X', _SHORT), - ('Y', _SHORT) - ] + _fields_ = [('X', _SHORT), ('Y', _SHORT)] class _FOCUS_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('bSetFocus', _BOOL) - ] + _fields_ = [('bSetFocus', _BOOL)] class _KEY_EVENT_RECORD(ctypes.Structure): class _uchar(ctypes.Union): - _fields_ = [ - ('UnicodeChar', _WCHAR), - ('AsciiChar', _CHAR) - ] + _fields_ = [('UnicodeChar', _WCHAR), ('AsciiChar', _CHAR)] _fields_ = [ ('bKeyDown', _BOOL), @@ -68,14 +60,12 @@ class _uchar(ctypes.Union): ('wVirtualKeyCode', _WORD), ('wVirtualScanCode', _WORD), ('uChar', _uchar), - ('dwControlKeyState', _DWORD) + ('dwControlKeyState', _DWORD), ] class _MENU_EVENT_RECORD(ctypes.Structure): - _fields_ = [ - ('dwCommandId', _UINT) - ] + _fields_ = [('dwCommandId', _UINT)] class _MOUSE_EVENT_RECORD(ctypes.Structure): @@ -83,14 +73,12 @@ class _MOUSE_EVENT_RECORD(ctypes.Structure): ('dwMousePosition', _COORD), ('dwButtonState', _DWORD), ('dwControlKeyState', _DWORD), - ('dwEventFlags', _DWORD) + ('dwEventFlags', _DWORD), ] class _WINDOW_BUFFER_SIZE_RECORD(ctypes.Structure): - _fields_ = [ - ('dwSize', _COORD) - ] + _fields_ = [('dwSize', _COORD)] class _INPUT_RECORD(ctypes.Structure): @@ -100,13 +88,10 @@ class _Event(ctypes.Union): ('MouseEvent', _MOUSE_EVENT_RECORD), ('WindowBufferSizeEvent', _WINDOW_BUFFER_SIZE_RECORD), ('MenuEvent', _MENU_EVENT_RECORD), - ('FocusEvent', _FOCUS_EVENT_RECORD) + ('FocusEvent', _FOCUS_EVENT_RECORD), ] - _fields_ = [ - ('EventType', _WORD), - ('Event', _Event) - ] + _fields_ = [('EventType', _WORD), ('Event', _Event)] def reset_console_mode(): @@ -119,9 +104,9 @@ def set_console_mode(): _SetConsoleMode(_HANDLE(_hConsoleInput), _DWORD(mode)) mode = ( - _output_mode.value | - _ENABLE_PROCESSED_OUTPUT | - _ENABLE_VIRTUAL_TERMINAL_PROCESSING + _output_mode.value + | _ENABLE_PROCESSED_OUTPUT + | _ENABLE_VIRTUAL_TERMINAL_PROCESSING ) _SetConsoleMode(_HANDLE(_hConsoleOutput), _DWORD(mode)) @@ -133,8 +118,9 @@ def getch(): _ReadConsoleInput( _HANDLE(_hConsoleInput), - lpBuffer, nLength, - ctypes.byref(lpNumberOfEventsRead) + lpBuffer, + nLength, + ctypes.byref(lpNumberOfEventsRead), ) char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index c0ccff4c..ecf8a6d3 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -8,7 +8,6 @@ class TextIOOutputWrapper(base.TextIO): - def __init__(self, stream: base.TextIO): self.stream = stream @@ -64,7 +63,7 @@ def __exit__( self, __t: Type[BaseException] | None, __value: BaseException | None, - __traceback: TracebackType | None + __traceback: TracebackType | None, ) -> None: return self.stream.__exit__(__t, __value, __traceback) @@ -94,7 +93,6 @@ def write(self, data): class LastLineStream(TextIOOutputWrapper): - line: str = '' def seekable(self) -> bool: diff --git a/progressbar/widgets.py b/progressbar/widgets.py index 57264ed5..fc09f5ba 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -252,11 +252,7 @@ def _apply_colors(self, text: str, data: Data) -> str: return text def __init__( - self, - *args, - fixed_colors=None, - gradient_colors=None, - **kwargs + self, *args, fixed_colors=None, gradient_colors=None, **kwargs ): if fixed_colors is not None: self._fixed_colors.update(fixed_colors) @@ -400,9 +396,8 @@ def __init__( ): self.samples = samples self.key_prefix = ( - key_prefix if key_prefix else - self.__class__.__name__ - ) + '_' + key_prefix if key_prefix else self.__class__.__name__ + ) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): @@ -465,7 +460,6 @@ def __init__( format_NA='ETA: N/A', **kwargs, ): - if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') @@ -819,6 +813,7 @@ def get_format( class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): '''Returns progress as a count of the total (e.g.: "5 of 47")''' + max_width_cache: dict[ types.Union[str, tuple[float, float | types.Type[base.UnknownLength]]], types.Optional[int], @@ -882,6 +877,7 @@ def __call__( class Bar(AutoWidthWidgetBase): '''A progress bar which stretches to fill the line.''' + fg: terminal.OptionalColor | None = colors.gradient bg: terminal.OptionalColor | None = None @@ -1259,12 +1255,19 @@ def __call__( # type: ignore center_left = int((width - center_len) / 2) center_right = center_left + center_len - return self._apply_colors( - bar[:center_left], data, - ) + self._apply_colors( - center, data, - ) + self._apply_colors( - bar[center_right:], data, + return ( + self._apply_colors( + bar[:center_left], + data, + ) + + self._apply_colors( + center, + data, + ) + + self._apply_colors( + bar[center_right:], + data, + ) ) diff --git a/tests/test_color.py b/tests/test_color.py index 3b5f5a15..e1b2c487 100644 --- a/tests/test_color.py +++ b/tests/test_color.py @@ -25,6 +25,7 @@ def test_color_environment_variables(monkeypatch, variable): bar = progressbar.ProgressBar() assert not bar.enable_colors + def test_enable_colors_flags(): bar = progressbar.ProgressBar(enable_colors=True) assert bar.enable_colors @@ -32,7 +33,9 @@ def test_enable_colors_flags(): bar = progressbar.ProgressBar(enable_colors=False) assert not bar.enable_colors - bar = progressbar.ProgressBar(enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR) + bar = progressbar.ProgressBar( + enable_colors=terminal.ColorSupport.XTERM_TRUECOLOR + ) assert bar.enable_colors with pytest.raises(ValueError): From 6b0e48e8916c4c239dd9ff5a3bf7a682230cadcd Mon Sep 17 00:00:00 2001 From: Rick van Hattem Date: Sat, 18 Mar 2023 12:25:49 +0100 Subject: [PATCH 23/24] Python 3.8 compatible --- progressbar/multi.py | 2 +- progressbar/terminal/base.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/progressbar/multi.py b/progressbar/multi.py index dff82d60..7922a812 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -38,7 +38,7 @@ class SortKey(str, enum.Enum): PERCENTAGE = 'percentage' -class MultiBar(dict[str, bar.ProgressBar]): +class MultiBar(typing.Dict[str, bar.ProgressBar]): fd: typing.TextIO _buffer: io.StringIO diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index b31e902e..469e37fa 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -450,7 +450,7 @@ def get_color(self, value: float) -> Color: return color -OptionalColor = Color | ColorGradient | None +OptionalColor = types.Union[Color, ColorGradient, None] def get_color(value: float, color: OptionalColor) -> Color | None: From a81ba837a969ccea97c3f12a680f5cc866602446 Mon Sep 17 00:00:00 2001 From: Sourcery AI Date: Tue, 25 Apr 2023 20:45:22 +0000 Subject: [PATCH 24/24] 'Refactored by Sourcery' --- docs/conf.py | 37 ++++++--- examples.py | 67 ++++++++-------- progressbar/bar.py | 51 ++++-------- progressbar/multi.py | 7 +- progressbar/shortcuts.py | 3 +- progressbar/terminal/base.py | 5 +- progressbar/terminal/os_specific/windows.py | 5 +- progressbar/terminal/stream.py | 6 +- progressbar/utils.py | 49 ++++++------ progressbar/widgets.py | 86 ++++++--------------- setup.py | 2 +- tests/original_examples.py | 36 +++++---- tests/test_custom_widgets.py | 3 +- tests/test_end.py | 2 +- tests/test_failure.py | 2 +- tests/test_iterators.py | 10 +-- tests/test_monitor_progress.py | 10 +-- tests/test_multibar.py | 4 +- tests/test_stream.py | 8 +- tests/test_timed.py | 5 +- tests/test_unicode.py | 4 +- tests/test_unknown_length.py | 2 +- tests/test_widgets.py | 10 +-- 23 files changed, 179 insertions(+), 235 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 15d5ba34..df9ef70e 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,10 +61,7 @@ # General information about the project. project = u'Progress Bar' project_slug = ''.join(project.capitalize().split()) -copyright = u'%s, %s' % ( - datetime.date.today().year, - metadata.__author__, -) +copyright = f'{datetime.date.today().year}, {metadata.__author__}' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -190,7 +187,7 @@ # html_file_suffix = None # Output file base name for HTML help builder. -htmlhelp_basename = '%sdoc' % project_slug +htmlhelp_basename = f'{project_slug}doc' # -- Options for LaTeX output -------------------------------------------- @@ -209,8 +206,13 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', '%s.tex' % project_slug, u'%s Documentation' % project, - metadata.__author__, 'manual'), + ( + 'index', + f'{project_slug}.tex', + f'{project} Documentation', + metadata.__author__, + 'manual', + ) ] # The name of an image file (relative to this directory) to place at the top of @@ -239,8 +241,13 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', project_slug.lower(), u'%s Documentation' % project, - [metadata.__author__], 1) + ( + 'index', + project_slug.lower(), + f'{project} Documentation', + [metadata.__author__], + 1, + ) ] # If true, show URL addresses after external links. @@ -253,9 +260,15 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', project_slug, u'%s Documentation' % project, - metadata.__author__, project_slug, 'One line description of project.', - 'Miscellaneous'), + ( + 'index', + project_slug, + f'{project} Documentation', + metadata.__author__, + project_slug, + 'One line description of project.', + 'Miscellaneous', + ) ] # Documents to append as an appendix to all manuals. diff --git a/examples.py b/examples.py index 1c033839..7fb48efe 100644 --- a/examples.py +++ b/examples.py @@ -39,19 +39,19 @@ def fast_example(): @example def shortcut_example(): - for i in progressbar.progressbar(range(10)): + for _ in progressbar.progressbar(range(10)): time.sleep(0.1) @example def prefixed_shortcut_example(): - for i in progressbar.progressbar(range(10), prefix='Hi: '): + for _ in progressbar.progressbar(range(10), prefix='Hi: '): time.sleep(0.1) @example def templated_shortcut_example(): - for i in progressbar.progressbar(range(10), suffix='{seconds_elapsed:.1}'): + for _ in progressbar.progressbar(range(10), suffix='{seconds_elapsed:.1}'): time.sleep(0.1) @@ -142,18 +142,14 @@ def multi_range_bar_example(): @example def multi_progress_bar_example(left=True): - jobs = [ - # Each job takes between 1 and 10 steps to complete - [0, random.randint(1, 10)] - for i in range(25) # 25 jobs total - ] + jobs = [[0, random.randint(1, 10)] for _ in range(25)] widgets = [ progressbar.Percentage(), ' ', progressbar.MultiProgressBar('jobs', fill_left=left), ] - max_value = sum([total for progress, total in jobs]) + max_value = sum(total for progress, total in jobs) with progressbar.ProgressBar(widgets=widgets, max_value=max_value) as bar: while True: incomplete_jobs = [ @@ -165,7 +161,7 @@ def multi_progress_bar_example(left=True): break which = random.choice(incomplete_jobs) jobs[which][0] += 1 - progress = sum([progress for progress, total in jobs]) + progress = sum(progress for progress, total in jobs) bar.update(progress, jobs=jobs, force=True) time.sleep(0.02) @@ -181,10 +177,10 @@ def granular_progress_example(): progressbar.GranularBar(markers=" ⡀⡄⡆⡇⣇⣧⣷⣿", left='', right='|'), progressbar.GranularBar(markers=" .oO", left='', right=''), ] - for i in progressbar.progressbar(list(range(100)), widgets=widgets): + for _ in progressbar.progressbar(list(range(100)), widgets=widgets): time.sleep(0.03) - for i in progressbar.progressbar(iter(range(100)), widgets=widgets): + for _ in progressbar.progressbar(iter(range(100)), widgets=widgets): time.sleep(0.03) @@ -216,17 +212,20 @@ def file_transfer_example(): @example def custom_file_transfer_example(): + + + class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): ''' It's bigger between 45 and 80 percent ''' def update(self, bar): if 45 < bar.percentage() < 80: - return 'Bigger Now ' + progressbar.FileTransferSpeed.update( - self, bar) + return f'Bigger Now {progressbar.FileTransferSpeed.update(self, bar)}' else: return progressbar.FileTransferSpeed.update(self, bar) + widgets = [ CrazyFileTransferSpeed(), ' <<<', progressbar.Bar(), '>>> ', @@ -300,7 +299,7 @@ def basic_progress(): def progress_with_automatic_max(): # Progressbar can guess max_value automatically. bar = progressbar.ProgressBar() - for i in bar(range(8)): + for _ in bar(range(8)): time.sleep(0.1) @@ -308,7 +307,7 @@ def progress_with_automatic_max(): def progress_with_unavailable_max(): # Progressbar can't guess max_value. bar = progressbar.ProgressBar(max_value=8) - for i in bar((i for i in range(8))): + for _ in bar(iter(range(8))): time.sleep(0.1) @@ -316,7 +315,7 @@ def progress_with_unavailable_max(): def animated_marker(): bar = progressbar.ProgressBar( widgets=['Working: ', progressbar.AnimatedMarker()]) - for i in bar((i for i in range(5))): + for _ in bar(iter(range(5))): time.sleep(0.1) @@ -327,7 +326,7 @@ def filling_bar_animated_marker(): marker=progressbar.AnimatedMarker(fill='#'), ), ]) - for i in bar(range(15)): + for _ in bar(range(15)): time.sleep(0.1) @@ -336,7 +335,7 @@ def counter_and_timer(): widgets = ['Processed: ', progressbar.Counter('Counter: %(value)05d'), ' lines (', progressbar.Timer(), ')'] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(15))): + for _ in bar(iter(range(15))): time.sleep(0.1) @@ -345,7 +344,7 @@ def format_label(): widgets = [progressbar.FormatLabel( 'Processed: %(value)d lines (in: %(elapsed)s)')] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(15))): + for _ in bar(iter(range(15))): time.sleep(0.1) @@ -353,7 +352,7 @@ def format_label(): def animated_balloons(): widgets = ['Balloon: ', progressbar.AnimatedMarker(markers='.oO@* ')] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(24))): + for _ in bar(iter(range(24))): time.sleep(0.1) @@ -363,7 +362,7 @@ def animated_arrows(): try: widgets = ['Arrows: ', progressbar.AnimatedMarker(markers='←↖↑↗→↘↓↙')] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(24))): + for _ in bar(iter(range(24))): time.sleep(0.1) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -375,7 +374,7 @@ def animated_filled_arrows(): try: widgets = ['Arrows: ', progressbar.AnimatedMarker(markers='◢◣◤◥')] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(24))): + for _ in bar(iter(range(24))): time.sleep(0.1) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -387,7 +386,7 @@ def animated_wheels(): try: widgets = ['Wheels: ', progressbar.AnimatedMarker(markers='◐◓◑◒')] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(24))): + for _ in bar(iter(range(24))): time.sleep(0.1) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -400,7 +399,7 @@ def format_label_bouncer(): progressbar.BouncingBar(), ] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(100))): + for _ in bar(iter(range(100))): time.sleep(0.01) @@ -410,7 +409,7 @@ def format_label_rotating_bouncer(): progressbar.BouncingBar(marker=progressbar.RotatingMarker())] bar = progressbar.ProgressBar(widgets=widgets) - for i in bar((i for i in range(18))): + for _ in bar(iter(range(18))): time.sleep(0.1) @@ -488,7 +487,7 @@ def incrementing_bar(): progressbar.Percentage(), progressbar.Bar(), ], max_value=10).start() - for i in range(10): + for _ in range(10): # do something time.sleep(0.1) bar += 1 @@ -545,7 +544,7 @@ def adaptive_eta_without_value_change(): progressbar.AdaptiveTransferSpeed(), ], max_value=2, poll_interval=0.0001) bar.start() - for i in range(100): + for _ in range(100): bar.update(1) time.sleep(0.1) bar.finish() @@ -556,7 +555,7 @@ def iterator_with_max_value(): # Testing using progressbar as an iterator with a max value bar = progressbar.ProgressBar() - for n in bar(iter(range(100)), 100): + for _ in bar(iter(range(100)), 100): # iter range is a way to get an iterator in both python 2 and 3 pass @@ -622,12 +621,12 @@ def user_variables(): num_subtasks = sum(len(x) for x in tasks.values()) with progressbar.ProgressBar( - prefix='{variables.task} >> {variables.subtask}', - variables={'task': '--', 'subtask': '--'}, - max_value=10 * num_subtasks) as bar: + prefix='{variables.task} >> {variables.subtask}', + variables={'task': '--', 'subtask': '--'}, + max_value=10 * num_subtasks) as bar: for tasks_name, subtasks in tasks.items(): for subtask_name in subtasks: - for i in range(10): + for _ in range(10): bar.update(bar.value + 1, task=tasks_name, subtask=subtask_name) time.sleep(0.1) @@ -656,7 +655,7 @@ def format_custom_text(): @example def simple_api_example(): bar = progressbar.ProgressBar(widget_kwargs=dict(fill='█')) - for i in bar(range(200)): + for _ in bar(range(200)): time.sleep(0.02) diff --git a/progressbar/bar.py b/progressbar/bar.py index d9616f68..01831fd6 100644 --- a/progressbar/bar.py +++ b/progressbar/bar.py @@ -93,10 +93,7 @@ def get_last_update_time(self) -> types.Optional[datetime]: return None def set_last_update_time(self, value: types.Optional[datetime]): - if value: - self._last_update_time = time.mktime(value.timetuple()) - else: - self._last_update_time = None + self._last_update_time = time.mktime(value.timetuple()) if value else None last_update_time = property(get_last_update_time, set_last_update_time) @@ -222,10 +219,7 @@ def __init__( enable_colors = terminal.ColorSupport.XTERM_256 elif enable_colors is False: enable_colors = terminal.ColorSupport.NONE - elif isinstance(enable_colors, terminal.ColorSupport): - # `enable_colors` is already a valid value - pass - else: + elif not isinstance(enable_colors, terminal.ColorSupport): raise ValueError(f'Invalid color support value: {enable_colors}') self.enable_colors = enable_colors @@ -242,11 +236,7 @@ def update(self, *args, **kwargs): if not self.enable_colors: line = utils.no_color(line) - if self.line_breaks: - line = line.rstrip() + '\n' - else: - line = '\r' + line - + line = line.rstrip() + '\n' if self.line_breaks else '\r' + line try: # pragma: no cover self.fd.write(line) except UnicodeEncodeError: # pragma: no cover @@ -530,13 +520,10 @@ def __init__( ) poll_interval = kwargs.get('poll') - if max_value: - # mypy doesn't understand that a boolean check excludes - # `UnknownLength` - if min_value > max_value: # type: ignore - raise ValueError( - 'Max value needs to be bigger than the min ' 'value' - ) + if max_value and min_value > max_value: + raise ValueError( + 'Max value needs to be bigger than the min ' 'value' + ) self.min_value = min_value # Legacy issue, `max_value` can be `None` before execution. After # that it either has a value or is `UnknownLength` @@ -589,9 +576,11 @@ def __init__( # A dictionary of names that can be used by Variable and FormatWidget self.variables = utils.AttributeDict(variables or {}) for widget in self.widgets: - if isinstance(widget, widgets_module.VariableMixin): - if widget.name not in self.variables: - self.variables[widget.name] = None + if ( + isinstance(widget, widgets_module.VariableMixin) + and widget.name not in self.variables + ): + self.variables[widget.name] = None @property def dynamic_messages(self): # pragma: no cover @@ -611,7 +600,7 @@ def init(self): self.start_time = None self.updates = 0 self.end_time = None - self.extra = dict() + self.extra = {} self._last_update_timer = timeit.default_timer() @property @@ -734,7 +723,7 @@ def default_widgets(self): widgets.Percentage(**self.widget_kwargs), ' ', widgets.SimpleProgress( - format='(%s)' % widgets.SimpleProgress.DEFAULT_FORMAT, + format=f'({widgets.SimpleProgress.DEFAULT_FORMAT})', **self.widget_kwargs, ), ' ', @@ -773,11 +762,7 @@ def __iter__(self): def __next__(self): try: - if self._iterable is None: # pragma: no cover - value = self.value - else: - value = next(self._iterable) - + value = self.value if self._iterable is None else next(self._iterable) if self.start_time is None: self.start() else: @@ -853,14 +838,12 @@ def update(self, value=None, force=False, **kwargs): pass elif self.min_value > value: # type: ignore raise ValueError( - 'Value %s is too small. Should be between %s and %s' - % (value, self.min_value, self.max_value) + f'Value {value} is too small. Should be between {self.min_value} and {self.max_value}' ) elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( - 'Value %s is too large. Should be between %s and %s' - % (value, self.min_value, self.max_value) + f'Value {value} is too large. Should be between {self.min_value} and {self.max_value}' ) else: value = self.max_value diff --git a/progressbar/multi.py b/progressbar/multi.py index 7922a812..087083dc 100644 --- a/progressbar/multi.py +++ b/progressbar/multi.py @@ -187,10 +187,9 @@ def update(force=True, write=True): # Force update to get the finished format update(write=False) - if self.remove_finished: - if expired >= self._finished_at[bar_]: - del self[bar_.label] - continue + if self.remove_finished and expired >= self._finished_at[bar_]: + del self[bar_.label] + continue if not self.show_finished: continue diff --git a/progressbar/shortcuts.py b/progressbar/shortcuts.py index 9e1502dd..eb30b715 100644 --- a/progressbar/shortcuts.py +++ b/progressbar/shortcuts.py @@ -19,5 +19,4 @@ def progressbar( **kwargs ) - for result in progressbar(iterator): - yield result + yield from progressbar(iterator) diff --git a/progressbar/terminal/base.py b/progressbar/terminal/base.py index 469e37fa..8df2e75a 100644 --- a/progressbar/terminal/base.py +++ b/progressbar/terminal/base.py @@ -213,10 +213,7 @@ def __call__(self, stream): res = tuple(int(item) if item.isdigit() else item for item in res) - if len(res) == 1: - return res[0] - - return res + return res[0] if len(res) == 1 else res def row(self, stream): row, _ = self(stream) diff --git a/progressbar/terminal/os_specific/windows.py b/progressbar/terminal/os_specific/windows.py index 6084f3b1..c611d417 100644 --- a/progressbar/terminal/os_specific/windows.py +++ b/progressbar/terminal/os_specific/windows.py @@ -124,7 +124,4 @@ def getch(): ) char = lpBuffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') - if char == '\x00': - return None - - return char + return None if char == '\x00' else char diff --git a/progressbar/terminal/stream.py b/progressbar/terminal/stream.py index ecf8a6d3..b4c3f233 100644 --- a/progressbar/terminal/stream.py +++ b/progressbar/terminal/stream.py @@ -111,11 +111,7 @@ def write(self, data): self.line = data def truncate(self, __size: int | None = None) -> int: - if __size is None: - self.line = '' - else: - self.line = self.line[:__size] - + self.line = '' if __size is None else self.line[:__size] return len(self.line) def writelines(self, __lines: Iterable[str]) -> None: diff --git a/progressbar/utils.py b/progressbar/utils.py index 256dd98c..d1d0d00b 100644 --- a/progressbar/utils.py +++ b/progressbar/utils.py @@ -39,7 +39,7 @@ 'tmux', 'vt(10[02]|220|320)', ) -ANSI_TERM_RE = re.compile('^({})'.format('|'.join(ANSI_TERMS)), re.IGNORECASE) +ANSI_TERM_RE = re.compile(f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE) def is_ansi_terminal( @@ -231,8 +231,7 @@ def flush(self) -> None: self.buffer.flush() def _flush(self) -> None: - value = self.buffer.getvalue() - if value: + if value := self.buffer.getvalue(): self.flush() self.target.write(value) self.buffer.seek(0) @@ -438,27 +437,25 @@ def needs_clear(self) -> bool: # pragma: no cover return stderr_needs_clear or stdout_needs_clear def flush(self) -> None: - if self.wrapped_stdout: # pragma: no branch - if isinstance(self.stdout, WrappingIO): # pragma: no branch - try: - self.stdout._flush() - except io.UnsupportedOperation: # pragma: no cover - self.wrapped_stdout = False - logger.warning( - 'Disabling stdout redirection, %r is not seekable', - sys.stdout, - ) - - if self.wrapped_stderr: # pragma: no branch - if isinstance(self.stderr, WrappingIO): # pragma: no branch - try: - self.stderr._flush() - except io.UnsupportedOperation: # pragma: no cover - self.wrapped_stderr = False - logger.warning( - 'Disabling stderr redirection, %r is not seekable', - sys.stderr, - ) + if self.wrapped_stdout and isinstance(self.stdout, WrappingIO): + try: + self.stdout._flush() + except io.UnsupportedOperation: # pragma: no cover + self.wrapped_stdout = False + logger.warning( + 'Disabling stdout redirection, %r is not seekable', + sys.stdout, + ) + + if self.wrapped_stderr and isinstance(self.stderr, WrappingIO): + try: + self.stderr._flush() + except io.UnsupportedOperation: # pragma: no cover + self.wrapped_stderr = False + logger.warning( + 'Disabling stderr redirection, %r is not seekable', + sys.stderr, + ) def excepthook(self, exc_type, exc_value, exc_traceback): self.original_excepthook(exc_type, exc_value, exc_traceback) @@ -515,7 +512,7 @@ def __getattr__(self, name: str) -> int: if name in self: return self[name] else: - raise AttributeError("No such attribute: " + name) + raise AttributeError(f"No such attribute: {name}") def __setattr__(self, name: str, value: int) -> None: self[name] = value @@ -524,7 +521,7 @@ def __delattr__(self, name: str) -> None: if name in self: del self[name] else: - raise AttributeError("No such attribute: " + name) + raise AttributeError(f"No such attribute: {name}") logger = logging.getLogger(__name__) diff --git a/progressbar/widgets.py b/progressbar/widgets.py index fc09f5ba..199e3fab 100644 --- a/progressbar/widgets.py +++ b/progressbar/widgets.py @@ -138,10 +138,7 @@ def __call__( '''Formats the widget into a string''' format = self.get_format(progress, data, format) try: - if self.new_style: - return format.format(**data) - else: - return format % data + return format.format(**data) if self.new_style else format % data except (TypeError, KeyError): print('Error while formatting %r' % format, file=sys.stderr) pprint.pprint(data, stream=sys.stderr) @@ -234,11 +231,7 @@ def uses_colors(self): if value is not None: return True - for key, value in self._fixed_colors.items(): - if value is not None: - return True - - return False + return any(value is not None for key, value in self._fixed_colors.items()) def _apply_colors(self, text: str, data: Data) -> str: if self.uses_colors: @@ -332,10 +325,7 @@ def __call__( ): for name, (key, transform) in self.mapping.items(): try: - if transform is None: - data[name] = data[key] - else: - data[name] = transform(data[key]) + data[name] = data[key] if transform is None else transform(data[key]) except (KeyError, ValueError, IndexError): # pragma: no cover pass @@ -401,10 +391,10 @@ def __init__( TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): - return progress.extra.setdefault(self.key_prefix + 'sample_times', []) + return progress.extra.setdefault(f'{self.key_prefix}sample_times', []) def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): - return progress.extra.setdefault(self.key_prefix + 'sample_values', []) + return progress.extra.setdefault(f'{self.key_prefix}sample_values', []) def __call__( self, progress: ProgressBarMixinBase, data: Data, delta: bool = False @@ -412,11 +402,7 @@ def __call__( sample_times = self.get_sample_times(progress, data) sample_values = self.get_sample_values(progress, data) - if sample_times: - sample_time = sample_times[-1] - else: - sample_time = datetime.datetime.min - + sample_time = sample_times[-1] if sample_times else datetime.datetime.min if progress.last_update_time - sample_time > self.INTERVAL: # Add a sample but limit the size to `num_samples` sample_times.append(progress.last_update_time) @@ -432,20 +418,15 @@ def __call__( ): sample_times.pop(0) sample_values.pop(0) - else: - if len(sample_times) > self.samples: - sample_times.pop(0) - sample_values.pop(0) + elif len(sample_times) > self.samples: + sample_times.pop(0) + sample_values.pop(0) - if delta: - delta_time = sample_times[-1] - sample_times[0] - delta_value = sample_values[-1] - sample_values[0] - if delta_time: - return delta_time, delta_value - else: - return None, None - else: + if not delta: return sample_times, sample_values + delta_time = sample_times[-1] - sample_times[0] + delta_value = sample_values[-1] - sample_values[0] + return (delta_time, delta_value) if delta_time else (None, None) class ETA(Timer): @@ -474,15 +455,12 @@ def _calculate_eta( self, progress: ProgressBarMixinBase, data: Data, value, elapsed ): '''Updates the widget to show the ETA or total time when finished.''' - if elapsed: - # The max() prevents zero division errors - per_item = elapsed.total_seconds() / max(value, 1e-6) - remaining = progress.max_value - data['value'] - eta_seconds = remaining * per_item - else: - eta_seconds = 0 + if not elapsed: + return 0 - return eta_seconds + # The max() prevents zero division errors + per_item = elapsed.total_seconds() / max(value, 1e-6) + return (progress.max_value - data['value']) * per_item def __call__( self, @@ -824,24 +802,15 @@ class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): def __init__(self, format=DEFAULT_FORMAT, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) - self.max_width_cache = dict() - self.max_width_cache['default'] = self.max_width or 0 + self.max_width_cache = {'default': self.max_width or 0} def __call__( self, progress: ProgressBarMixinBase, data: Data, format=None ): # If max_value is not available, display N/A - if data.get('max_value'): - data['max_value_s'] = data['max_value'] - else: - data['max_value_s'] = 'N/A' - + data['max_value_s'] = data['max_value'] if data.get('max_value') else 'N/A' # if value is not available it's the zeroth iteration - if data.get('value'): - data['value_s'] = data['value'] - else: - data['value_s'] = 0 - + data['value_s'] = data['value'] if data.get('value') else 0 formatted = FormatWidgetMixin.__call__( self, progress, data, format=format ) @@ -858,12 +827,11 @@ def __call__( continue temporary_data['value'] = value - width = progress.custom_len( + if width := progress.custom_len( FormatWidgetMixin.__call__( self, progress, temporary_data, format=format ) - ) - if width: # pragma: no branch + ): max_width = max(max_width or 0, width) self.max_width_cache[key] = max_width @@ -1131,10 +1099,7 @@ def get_values(self, progress: ProgressBarMixinBase, data: Data): value = float(progress_value) / float(progress_max) if not 0 <= value <= 1: - raise ValueError( - 'Range value needs to be in the range [0..1], got %s' - % value - ) + raise ValueError(f'Range value needs to be in the range [0..1], got {value}') range_ = value * (len(ranges) - 1) pos = int(range_) @@ -1220,8 +1185,7 @@ def __call__( marker = self.markers[-1] * int(num_chars) - marker_idx = int((num_chars % 1) * (len(self.markers) - 1)) - if marker_idx: + if marker_idx := int((num_chars % 1) * (len(self.markers) - 1)): marker += self.markers[marker_idx] marker = converters.to_unicode(marker) diff --git a/setup.py b/setup.py index 850df2de..ab559f5c 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ install_reqs = [] if sys.argv[-1] == 'info': for k, v in about.items(): - print('%s: %s' % (k, v)) + print(f'{k}: {v}') sys.exit() if os.path.isfile('README.rst'): diff --git a/tests/original_examples.py b/tests/original_examples.py index 2e521e9d..f85c1311 100644 --- a/tests/original_examples.py +++ b/tests/original_examples.py @@ -45,14 +45,18 @@ def example1(): @example def example2(): + + + class CrazyFileTransferSpeed(FileTransferSpeed): """It's bigger between 45 and 80 percent.""" def update(self, pbar): if 45 < pbar.percentage() < 80: - return 'Bigger Now ' + FileTransferSpeed.update(self,pbar) + return f'Bigger Now {FileTransferSpeed.update(self, pbar)}' else: return FileTransferSpeed.update(self,pbar) + widgets = [CrazyFileTransferSpeed(),' <<<', Bar(), '>>> ', Percentage(),' ', ETA()] pbar = ProgressBar(widgets=widgets, maxval=10000) @@ -103,40 +107,40 @@ def example6(): @example def example7(): pbar = ProgressBar() # Progressbar can guess maxval automatically. - for i in pbar(range(80)): + for _ in pbar(range(80)): time.sleep(0.01) @example def example8(): pbar = ProgressBar(maxval=80) # Progressbar can't guess maxval. - for i in pbar((i for i in range(80))): + for _ in pbar(iter(range(80))): time.sleep(0.01) @example def example9(): pbar = ProgressBar(widgets=['Working: ', AnimatedMarker()]) - for i in pbar((i for i in range(50))): + for _ in pbar(iter(range(50))): time.sleep(.08) @example def example10(): widgets = ['Processed: ', Counter(), ' lines (', Timer(), ')'] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(150))): + for _ in pbar(iter(range(150))): time.sleep(0.1) @example def example11(): widgets = [FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(150))): + for _ in pbar(iter(range(150))): time.sleep(0.1) @example def example12(): widgets = ['Balloon: ', AnimatedMarker(markers='.oO@* ')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _ in pbar(iter(range(24))): time.sleep(0.3) @example @@ -145,7 +149,7 @@ def example13(): try: widgets = ['Arrows: ', AnimatedMarker(markers='←↖↑↗→↘↓↙')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _ in pbar(iter(range(24))): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -155,7 +159,7 @@ def example14(): try: widgets = ['Arrows: ', AnimatedMarker(markers='◢◣◤◥')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _ in pbar(iter(range(24))): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -165,7 +169,7 @@ def example15(): try: widgets = ['Wheels: ', AnimatedMarker(markers='◐◓◑◒')] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(24))): + for _ in pbar(iter(range(24))): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @@ -173,7 +177,7 @@ def example15(): def example16(): widgets = [FormatLabel('Bouncer: value %(value)d - '), BouncingBar()] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(180))): + for _ in pbar(iter(range(180))): time.sleep(0.05) @example @@ -182,7 +186,7 @@ def example17(): BouncingBar(marker=RotatingMarker())] pbar = ProgressBar(widgets=widgets) - for i in pbar((i for i in range(180))): + for _ in pbar(iter(range(180))): time.sleep(0.05) @example @@ -200,10 +204,10 @@ def example18(): @example def example19(): - pbar = ProgressBar() - for i in pbar([]): - pass - pbar.finish() + pbar = ProgressBar() + for _ in pbar([]): + pass + pbar.finish() @example def example20(): diff --git a/tests/test_custom_widgets.py b/tests/test_custom_widgets.py index e757ded5..22b1e66b 100644 --- a/tests/test_custom_widgets.py +++ b/tests/test_custom_widgets.py @@ -10,8 +10,7 @@ class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): def update(self, pbar): if 45 < pbar.percentage() < 80: - return 'Bigger Now ' + progressbar.FileTransferSpeed.update(self, - pbar) + return f'Bigger Now {progressbar.FileTransferSpeed.update(self, pbar)}' else: return progressbar.FileTransferSpeed.update(self, pbar) diff --git a/tests/test_end.py b/tests/test_end.py index 75d45723..42842a2e 100644 --- a/tests/test_end.py +++ b/tests/test_end.py @@ -37,7 +37,7 @@ def test_end_100(monkeypatch): max_value=103, ) - for x in range(0, 102): + for x in range(102): p.update(x) data = p.data() diff --git a/tests/test_failure.py b/tests/test_failure.py index a389da4b..204053c8 100644 --- a/tests/test_failure.py +++ b/tests/test_failure.py @@ -64,7 +64,7 @@ def test_one_max_value(): def test_changing_max_value(): '''Changing max_value? No problem''' p = progressbar.ProgressBar(max_value=10)(range(20), max_value=20) - for i in p: + for _ in p: time.sleep(1) diff --git a/tests/test_iterators.py b/tests/test_iterators.py index b32c529e..218f048c 100644 --- a/tests/test_iterators.py +++ b/tests/test_iterators.py @@ -6,14 +6,14 @@ def test_list(): '''Progressbar can guess max_value automatically.''' p = progressbar.ProgressBar() - for i in p(range(10)): + for _ in p(range(10)): time.sleep(0.001) def test_iterator_with_max_value(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar(max_value=10) - for i in p((i for i in range(10))): + for _ in p(iter(range(10))): time.sleep(0.001) @@ -21,7 +21,7 @@ def test_iterator_without_max_value_error(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar() - for i in p((i for i in range(10))): + for _ in p(iter(range(10))): time.sleep(0.001) assert p.max_value is progressbar.UnknownLength @@ -35,7 +35,7 @@ def test_iterator_without_max_value(): progressbar.BouncingBar(), progressbar.BouncingBar(marker=progressbar.RotatingMarker()), ]) - for i in p((i for i in range(10))): + for _ in p(iter(range(10))): time.sleep(0.001) @@ -43,7 +43,7 @@ def test_iterator_with_incorrect_max_value(): '''Progressbar can't guess max_value.''' p = progressbar.ProgressBar(max_value=10) with pytest.raises(ValueError): - for i in p((i for i in range(20))): + for _ in p(iter(range(20))): time.sleep(0.001) diff --git a/tests/test_monitor_progress.py b/tests/test_monitor_progress.py index 5dd6f5ee..f1e843db 100644 --- a/tests/test_monitor_progress.py +++ b/tests/test_monitor_progress.py @@ -90,12 +90,10 @@ def test_generator_example(testdir): result.stderr.lines = [l for l in result.stderr.lines if l.strip()] pprint.pprint(result.stderr.lines, width=70) - lines = [] - for i in range(9): - lines.append( - r'[/\\|\-]\s+\|\s*#\s*\| %(i)d Elapsed Time: \d:00:%(i)02d' % - dict(i=i)) - + lines = [ + r'[/\\|\-]\s+\|\s*#\s*\| %(i)d Elapsed Time: \d:00:%(i)02d' % dict(i=i) + for i in range(9) + ] result.stderr.re_match_lines(lines) diff --git a/tests/test_multibar.py b/tests/test_multibar.py index 4865ae3e..11c5f3e8 100644 --- a/tests/test_multibar.py +++ b/tests/test_multibar.py @@ -86,11 +86,11 @@ def test_multibar_sorting(sort_key): with progressbar.MultiBar() as multibar: for i in range(bars): - label = 'bar {}'.format(i) + label = f'bar {i}' multibar[label] = progressbar.ProgressBar(max_value=N) for bar in multibar.values(): - for j in bar(range(N)): + for _ in bar(range(N)): assert bar.started() time.sleep(0.002) diff --git a/tests/test_stream.py b/tests/test_stream.py index 6dcfcf7c..abb9f48c 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -6,7 +6,7 @@ def test_nowrap(): # Make sure we definitely unwrap - for i in range(5): + for _ in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) stdout = sys.stdout @@ -23,13 +23,13 @@ def test_nowrap(): assert stderr == sys.stderr # Make sure we definitely unwrap - for i in range(5): + for _ in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) def test_wrap(): # Make sure we definitely unwrap - for i in range(5): + for _ in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) stdout = sys.stdout @@ -50,7 +50,7 @@ def test_wrap(): assert stderr == sys.stderr # Make sure we definitely unwrap - for i in range(5): + for _ in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) diff --git a/tests/test_timed.py b/tests/test_timed.py index 6753f537..b7c3170b 100644 --- a/tests/test_timed.py +++ b/tests/test_timed.py @@ -57,7 +57,7 @@ def test_adaptive_eta(): ) p.start() - for i in range(20): + for _ in range(20): p.update(1) time.sleep(0.001) p.finish() @@ -160,8 +160,7 @@ def test_eta_not_available(): ETAs should not raise exceptions. """ def gen(): - for x in range(200): - yield x + yield from range(200) widgets = [progressbar.AdaptiveETA(), progressbar.ETA()] diff --git a/tests/test_unicode.py b/tests/test_unicode.py index 3b8e4aec..7ef6bc83 100644 --- a/tests/test_unicode.py +++ b/tests/test_unicode.py @@ -19,11 +19,11 @@ def test_markers(name, markers, as_unicode): markers = converters.to_str(markers) widgets = [ - '%s: ' % name.capitalize(), + f'{name.capitalize()}: ', progressbar.AnimatedMarker(markers=markers), ] bar = progressbar.ProgressBar(widgets=widgets) bar._MINIMUM_UPDATE_INTERVAL = 1e-12 - for i in bar((i for i in range(24))): + for _ in bar(iter(range(24))): time.sleep(0.001) diff --git a/tests/test_unknown_length.py b/tests/test_unknown_length.py index fe08e209..8b2cbdfc 100644 --- a/tests/test_unknown_length.py +++ b/tests/test_unknown_length.py @@ -25,4 +25,4 @@ def test_unknown_length_at_start(): pb2 = progressbar.ProgressBar().start(max_value=progressbar.UnknownLength) for w in pb2.widgets: print(type(w), repr(w)) - assert any([isinstance(w, progressbar.Bar) for w in pb2.widgets]) + assert any(isinstance(w, progressbar.Bar) for w in pb2.widgets) diff --git a/tests/test_widgets.py b/tests/test_widgets.py index a38574da..23044f23 100644 --- a/tests/test_widgets.py +++ b/tests/test_widgets.py @@ -57,12 +57,12 @@ def test_widgets_large_values(max_value): def test_format_widget(): - widgets = [] - for mapping in progressbar.FormatLabel.mapping: - widgets.append(progressbar.FormatLabel('%%(%s)r' % mapping)) - + widgets = [ + progressbar.FormatLabel('%%(%s)r' % mapping) + for mapping in progressbar.FormatLabel.mapping + ] p = progressbar.ProgressBar(widgets=widgets) - for i in p(range(10)): + for _ in p(range(10)): time.sleep(1)