pax_global_header00006660000000000000000000000064146637246200014524gustar00rootroot0000000000000052 comment=eb840a46af96bdd37065f99d9ec66e3066975300 python-progressbar-4.5.0/000077500000000000000000000000001466372462000154025ustar00rootroot00000000000000python-progressbar-4.5.0/.github/000077500000000000000000000000001466372462000167425ustar00rootroot00000000000000python-progressbar-4.5.0/.github/FUNDING.yml000066400000000000000000000000161466372462000205540ustar00rootroot00000000000000github: WoLpH python-progressbar-4.5.0/.github/ci-reporter.yml000066400000000000000000000006431466372462000217230ustar00rootroot00000000000000# Set to false to create a new comment instead of updating the app's first one updateComment: true # Use a custom string, or set to false to disable before: "✨ Good work on this PR so far! ✨ Unfortunately, the [ build]() is failing as of . Here's the output:" # Use a custom string, or set to false to disable after: "I'm sure you can fix it! If you need help, don't hesitate to ask a maintainer of the project!" python-progressbar-4.5.0/.github/workflows/000077500000000000000000000000001466372462000207775ustar00rootroot00000000000000python-progressbar-4.5.0/.github/workflows/codeql.yml000066400000000000000000000016461466372462000230000ustar00rootroot00000000000000name: "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 }}" python-progressbar-4.5.0/.github/workflows/main.yml000066400000000000000000000010411466372462000224420ustar00rootroot00000000000000name: tox on: push: pull_request: workflow_dispatch: jobs: build: runs-on: ubuntu-latest timeout-minutes: 10 strategy: matrix: python-version: ['3.8', '3.9', '3.10', '3.11'] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip tox - name: Test with tox run: tox python-progressbar-4.5.0/.github/workflows/stale.yml000066400000000000000000000006071466372462000226350ustar00rootroot00000000000000name: Close stale issues and pull requests on: workflow_dispatch: schedule: - cron: '0 0 * * *' # Run every day at midnight jobs: stale: runs-on: ubuntu-latest steps: - uses: actions/stale@v8 with: days-before-issue-stale: 30 exempt-issue-labels: in-progress,help-wanted,pinned,security,enhancement exempt-all-assignees: true python-progressbar-4.5.0/.gitignore000066400000000000000000000007141466372462000173740ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ python-progressbar-4.5.0/.pre-commit-config.yaml000066400000000000000000000005671466372462000216730ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.4.0 hooks: - id: trailing-whitespace - id: check-yaml - id: check-added-large-files - repo: https://gitlab.com/pycqa/flake8 rev: 3.7.9 hooks: - id: flake8 python-progressbar-4.5.0/.readthedocs.yaml000066400000000000000000000017761466372462000206440ustar00rootroot00000000000000# Read the Docs configuration file for Sphinx projects # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Set the OS, Python version and other tools you might need build: os: ubuntu-22.04 tools: python: "3.12" # You can also specify other tool versions: # nodejs: "20" # rust: "1.70" # golang: "1.20" # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/conf.py # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs # builder: "dirhtml" # Fail on all warnings to avoid broken references # fail_on_warning: true # Optionally build your docs in additional formats such as PDF and ePub formats: - pdf - epub # Optional but recommended, declare the Python requirements required # to build your documentation # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html python: install: - requirements: docs/requirements.txt python-progressbar-4.5.0/CHANGES.rst000066400000000000000000000004741466372462000172110ustar00rootroot00000000000000========= Changelog ========= For the most recent changes to the Python Progressbar please look at the Git releases or the commit log: - https://github.com/WoLpH/python-progressbar/releases - https://github.com/WoLpH/python-progressbar/commits/develop Hint: click on the `...` button to see the change message. python-progressbar-4.5.0/CONTRIBUTING.rst000066400000000000000000000070651466372462000200530ustar00rootroot00000000000000============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: Types of Contributions ---------------------- Report Bugs ~~~~~~~~~~~ Report bugs at https://github.com/WoLpH/python-progressbar/issues. If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ~~~~~~~~ Look through the GitHub issues for bugs. Anything tagged with "bug" is open to whoever wants to implement it. Implement Features ~~~~~~~~~~~~~~~~~~ Look through the GitHub issues for features. Anything tagged with "feature" is open to whoever wants to implement it. Write Documentation ~~~~~~~~~~~~~~~~~~~ Python Progressbar could always use more documentation, whether as part of the official Python Progressbar docs, in docstrings, or even on the web in blog posts, articles, and such. Submit Feedback ~~~~~~~~~~~~~~~ The best way to send feedback is to file an issue at https://github.com/WoLpH/python-progressbar/issues. If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) Get Started! ------------ Ready to contribute? Here's how to set up `python-progressbar` for local development. 1. Fork the `python-progressbar` repo on GitHub. 2. Clone your fork locally:: $ git clone --branch develop git@github.com:your_name_here/python-progressbar.git 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: $ mkvirtualenv progressbar $ cd progressbar/ $ pip install -e . 4. Create a branch for local development with `git-flow-avh`_:: $ git-flow feature start name-of-your-bugfix-or-feature Or without git-flow: $ git checkout -b feature/name-of-your-bugfix-or-feature Now you can make your changes locally. 5. When you're done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:: $ flake8 progressbar tests $ py.test $ tox To get flake8 and tox, just pip install them into your virtualenv using the requirements file. $ pip install -r tests/requirements.txt 6. Commit your changes and push your branch to GitHub with `git-flow-avh`_:: $ git add . $ git commit -m "Your detailed description of your changes." $ git-flow feature publish Or without git-flow: $ git add . $ git commit -m "Your detailed description of your changes." $ git push -u origin feature/name-of-your-bugfix-or-feature 7. Submit a pull request through the GitHub website. Pull Request Guidelines ----------------------- Before you submit a pull request, check that it meets these guidelines: 1. The pull request should include tests. 2. If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst. 3. The pull request should work for Python 2.7, 3.3, and for PyPy. Check https://travis-ci.org/WoLpH/python-progressbar/pull_requests and make sure that the tests pass for all supported Python versions. Tips ---- To run a subset of tests:: $ py.test tests/some_test.py .. _git-flow-avh: https://github.com/petervanderdoes/gitflow python-progressbar-4.5.0/ISSUE_TEMPLATE000066400000000000000000000006071466372462000175130ustar00rootroot00000000000000#### Description Description of the problem #### Code If applicable, code to reproduce the issue and/or the stacktrace of the issue #### Versions - Python version: `import sys; print(sys.version)` - Python distribution/environment: CPython/Anaconda/IPython/IDLE - Operating System: Windows 10, Ubuntu Linux, etc. - Package version: `import progressbar; print(progressbar.__version__)` python-progressbar-4.5.0/LICENSE000066400000000000000000000027441466372462000164160ustar00rootroot00000000000000Copyright (c) 2022, Rick van Hattem (Wolph) All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Python Progressbar nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-progressbar-4.5.0/MANIFEST.in000066400000000000000000000003761466372462000171460ustar00rootroot00000000000000recursive-exclude *.pyc recursive-exclude *.pyo recursive-exclude *.html include AUTHORS.rst include CHANGES.rst include CONTRIBUTING.rst include LICENSE include README.rst include examples.py include requirements.txt include Makefile include pytest.ini python-progressbar-4.5.0/README.rst000066400000000000000000000306271466372462000171010ustar00rootroot00000000000000############################################################################## Text progress bar library for Python. ############################################################################## Build status: .. image:: https://github.com/WoLpH/python-progressbar/actions/workflows/main.yml/badge.svg :alt: python-progressbar test status :target: https://github.com/WoLpH/python-progressbar/actions Coverage: .. image:: https://coveralls.io/repos/WoLpH/python-progressbar/badge.svg?branch=master :target: https://coveralls.io/r/WoLpH/python-progressbar?branch=master ****************************************************************************** Install ****************************************************************************** The package can be installed through `pip` (this is the recommended method): pip install progressbar2 Or if `pip` is not available, `easy_install` should work as well: easy_install progressbar2 Or download the latest release from Pypi (https://pypi.python.org/pypi/progressbar2) or Github. Note that the releases on Pypi are signed with my GPG key (https://pgp.mit.edu/pks/lookup?op=vindex&search=0xE81444E9CE1F695D) and can be checked using GPG: gpg --verify progressbar2-.tar.gz.asc progressbar2-.tar.gz ****************************************************************************** Introduction ****************************************************************************** A text progress bar is typically used to display the progress of a long running operation, providing a visual cue that processing is underway. The progressbar is based on the old Python progressbar package that was published on the now defunct Google Code. Since that project was completely abandoned by its developer and the developer did not respond to email, I decided to fork the package. This package is still backwards compatible with the original progressbar package so you can safely use it as a drop-in replacement for existing project. The ProgressBar class manages the current progress, and the format of the line is given by a number of widgets. A widget is an object that may display differently depending on the state of the progress bar. There are many types of widgets: - `AbsoluteETA `_ - `AdaptiveETA `_ - `AdaptiveTransferSpeed `_ - `AnimatedMarker `_ - `Bar `_ - `BouncingBar `_ - `Counter `_ - `CurrentTime `_ - `DataSize `_ - `DynamicMessage `_ - `ETA `_ - `FileTransferSpeed `_ - `FormatCustomText `_ - `FormatLabel `_ - `FormatLabelBar `_ - `GranularBar `_ - `Percentage `_ - `PercentageLabelBar `_ - `ReverseBar `_ - `RotatingMarker `_ - `SimpleProgress `_ - `Timer `_ 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. ****************************************************************************** Known issues ****************************************************************************** - 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 - 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 ****************************************************************************** Links ****************************************************************************** * Documentation - https://progressbar-2.readthedocs.org/en/latest/ * Source - https://github.com/WoLpH/python-progressbar * Bug reports - https://github.com/WoLpH/python-progressbar/issues * Package homepage - https://pypi.python.org/pypi/progressbar2 * My blog - https://w.wol.ph/ ****************************************************************************** Usage ****************************************************************************** There are many ways to use Python Progressbar, you can see a few basic examples here but there are many more in the examples file. Wrapping an iterable ============================================================================== .. code:: python import time import progressbar for i in progressbar.progressbar(range(100)): time.sleep(0.02) Progressbars with logging ============================================================================== Progressbars with logging require `stderr` redirection _before_ the `StreamHandler` is initialized. To make sure the `stderr` stream has been redirected on time make sure to call `progressbar.streams.wrap_stderr()` before you initialize the `logger`. One option to force early initialization is by using the `WRAP_STDERR` environment variable, on Linux/Unix systems this can be done through: .. code:: sh # WRAP_STDERR=true python your_script.py If you need to flush manually while wrapping, you can do so using: .. code:: python import progressbar progressbar.streams.flush() In most cases the following will work as well, as long as you initialize the `StreamHandler` after the wrapping has taken place. .. code:: python import time import logging import progressbar progressbar.streams.wrap_stderr() logging.basicConfig() for i in progressbar.progressbar(range(10)): 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 import time import progressbar with progressbar.ProgressBar(max_value=10) as bar: for i in range(10): time.sleep(0.1) bar.update(i) Combining progressbars with print output ============================================================================== .. code:: python import time import progressbar for i in progressbar.progressbar(range(100), redirect_stdout=True): print('Some text', i) time.sleep(0.1) Progressbar with unknown length ============================================================================== .. code:: python import time import progressbar bar = progressbar.ProgressBar(max_value=progressbar.UnknownLength) for i in range(20): time.sleep(0.1) bar.update(i) Bar with custom widgets ============================================================================== .. code:: python import time import progressbar widgets=[ ' [', progressbar.Timer(), '] ', progressbar.Bar(), ' (', progressbar.ETA(), ') ', ] for i in progressbar.progressbar(range(20), widgets=widgets): time.sleep(0.1) Bar with wide Chinese (or other multibyte) characters ============================================================================== .. code:: python # vim: fileencoding=utf-8 import time import progressbar def custom_len(value): # These characters take up more space characters = { '进': 2, '度': 2, } total = 0 for c in value: total += characters.get(c, 1) return total bar = progressbar.ProgressBar( widgets=[ '进度: ', progressbar.Bar(), ' ', progressbar.Counter(format='%(value)02d/%(max_value)d'), ], len_func=custom_len, ) for i in bar(range(10)): time.sleep(0.1) Showing multiple independent progress bars in parallel ============================================================================== .. code:: python import random import sys import time import progressbar 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, ) ) # Create a file descriptor for regular printing as well print_fd = progressbar.LineOffsetStreamWrapper(lines=0, stream=sys.stdout) # The progress bar updates, normally you would do something useful here for i in range(N * BARS): time.sleep(0.005) # Increment one of the progress bars at random bars[random.randrange(0, BARS)].increment() # 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() # Add a newline to make sure the next print starts on a new line print() ****************************************************************************** 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): def __init__(self, bar): super().__init__() self.bar = bar def run(self): for i in range(N): time.sleep(random.random() / 25) self.bar.update(i) for bar in bars: Worker(bar).start() print() python-progressbar-4.5.0/appveyor.yml000066400000000000000000000015651466372462000200010ustar00rootroot00000000000000# What Python version is installed where: # http://www.appveyor.com/docs/installed-software#python environment: matrix: - PYTHON: "C:\\Python27" TOX_ENV: "py27" - PYTHON: "C:\\Python34" TOX_ENV: "py34" - PYTHON: "C:\\Python35" TOX_ENV: "py35" - PYTHON: "C:\\Python36" TOX_ENV: "py36" init: - "%PYTHON%/python -V" - "%PYTHON%/python -c \"import struct;print( 8 * struct.calcsize(\'P\'))\"" install: - "%PYTHON%/Scripts/easy_install -U pip" - "%PYTHON%/Scripts/pip install tox" - "%PYTHON%/Scripts/pip install wheel" build: false # Not a C# project, build stuff at the test step instead. test_script: - "%PYTHON%/Scripts/tox -e %TOX_ENV%" after_test: - "%PYTHON%/python setup.py bdist_wheel" - ps: "ls dist" artifacts: - path: dist\* #on_success: # - TODO: upload the content of dist/*.whl to a public wheelhouse python-progressbar-4.5.0/circle.yml000066400000000000000000000001431466372462000173640ustar00rootroot00000000000000dependencies: pre: - pip install -r tests/requirements.txt test: override: - py.test python-progressbar-4.5.0/docs/000077500000000000000000000000001466372462000163325ustar00rootroot00000000000000python-progressbar-4.5.0/docs/.gitignore000066400000000000000000000000071466372462000203170ustar00rootroot00000000000000_build python-progressbar-4.5.0/docs/Makefile000066400000000000000000000151761466372462000200040ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ProgressBar.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ProgressBar.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/ProgressBar" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ProgressBar" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." python-progressbar-4.5.0/docs/_theme/000077500000000000000000000000001466372462000175735ustar00rootroot00000000000000python-progressbar-4.5.0/docs/_theme/LICENSE000066400000000000000000000035521466372462000206050ustar00rootroot00000000000000Modifications: Copyright (c) 2012 Rick van Hattem. Original Projects: Copyright (c) 2010 Kenneth Reitz. Copyright (c) 2010 by Armin Ronacher. Some rights reserved. Redistribution and use in source and binary forms of the theme, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The names of the contributors may not be used to endorse or promote products derived from this software without specific prior written permission. We kindly ask you to only use these themes in an unmodified manner just for Flask and Flask-related products, not for unrelated projects. If you like the visual style and want to use it for your own projects, please consider making some larger changes to the themes (such as changing font faces, sizes, colors or margins). THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. python-progressbar-4.5.0/docs/_theme/flask_theme_support.py000066400000000000000000000075011466372462000242260ustar00rootroot00000000000000# flasky extensions. flasky pygments style based on tango style from pygments.style import Style from pygments.token import ( Comment, Error, Generic, Keyword, Literal, Name, Number, Operator, Other, Punctuation, String, Whitespace, ) class FlaskyStyle(Style): background_color = '#f8f8f8' default_style = '' styles = { # No corresponding class for the following: # Text: '', # class: '' Whitespace: 'underline #f8f8f8', # class: 'w' Error: '#a40000 border:#ef2929', # class: 'err' Other: '#000000', # class 'x' Comment: 'italic #8f5902', # class: 'c' Comment.Preproc: 'noitalic', # class: 'cp' Keyword: 'bold #004461', # class: 'k' Keyword.Constant: 'bold #004461', # class: 'kc' Keyword.Declaration: 'bold #004461', # class: 'kd' Keyword.Namespace: 'bold #004461', # class: 'kn' Keyword.Pseudo: 'bold #004461', # class: 'kp' Keyword.Reserved: 'bold #004461', # class: 'kr' Keyword.Type: 'bold #004461', # class: 'kt' Operator: '#582800', # class: 'o' Operator.Word: 'bold #004461', # class: 'ow' - like keywords Punctuation: 'bold #000000', # class: 'p' # because special names such as Name.Class, Name.Function, etc. # are not recognized as such later in the parsing, we choose them # to look the same as ordinary variables. Name: '#000000', # class: 'n' Name.Attribute: '#c4a000', # class: 'na' - to be revised Name.Builtin: '#004461', # class: 'nb' Name.Builtin.Pseudo: '#3465a4', # class: 'bp' Name.Class: '#000000', # class: 'nc' - to be revised Name.Constant: '#000000', # class: 'no' - to be revised Name.Decorator: '#888', # class: 'nd' - to be revised Name.Entity: '#ce5c00', # class: 'ni' Name.Exception: 'bold #cc0000', # class: 'ne' Name.Function: '#000000', # class: 'nf' Name.Property: '#000000', # class: 'py' Name.Label: '#f57900', # class: 'nl' Name.Namespace: '#000000', # class: 'nn' - to be revised Name.Other: '#000000', # class: 'nx' Name.Tag: 'bold #004461', # class: 'nt' - like a keyword Name.Variable: '#000000', # class: 'nv' - to be revised Name.Variable.Class: '#000000', # class: 'vc' - to be revised Name.Variable.Global: '#000000', # class: 'vg' - to be revised Name.Variable.Instance: '#000000', # class: 'vi' - to be revised Number: '#990000', # class: 'm' Literal: '#000000', # class: 'l' Literal.Date: '#000000', # class: 'ld' String: '#4e9a06', # class: 's' String.Backtick: '#4e9a06', # class: 'sb' String.Char: '#4e9a06', # class: 'sc' String.Doc: 'italic #8f5902', # class: 'sd' - like a comment String.Double: '#4e9a06', # class: 's2' String.Escape: '#4e9a06', # class: 'se' String.Heredoc: '#4e9a06', # class: 'sh' String.Interpol: '#4e9a06', # class: 'si' String.Other: '#4e9a06', # class: 'sx' String.Regex: '#4e9a06', # class: 'sr' String.Single: '#4e9a06', # class: 's1' String.Symbol: '#4e9a06', # class: 'ss' Generic: '#000000', # class: 'g' Generic.Deleted: '#a40000', # class: 'gd' Generic.Emph: 'italic #000000', # class: 'ge' Generic.Error: '#ef2929', # class: 'gr' Generic.Heading: 'bold #000080', # class: 'gh' Generic.Inserted: '#00A000', # class: 'gi' Generic.Output: '#888', # class: 'go' Generic.Prompt: '#745334', # class: 'gp' Generic.Strong: 'bold #000000', # class: 'gs' Generic.Subheading: 'bold #800080', # class: 'gu' Generic.Traceback: 'bold #a40000', # class: 'gt' } python-progressbar-4.5.0/docs/_theme/wolph/000077500000000000000000000000001466372462000207245ustar00rootroot00000000000000python-progressbar-4.5.0/docs/_theme/wolph/layout.html000066400000000000000000000017271466372462000231360ustar00rootroot00000000000000{%- extends "basic/layout.html" %} {%- block extrahead %} {{ super() }} {% if theme_touch_icon %} {% endif %} {% endblock %} {%- block relbar2 %}{% endblock %} {%- block footer %} {%- endblock %} python-progressbar-4.5.0/docs/_theme/wolph/relations.html000066400000000000000000000011161466372462000236110ustar00rootroot00000000000000

Related Topics

python-progressbar-4.5.0/docs/_theme/wolph/static/000077500000000000000000000000001466372462000222135ustar00rootroot00000000000000python-progressbar-4.5.0/docs/_theme/wolph/static/flasky.css_t000066400000000000000000000156061466372462000245510ustar00rootroot00000000000000/* * flasky.css_t * ~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. Modifications by Kenneth Reitz. * :license: Flask Design License, see LICENSE for details. */ {% set page_width = '940px' %} {% set sidebar_width = '220px' %} @import url("basic.css"); /* -- page layout ----------------------------------------------------------- */ body { font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro'; font-size: 17px; background-color: white; color: #000; margin: 0; padding: 0; } div.document { width: {{ page_width }}; margin: 30px auto 0 auto; } div.documentwrapper { float: left; width: 100%; } div.bodywrapper { margin: 0 0 0 {{ sidebar_width }}; } div.sphinxsidebar { width: {{ sidebar_width }}; } hr { border: 1px solid #B1B4B6; } div.body { background-color: #ffffff; color: #3E4349; padding: 0 30px 0 30px; } img.floatingflask { padding: 0 0 10px 10px; float: right; } div.footer { width: {{ page_width }}; margin: 20px auto 30px auto; font-size: 14px; color: #888; text-align: right; } div.footer a { color: #888; } div.related { display: none; } div.sphinxsidebar a { color: #444; text-decoration: none; border-bottom: 1px dotted #999; } div.sphinxsidebar a:hover { border-bottom: 1px solid #999; } div.sphinxsidebar { font-size: 14px; line-height: 1.5; } div.sphinxsidebarwrapper { padding: 0px 10px; } div.sphinxsidebarwrapper p.logo { padding: 0 0 20px 0; margin: 0; text-align: center; } div.sphinxsidebar h3, div.sphinxsidebar h4 { font-family: 'Garamond', 'Georgia', serif; color: #555; font-size: 24px; font-weight: normal; margin: 0 0 5px 0; padding: 0; } div.sphinxsidebar h4 { font-size: 20px; } div.sphinxsidebar h3 a { color: #444; } div.sphinxsidebar p.logo a, div.sphinxsidebar h3 a, div.sphinxsidebar p.logo a:hover, div.sphinxsidebar h3 a:hover { border: none; } div.sphinxsidebar p { color: #555; margin: 10px 0; } div.sphinxsidebar ul { margin: 10px 0; padding: 0; color: #000; } div.sphinxsidebar input[type="text"] { width: 160px!important; } div.sphinxsidebar input { border: 1px solid #ccc; font-family: 'Georgia', serif; font-size: 1em; } /* -- body styles ----------------------------------------------------------- */ a { color: #004B6B; text-decoration: underline; } a:hover { color: #6D4100; text-decoration: underline; } div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; margin: 30px 0px 10px 0px; padding: 0; } div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } div.body h2 { font-size: 180%; } div.body h3 { font-size: 150%; } div.body h4 { font-size: 130%; } div.body h5 { font-size: 100%; } div.body h6 { font-size: 100%; } a.headerlink { color: #ddd; padding: 0 4px; text-decoration: none; } a.headerlink:hover { color: #444; background: #eaeaea; } div.body p, div.body dd, div.body li { line-height: 1.4em; } div.admonition { background: #fafafa; margin: 20px -30px; padding: 10px 30px; border-top: 1px solid #ccc; border-bottom: 1px solid #ccc; } div.admonition tt.xref, div.admonition a tt { border-bottom: 1px solid #fafafa; } dd div.admonition { margin-left: -60px; padding-left: 60px; } div.admonition p.admonition-title { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; font-size: 24px; margin: 0 0 10px 0; padding: 0; line-height: 1; } div.admonition p.last { margin-bottom: 0; } div.highlight { background-color: white; } dt:target, .highlight { background: #FAF3E8; } div.note { background-color: #eee; border: 1px solid #ccc; } div.seealso { background-color: #ffc; border: 1px solid #ff6; } div.topic { background-color: #eee; } p.admonition-title { display: inline; } p.admonition-title:after { content: ":"; } pre, tt { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; font-size: 0.9em; } img.screenshot { } tt.descname, tt.descclassname { font-size: 0.95em; } tt.descname { padding-right: 0.08em; } img.screenshot { -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils { border: 1px solid #888; -moz-box-shadow: 2px 2px 4px #eee; -webkit-box-shadow: 2px 2px 4px #eee; box-shadow: 2px 2px 4px #eee; } table.docutils td, table.docutils th { border: 1px solid #888; padding: 0.25em 0.7em; } table.field-list, table.footnote { border: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; } table.footnote { margin: 15px 0; width: 100%; border: 1px solid #eee; background: #fdfdfd; font-size: 0.9em; } table.footnote + table.footnote { margin-top: -15px; border-top: none; } table.field-list th { padding: 0 0.8em 0 0; } table.field-list td { padding: 0; } table.footnote td.label { width: 0px; padding: 0.3em 0 0.3em 0.5em; } table.footnote td { padding: 0.3em 0.5em; } dl { margin: 0; padding: 0; } dl dd { margin-left: 30px; } blockquote { margin: 0 0 0 30px; padding: 0; } ul, ol { margin: 10px 0 10px 30px; padding: 0; } pre { background: #eee; padding: 7px 30px; margin: 15px -30px; line-height: 1.3em; } dl pre, blockquote pre, li pre { margin-left: -60px; padding-left: 60px; } dl dl pre { margin-left: -90px; padding-left: 90px; } tt { background-color: #ecf0f3; color: #222; /* padding: 1px 2px; */ } tt.xref, a tt { background-color: #FBFBFB; border-bottom: 1px solid white; } a.reference { text-decoration: none; border-bottom: 1px dotted #004B6B; } a.reference:hover { border-bottom: 1px solid #6D4100; } a.footnote-reference { text-decoration: none; font-size: 0.7em; vertical-align: top; border-bottom: 1px dotted #004B6B; } a.footnote-reference:hover { border-bottom: 1px solid #6D4100; } a:hover tt { background: #EEE; } /* scrollbars */ ::-webkit-scrollbar { width: 6px; height: 6px; } ::-webkit-scrollbar-button:start:decrement, ::-webkit-scrollbar-button:end:increment { display: block; height: 10px; } ::-webkit-scrollbar-button:vertical:increment { background-color: #fff; } ::-webkit-scrollbar-track-piece { background-color: #eee; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:vertical { height: 50px; background-color: #ccc; -webkit-border-radius: 3px; } ::-webkit-scrollbar-thumb:horizontal { width: 50px; background-color: #ccc; -webkit-border-radius: 3px; } /* misc. */ .revsys-inline { display: none!important; } python-progressbar-4.5.0/docs/_theme/wolph/static/small_flask.css000066400000000000000000000017201466372462000252150ustar00rootroot00000000000000/* * small_flask.css_t * ~~~~~~~~~~~~~~~~~ * * :copyright: Copyright 2010 by Armin Ronacher. * :license: Flask Design License, see LICENSE for details. */ body { margin: 0; padding: 20px 30px; } div.documentwrapper { float: none; background: white; } div.sphinxsidebar { display: block; float: none; width: 102.5%; margin: 50px -30px -20px -30px; padding: 10px 20px; background: #333; color: white; } div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, div.sphinxsidebar h3 a { color: white; } div.sphinxsidebar a { color: #aaa; } div.sphinxsidebar p.logo { display: none; } div.document { width: 100%; margin: 0; } div.related { display: block; margin: 0; padding: 10px 0 20px 0; } div.related ul, div.related ul li { margin: 0; padding: 0; } div.footer { display: none; } div.bodywrapper { margin: 0; } div.body { min-height: 0; padding: 0; } python-progressbar-4.5.0/docs/_theme/wolph/theme.conf000066400000000000000000000001711466372462000226740ustar00rootroot00000000000000[theme] inherit = basic stylesheet = flasky.css pygments_style = flask_theme_support.FlaskyStyle [options] touch_icon = python-progressbar-4.5.0/docs/conf.py000066400000000000000000000245541466372462000176430ustar00rootroot00000000000000from __future__ import annotations # # Progress Bar documentation build configuration file, created by # sphinx-quickstart on Tue Aug 20 11:47:33 2013. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import datetime import os import sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) from progressbar import __about__ as metadata # -- General configuration ----------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.mathjax', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', ] suppress_warnings = [ 'image.nonlocal_uri', ] needs_sphinx = '1.4' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Progress Bar' project_slug: str = ''.join(project.capitalize().split()) 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 # built documents. # # The short X.Y version. version: str = metadata.__version__ # The full version, including alpha/beta/rc tags. release: str = metadata.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = True # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. # keep_warnings = False # -- Options for HTML output --------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'wolph' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. html_theme_path = ['_theme'] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = f'{project_slug}doc' # -- Options for LaTeX output -------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents: list[tuple[str, ...]] = [ ( '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 # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output -------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages: list[tuple[str, str, str, list[str], int]] = [ ( 'index', project_slug.lower(), f'{project} Documentation', [metadata.__author__], 1, ) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ------------------------------------------ # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents: list[tuple[str, ...]] = [ ( '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. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. # texinfo_no_detailmenu = False # -- Options for Epub output --------------------------------------------- # Bibliographic Dublin Core info. epub_title: str = project epub_author: str = metadata.__author__ epub_publisher: str = metadata.__author__ epub_copyright: str = copyright # The language of the text. It defaults to the language option # or en if the language is not set. # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. # epub_identifier = '' # A unique identification for the text. # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # epub_cover = () # A sequence of (type, uri, title) tuples for the guide element of content.opf. # epub_guide = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_pre_files = [] # HTML files that should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] # A list of files that should not be packed into the epub file. # epub_exclude_files = [] # The depth of the table of contents in toc.ncx. # epub_tocdepth = 3 # Allow duplicate toc entries. # epub_tocdup = True # Fix unsupported image types using the PIL. # epub_fix_images = False # Scale large images. # epub_max_image_width = 0 # If 'no', URL addresses will not be shown. # epub_show_urls = 'inline' # If false, no index is generated. # epub_use_index = True # Example configuration for intersphinx: refer to the Python standard library. intersphinx_mapping: dict[str, tuple[str, None]] = { 'python': ('https://docs.python.org/3', None) } python-progressbar-4.5.0/docs/contributing.rst000066400000000000000000000000411466372462000215660ustar00rootroot00000000000000.. include:: ../CONTRIBUTING.rst python-progressbar-4.5.0/docs/examples.rst000066400000000000000000000001021466372462000206730ustar00rootroot00000000000000Examples =================== .. literalinclude:: ../examples.py python-progressbar-4.5.0/docs/history.rst000066400000000000000000000001261466372462000205640ustar00rootroot00000000000000.. _history: ======= History ======= .. include:: ../CHANGES.rst :start-line: 5 python-progressbar-4.5.0/docs/index.rst000066400000000000000000000007221466372462000201740ustar00rootroot00000000000000======================================== Welcome to Progress Bar's documentation! ======================================== .. toctree:: :maxdepth: 4 usage examples contributing installation progressbar.shortcuts progressbar.bar progressbar.base progressbar.utils progressbar.widgets history .. include:: ../README.rst ****************** Indices and tables ****************** * :ref:`genindex` * :ref:`modindex` * :ref:`search` python-progressbar-4.5.0/docs/installation.rst000066400000000000000000000004121466372462000215620ustar00rootroot00000000000000============ Installation ============ At the command line:: $ pip install progressbar2 Or if you don't have pip:: $ easy_install progressbar2 Or, if you have virtualenvwrapper installed:: $ mkvirtualenv progressbar2 $ pip install progressbar2 python-progressbar-4.5.0/docs/make.bat000066400000000000000000000145051466372462000177440ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\progressbar.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\progressbar.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end python-progressbar-4.5.0/docs/progressbar.algorithms.rst000066400000000000000000000002321466372462000235620ustar00rootroot00000000000000progressbar.algorithms module ============================= .. automodule:: progressbar.algorithms :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.bar.rst000066400000000000000000000002441466372462000221600ustar00rootroot00000000000000progressbar.bar module ====================== .. automodule:: progressbar.bar :members: :undoc-members: :show-inheritance: :member-order: bysource python-progressbar-4.5.0/docs/progressbar.base.rst000066400000000000000000000002131466372462000223220ustar00rootroot00000000000000progressbar.base module ======================= .. automodule:: progressbar.base :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.env.rst000066400000000000000000000002051466372462000222010ustar00rootroot00000000000000progressbar.env module ====================== .. automodule:: progressbar.env :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.multi.rst000066400000000000000000000002131466372462000225420ustar00rootroot00000000000000progressbar.multi module ======================== .. automodule:: progressbar.multi :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.rst000066400000000000000000000006431466372462000214200ustar00rootroot00000000000000progressbar package =================== Subpackages ----------- .. toctree:: :maxdepth: 4 progressbar.terminal Submodules ---------- .. toctree:: :maxdepth: 4 progressbar.bar progressbar.base progressbar.multi progressbar.shortcuts progressbar.utils progressbar.widgets Module contents --------------- .. automodule:: progressbar :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.shortcuts.rst000066400000000000000000000002341466372462000234510ustar00rootroot00000000000000progressbar\.shortcuts module ============================= .. automodule:: progressbar.shortcuts :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.terminal.base.rst000066400000000000000000000002431466372462000241370ustar00rootroot00000000000000progressbar.terminal.base module ================================ .. automodule:: progressbar.terminal.base :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.terminal.colors.rst000066400000000000000000000002511466372462000245250ustar00rootroot00000000000000progressbar.terminal.colors module ================================== .. automodule:: progressbar.terminal.colors :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.terminal.rst000066400000000000000000000006441466372462000232330ustar00rootroot00000000000000progressbar.terminal package ============================ Subpackages ----------- .. toctree:: :maxdepth: 4 progressbar.terminal.os_specific Submodules ---------- .. toctree:: :maxdepth: 4 progressbar.terminal.base progressbar.terminal.colors progressbar.terminal.stream Module contents --------------- .. automodule:: progressbar.terminal :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.terminal.stream.rst000066400000000000000000000002511466372462000245170ustar00rootroot00000000000000progressbar.terminal.stream module ================================== .. automodule:: progressbar.terminal.stream :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.utils.rst000066400000000000000000000002161466372462000225530ustar00rootroot00000000000000progressbar.utils module ======================== .. automodule:: progressbar.utils :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/progressbar.widgets.rst000066400000000000000000000002241466372462000230600ustar00rootroot00000000000000progressbar.widgets module ========================== .. automodule:: progressbar.widgets :members: :undoc-members: :show-inheritance: python-progressbar-4.5.0/docs/requirements.txt000066400000000000000000000000201466372462000216060ustar00rootroot00000000000000-e.[docs,tests] python-progressbar-4.5.0/docs/usage.rst000066400000000000000000000031531466372462000201720ustar00rootroot00000000000000======== Usage ======== There are many ways to use Python Progressbar, you can see a few basic examples here but there are many more in the :doc:`examples` file. Wrapping an iterable ------------------------------------------------------------------------------ :: import time import progressbar bar = progressbar.ProgressBar() for i in bar(range(100)): time.sleep(0.02) Context wrapper ------------------------------------------------------------------------------ :: import time import progressbar with progressbar.ProgressBar(max_value=10) as bar: for i in range(10): time.sleep(0.1) bar.update(i) Combining progressbars with print output ------------------------------------------------------------------------------ :: import time import progressbar bar = progressbar.ProgressBar(redirect_stdout=True) for i in range(100): print 'Some text', i time.sleep(0.1) bar.update(i) Progressbar with unknown length ------------------------------------------------------------------------------ :: import time import progressbar bar = progressbar.ProgressBar(max_value=progressbar.UnknownLength) for i in range(20): time.sleep(0.1) bar.update(i) Bar with custom widgets ------------------------------------------------------------------------------ :: import time import progressbar bar = progressbar.ProgressBar(widgets=[ ' [', progressbar.Timer(), '] ', progressbar.Bar(), ' (', progressbar.ETA(), ') ', ]) for i in bar(range(20)): time.sleep(0.1) python-progressbar-4.5.0/examples.py000066400000000000000000000543121466372462000175770ustar00rootroot00000000000000#!/usr/bin/python from __future__ import annotations import contextlib import functools import os import random import sys import time import typing import progressbar examples: list[typing.Callable[[typing.Any], typing.Any]] = [] def example(fn): """Wrap the examples so they generate readable output""" @functools.wraps(fn) def wrapped(*args, **kwargs): try: sys.stdout.write(f'Running: {fn.__name__}\n') fn(*args, **kwargs) sys.stdout.write('\n') except KeyboardInterrupt: sys.stdout.write('\nSkipping example.\n\n') # Sleep a bit to make killing the script easier time.sleep(0.2) examples.append(wrapped) return wrapped @example def fast_example() -> None: """Updates bar really quickly to cause flickering""" with progressbar.ProgressBar(widgets=[progressbar.Bar()]) as bar: for i in range(100): bar.update(int(i / 10), force=True) @example def shortcut_example() -> None: for _ in progressbar.progressbar(range(10)): time.sleep(0.1) @example def prefixed_shortcut_example() -> None: for _ in progressbar.progressbar(range(10), prefix='Hi: '): time.sleep(0.1) @example def parallel_bars_multibar_example() -> None: if os.name == 'nt': print( 'Skipping multibar example on Windows due to threading ' 'incompatibilities with the example code.' ) return BARS = 5 N = 50 def do_something(bar): for _ in bar(range(N)): # Sleep up to 0.1 seconds time.sleep(random.random() * 0.1) with progressbar.MultiBar() as multibar: bar_labels = [] for i in range(BARS): # Get a progressbar bar_label = 'Bar #%d' % i bar_labels.append(bar_label) multibar[bar_label] for _ in range(N * BARS): time.sleep(0.005) bar_i = random.randrange(0, BARS) bar_label = bar_labels[bar_i] # Increment one of the progress bars at random multibar[bar_label].increment() @example def multiple_bars_line_offset_example() -> None: BARS = 5 N = 100 bars = [ 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, ) for i in range(BARS) ] # Create a file descriptor for regular printing as well print_fd = progressbar.LineOffsetStreamWrapper(lines=0, stream=sys.stdout) assert print_fd # The progress bar updates, normally you would do something useful here for _ in range(N * BARS): time.sleep(0.005) # Increment one of the progress bars at random bars[random.randrange(0, BARS)].increment() # Cleanup the bars for bar in bars: bar.finish() # Add a newline to make sure the next print starts on a new line print() @example def templated_shortcut_example() -> None: for _ in progressbar.progressbar(range(10), suffix='{seconds_elapsed:.1}'): time.sleep(0.1) @example def job_status_example() -> None: with progressbar.ProgressBar( redirect_stdout=True, widgets=[progressbar.widgets.JobStatusBar('status')], ) as bar: for _ in range(30): print('random', random.random()) # Roughly 1/3 probability for each status ;) # Yes... probability is confusing at times if random.random() > 0.66: bar.increment(status=True) elif random.random() > 0.5: bar.increment(status=False) else: bar.increment(status=None) time.sleep(0.1) @example def with_example_stdout_redirection() -> None: with progressbar.ProgressBar(max_value=10, redirect_stdout=True) as p: for i in range(10): if i % 3 == 0: print('Some print statement %i' % i) # do something p.update(i) time.sleep(0.1) @example def basic_widget_example() -> None: widgets = [progressbar.Percentage(), progressbar.Bar()] bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start() for i in range(10): # do something time.sleep(0.1) bar.update(i + 1) bar.finish() @example def color_bar_example() -> None: widgets = [ '\x1b[33mColorful example\x1b[39m', progressbar.Percentage(), progressbar.Bar(marker='\x1b[32m#\x1b[39m'), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start() for i in range(10): # do something time.sleep(0.1) bar.update(i + 1) bar.finish() @example def color_bar_animated_marker_example() -> None: widgets = [ # Colored animated marker with colored fill: progressbar.Bar( marker=progressbar.AnimatedMarker( fill='x', # fill='█', fill_wrap='\x1b[32m{}\x1b[39m', marker_wrap='\x1b[31m{}\x1b[39m', ) ), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start() for i in range(10): # do something time.sleep(0.1) bar.update(i + 1) bar.finish() @example def multi_range_bar_example() -> None: markers = [ '\x1b[32m█\x1b[39m', # Done '\x1b[33m#\x1b[39m', # Processing '\x1b[31m.\x1b[39m', # Scheduling ' ', # Not started ] widgets = [progressbar.MultiRangeBar('amounts', markers=markers)] amounts = [0] * (len(markers) - 1) + [25] with progressbar.ProgressBar(widgets=widgets, max_value=10).start() as bar: while True: incomplete_items = [ idx for idx, amount in enumerate(amounts) for _ in range(amount) if idx != 0 ] if not incomplete_items: break which = random.choice(incomplete_items) amounts[which] -= 1 amounts[which - 1] += 1 bar.update(amounts=amounts, force=True) time.sleep(0.02) @example def multi_progress_bar_example(left: bool = True) -> None: jobs = [ # Each job takes between 1 and 10 steps to complete [0, random.randint(1, 10)] for _ in range(25) # 25 jobs total ] widgets = [ progressbar.Percentage(), ' ', progressbar.MultiProgressBar('jobs', fill_left=left), ] max_value = sum([total for progress, total in jobs]) with progressbar.ProgressBar(widgets=widgets, max_value=max_value) as bar: while True: incomplete_jobs = [ idx for idx, (progress, total) in enumerate(jobs) if progress < total ] if not incomplete_jobs: break which = random.choice(incomplete_jobs) jobs[which][0] += 1 progress = sum([progress for progress, total in jobs]) bar.update(progress, jobs=jobs, force=True) time.sleep(0.02) @example def granular_progress_example() -> None: widgets = [ progressbar.GranularBar(markers=' ▏▎▍▌▋▊▉█', left='', right='|'), progressbar.GranularBar(markers=' ▁▂▃▄▅▆▇█', left='', right='|'), progressbar.GranularBar(markers=' ▖▌▛█', left='', right='|'), progressbar.GranularBar(markers=' ░▒▓█', left='', right='|'), progressbar.GranularBar(markers=' ⡀⡄⡆⡇⣇⣧⣷⣿', left='', right='|'), progressbar.GranularBar(markers=' .oO', left='', right=''), ] for _ in progressbar.progressbar(list(range(100)), widgets=widgets): time.sleep(0.03) for _ in progressbar.progressbar(iter(range(100)), widgets=widgets): time.sleep(0.03) @example def percentage_label_bar_example() -> None: widgets = [progressbar.PercentageLabelBar()] bar = progressbar.ProgressBar(widgets=widgets, max_value=10).start() for i in range(10): # do something time.sleep(0.1) bar.update(i + 1) bar.finish() @example def file_transfer_example() -> None: widgets = [ 'Test: ', progressbar.Percentage(), ' ', progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', progressbar.ETA(), ' ', progressbar.FileTransferSpeed(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=1000).start() for i in range(100): # do something time.sleep(0.01) bar.update(10 * i + 1) bar.finish() @example def custom_file_transfer_example() -> None: 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 ) else: return progressbar.FileTransferSpeed.update(self, bar) widgets = [ CrazyFileTransferSpeed(), ' <<<', progressbar.Bar(), '>>> ', progressbar.Percentage(), ' ', progressbar.ETA(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=1000) # maybe do something bar.start() for i in range(200): # do something time.sleep(0.01) bar.update(5 * i + 1) bar.finish() @example def double_bar_example() -> None: widgets = [ progressbar.Bar('>'), ' ', progressbar.ETA(), ' ', progressbar.ReverseBar('<'), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=1000).start() for i in range(100): # do something time.sleep(0.01) bar.update(10 * i + 1) bar.finish() @example def basic_file_transfer() -> None: widgets = [ 'Test: ', progressbar.Percentage(), ' ', progressbar.Bar(marker='0', left='[', right=']'), ' ', progressbar.ETA(), ' ', progressbar.FileTransferSpeed(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=500) bar.start() # Go beyond the max_value for i in range(100, 501, 50): time.sleep(0.1) bar.update(i) bar.finish() @example def simple_progress() -> None: bar = progressbar.ProgressBar( widgets=[progressbar.SimpleProgress()], max_value=17, ).start() for i in range(17): time.sleep(0.1) bar.update(i + 1) bar.finish() @example def basic_progress() -> None: bar = progressbar.ProgressBar().start() for i in range(10): time.sleep(0.1) bar.update(i + 1) bar.finish() @example def progress_with_automatic_max() -> None: # Progressbar can guess max_value automatically. bar = progressbar.ProgressBar() for _ in bar(range(8)): time.sleep(0.1) @example def progress_with_unavailable_max() -> None: # Progressbar can't guess max_value. bar = progressbar.ProgressBar(max_value=8) for _ in bar(i for i in range(8)): time.sleep(0.1) @example def animated_marker() -> None: bar = progressbar.ProgressBar( widgets=['Working: ', progressbar.AnimatedMarker()] ) for _ in bar(i for i in range(5)): time.sleep(0.1) @example def filling_bar_animated_marker() -> None: bar = progressbar.ProgressBar( widgets=[ progressbar.Bar( marker=progressbar.AnimatedMarker(fill='#'), ), ] ) for _ in bar(range(15)): time.sleep(0.1) @example def counter_and_timer() -> None: widgets = [ 'Processed: ', progressbar.Counter('Counter: %(value)05d'), ' lines (', progressbar.Timer(), ')', ] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(15)): time.sleep(0.1) @example def format_label() -> None: widgets = [ progressbar.FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)') ] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(15)): time.sleep(0.1) @example def animated_balloons() -> None: widgets = ['Balloon: ', progressbar.AnimatedMarker(markers='.oO@* ')] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(24)): time.sleep(0.1) @example def animated_arrows() -> None: # You may need python 3.x to see this correctly try: widgets = ['Arrows: ', progressbar.AnimatedMarker(markers='←↖↑↗→↘↓↙')] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(24)): time.sleep(0.1) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @example def animated_filled_arrows() -> None: # You may need python 3.x to see this correctly try: widgets = ['Arrows: ', progressbar.AnimatedMarker(markers='◢◣◤◥')] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(24)): time.sleep(0.1) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @example def animated_wheels() -> None: # You may need python 3.x to see this correctly try: widgets = ['Wheels: ', progressbar.AnimatedMarker(markers='◐◓◑◒')] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(24)): time.sleep(0.1) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @example def format_label_bouncer() -> None: widgets = [ progressbar.FormatLabel('Bouncer: value %(value)d - '), progressbar.BouncingBar(), ] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(100)): time.sleep(0.01) @example def format_label_rotating_bouncer() -> None: widgets = [ progressbar.FormatLabel('Animated Bouncer: value %(value)d - '), progressbar.BouncingBar(marker=progressbar.RotatingMarker()), ] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(i for i in range(18)): time.sleep(0.1) @example def with_right_justify() -> None: with progressbar.ProgressBar( max_value=10, term_width=20, left_justify=False ) as progress: assert progress.term_width is not None for i in range(10): progress.update(i) @example def exceeding_maximum() -> None: with progressbar.ProgressBar(max_value=1) as progress, contextlib.suppress( ValueError ): progress.update(2) @example def reaching_maximum() -> None: progress = progressbar.ProgressBar(max_value=1) with contextlib.suppress(RuntimeError): progress.update(1) @example def stdout_redirection() -> None: with progressbar.ProgressBar(redirect_stdout=True) as progress: print('', file=sys.stdout) progress.update(0) @example def stderr_redirection() -> None: with progressbar.ProgressBar(redirect_stderr=True) as progress: print('', file=sys.stderr) progress.update(0) @example def rotating_bouncing_marker() -> None: widgets = [progressbar.BouncingBar(marker=progressbar.RotatingMarker())] with progressbar.ProgressBar( widgets=widgets, max_value=20, term_width=10 ) as progress: for i in range(20): time.sleep(0.1) progress.update(i) widgets = [ progressbar.BouncingBar( marker=progressbar.RotatingMarker(), fill_left=False ) ] with progressbar.ProgressBar( widgets=widgets, max_value=20, term_width=10 ) as progress: for i in range(20): time.sleep(0.1) progress.update(i) @example def incrementing_bar() -> None: bar = progressbar.ProgressBar( widgets=[ progressbar.Percentage(), progressbar.Bar(), ], max_value=10, ).start() for _ in range(10): # do something time.sleep(0.1) bar += 1 bar.finish() @example def increment_bar_with_output_redirection() -> None: widgets = [ 'Test: ', progressbar.Percentage(), ' ', progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', progressbar.ETA(), ' ', progressbar.FileTransferSpeed(), ] bar = progressbar.ProgressBar( widgets=widgets, max_value=100, redirect_stdout=True ).start() for i in range(10): # do something time.sleep(0.01) bar += 10 print('Got', i) bar.finish() @example def eta_types_demonstration() -> None: widgets = [ progressbar.Percentage(), ' ETA: ', progressbar.ETA(), ' Adaptive : ', progressbar.AdaptiveETA(), ' Smoothing(a=0.1): ', progressbar.SmoothingETA(smoothing_parameters=dict(alpha=0.1)), ' Smoothing(a=0.9): ', progressbar.SmoothingETA(smoothing_parameters=dict(alpha=0.9)), ' Absolute: ', progressbar.AbsoluteETA(), ' Transfer: ', progressbar.FileTransferSpeed(), ' Adaptive T: ', progressbar.AdaptiveTransferSpeed(), ' ', progressbar.Bar(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=500) bar.start() for i in range(500): if i < 100: time.sleep(0.02) elif i > 400: time.sleep(0.1) else: time.sleep(0.01) bar.update(i + 1) bar.finish() @example def adaptive_eta_without_value_change() -> None: # Testing progressbar.AdaptiveETA when the value doesn't actually change bar = progressbar.ProgressBar( widgets=[ progressbar.AdaptiveETA(), progressbar.AdaptiveTransferSpeed(), ], max_value=2, poll_interval=0.0001, ) bar.start() for _ in range(100): bar.update(1) time.sleep(0.1) bar.finish() @example def iterator_with_max_value() -> None: # Testing using progressbar as an iterator with a max value bar = progressbar.ProgressBar() for _ in bar(iter(range(100)), 100): # iter range is a way to get an iterator in both python 2 and 3 time.sleep(0.01) @example def eta() -> None: widgets = [ 'Test: ', progressbar.Percentage(), ' | ETA: ', progressbar.ETA(), ' | AbsoluteETA: ', progressbar.AbsoluteETA(), ' | AdaptiveETA: ', progressbar.AdaptiveETA(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=50).start() for i in range(50): time.sleep(0.1) bar.update(i + 1) bar.finish() @example def variables() -> None: # Use progressbar.Variable to keep track of some parameter(s) during # your calculations widgets = [ progressbar.Percentage(), progressbar.Bar(), progressbar.Variable('loss'), ', ', progressbar.Variable('username', width=12, precision=12), ] with progressbar.ProgressBar(max_value=100, widgets=widgets) as bar: min_so_far = 1 for i in range(100): time.sleep(0.01) val = random.random() if val < min_so_far: min_so_far = val bar.update(i, loss=min_so_far, username='Some user') @example def user_variables() -> None: tasks = { 'Download': [ 'SDK', 'IDE', 'Dependencies', ], 'Build': [ 'Compile', 'Link', ], 'Test': [ 'Unit tests', 'Integration tests', 'Regression tests', ], 'Deploy': [ 'Send to server', 'Restart server', ], } 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: for tasks_name, subtasks in tasks.items(): for subtask_name in subtasks: for _ in range(10): bar.update( bar.value + 1, task=tasks_name, subtask=subtask_name ) time.sleep(0.1) @example def format_custom_text() -> None: format_custom_text = progressbar.FormatCustomText( 'Spam: %(spam).1f kg, eggs: %(eggs)d', dict( spam=0.25, eggs=3, ), ) bar = progressbar.ProgressBar( widgets=[ format_custom_text, ' :: ', progressbar.Percentage(), ] ) for i in bar(range(25)): format_custom_text.update_mapping(eggs=i * 2) time.sleep(0.1) @example def simple_api_example() -> None: bar = progressbar.ProgressBar(widget_kwargs=dict(fill='█')) for _ in bar(range(200)): time.sleep(0.02) @example def eta_on_generators(): def gen(): for _ in range(200): yield None widgets = [ progressbar.AdaptiveETA(), ' ', progressbar.ETA(), ' ', progressbar.Timer(), ] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(gen()): time.sleep(0.02) @example def percentage_on_generators(): def gen(): for _ in range(200): yield None widgets = [ progressbar.Counter(), ' ', progressbar.Percentage(), ' ', progressbar.SimpleProgress(), ' ', ] bar = progressbar.ProgressBar(widgets=widgets) for _ in bar(gen()): time.sleep(0.02) def test(*tests) -> None: if tests: no_tests = True for example in examples: for test in tests: if test in example.__name__: example() no_tests = False break if no_tests: for example in examples: print('Skipping', example.__name__) else: for example in examples: example() if __name__ == '__main__': try: test(*sys.argv[1:]) except KeyboardInterrupt: sys.stdout.write('\nQuitting examples.\n') python-progressbar-4.5.0/progressbar/000077500000000000000000000000001466372462000177335ustar00rootroot00000000000000python-progressbar-4.5.0/progressbar/__about__.py000066400000000000000000000017561466372462000222240ustar00rootroot00000000000000"""Text progress bar library for Python. A text progress bar is typically used to display the progress of a long running operation, providing a visual cue that processing is underway. The ProgressBar class manages the current progress, and the format of the line is given by a number of widgets. A widget is an object that may display differently depending on the state of the progress bar. 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. """ __title__ = 'Python Progressbar' __package_name__ = 'progressbar2' __author__ = 'Rick van Hattem (Wolph)' __description__: str = ' '.join( """ A Python Progressbar library to provide visual (yet text based) progress to long running operations. """.strip().split(), ) __email__ = 'wolph@wol.ph' __version__ = '4.5.0' __license__ = 'BSD' __copyright__ = 'Copyright 2015 Rick van Hattem (Wolph)' __url__ = 'https://github.com/WoLpH/python-progressbar' python-progressbar-4.5.0/progressbar/__init__.py000066400000000000000000000035331466372462000220500ustar00rootroot00000000000000from datetime import date from .__about__ import __author__, __version__ from .algorithms import ( DoubleExponentialMovingAverage, ExponentialMovingAverage, SmoothingAlgorithm, ) from .bar import DataTransferBar, NullBar, ProgressBar from .base import UnknownLength from .multi import MultiBar, SortKey from .shortcuts import progressbar from .terminal.stream import LineOffsetStreamWrapper from .utils import len_color, streams from .widgets import ( ETA, AbsoluteETA, AdaptiveETA, AdaptiveTransferSpeed, AnimatedMarker, Bar, BouncingBar, Counter, CurrentTime, DataSize, DynamicMessage, FileTransferSpeed, FormatCustomText, FormatLabel, FormatLabelBar, GranularBar, JobStatusBar, MultiProgressBar, MultiRangeBar, Percentage, PercentageLabelBar, ReverseBar, RotatingMarker, SimpleProgress, SmoothingETA, Timer, Variable, VariableMixin, ) __date__ = str(date.today()) __all__ = [ 'progressbar', 'len_color', 'streams', 'Timer', 'ETA', 'AdaptiveETA', 'AbsoluteETA', 'SmoothingETA', 'SmoothingAlgorithm', 'ExponentialMovingAverage', 'DoubleExponentialMovingAverage', 'DataSize', 'FileTransferSpeed', 'AdaptiveTransferSpeed', 'AnimatedMarker', 'Counter', 'Percentage', 'FormatLabel', 'SimpleProgress', 'Bar', 'ReverseBar', 'BouncingBar', 'UnknownLength', 'ProgressBar', 'DataTransferBar', 'RotatingMarker', 'VariableMixin', 'MultiRangeBar', 'MultiProgressBar', 'GranularBar', 'FormatLabelBar', 'PercentageLabelBar', 'Variable', 'DynamicMessage', 'FormatCustomText', 'CurrentTime', 'NullBar', '__author__', '__version__', 'LineOffsetStreamWrapper', 'MultiBar', 'SortKey', 'JobStatusBar', ] python-progressbar-4.5.0/progressbar/__main__.py000066400000000000000000000253151466372462000220330ustar00rootroot00000000000000from __future__ import annotations import argparse import contextlib import pathlib import sys import typing from pathlib import Path from typing import IO, BinaryIO, TextIO import progressbar def size_to_bytes(size_str: str) -> int: """ Convert a size string with suffixes 'k', 'm', etc., to bytes. Note: This function also supports '@' as a prefix to a file path to get the file size. >>> size_to_bytes('1024k') 1048576 >>> size_to_bytes('1024m') 1073741824 >>> size_to_bytes('1024g') 1099511627776 >>> size_to_bytes('1024') 1024 >>> size_to_bytes('1024p') 1125899906842624 """ # Define conversion rates suffix_exponent = { 'k': 1, 'm': 2, 'g': 3, 't': 4, 'p': 5, } # Initialize the default exponent to 0 (for bytes) exponent = 0 # Check if the size starts with '@' (for file sizes, not handled here) if size_str.startswith('@'): return pathlib.Path(size_str[1:]).stat().st_size # Check if the last character is a known suffix and adjust the multiplier if size_str[-1].lower() in suffix_exponent: # Update exponent based on the suffix exponent = suffix_exponent[size_str[-1].lower()] # Remove the suffix from the size_str size_str = size_str[:-1] # Convert the size_str to an integer and apply the exponent return int(size_str) * (1024**exponent) def create_argument_parser() -> argparse.ArgumentParser: """ Create the argument parser for the `progressbar` command. """ parser = argparse.ArgumentParser( description=""" Monitor the progress of data through a pipe. Note that this is a Python implementation of the original `pv` command that is functional but not yet feature complete. """ ) # Display switches parser.add_argument( '-p', '--progress', action='store_true', help='Turn the progress bar on.', ) parser.add_argument( '-t', '--timer', action='store_true', help='Turn the timer on.' ) parser.add_argument( '-e', '--eta', action='store_true', help='Turn the ETA timer on.' ) parser.add_argument( '-I', '--fineta', action='store_true', help='Display the ETA as local time of arrival.', ) parser.add_argument( '-r', '--rate', action='store_true', help='Turn the rate counter on.' ) parser.add_argument( '-a', '--average-rate', action='store_true', help='Turn the average rate counter on.', ) parser.add_argument( '-b', '--bytes', action='store_true', help='Turn the total byte counter on.', ) parser.add_argument( '-8', '--bits', action='store_true', help='Display total bits instead of bytes.', ) parser.add_argument( '-T', '--buffer-percent', action='store_true', help='Turn on the transfer buffer percentage display.', ) parser.add_argument( '-A', '--last-written', type=int, help='Show the last NUM bytes written.', ) parser.add_argument( '-F', '--format', type=str, help='Use the format string FORMAT for output format.', ) parser.add_argument( '-n', '--numeric', action='store_true', help='Numeric output.' ) parser.add_argument( '-q', '--quiet', action='store_true', help='No output.', ) # Output modifiers parser.add_argument( '-W', '--wait', action='store_true', help='Wait until the first byte has been transferred.', ) parser.add_argument('-D', '--delay-start', type=float, help='Delay start.') parser.add_argument( '-s', '--size', type=str, help='Assume total data size is SIZE.' ) parser.add_argument( '-l', '--line-mode', action='store_true', help='Count lines instead of bytes.', ) parser.add_argument( '-0', '--null', action='store_true', help='Count lines terminated with a zero byte.', ) parser.add_argument( '-i', '--interval', type=float, help='Interval between updates.' ) parser.add_argument( '-m', '--average-rate-window', type=int, help='Window for average rate calculation.', ) parser.add_argument( '-w', '--width', type=int, help='Assume terminal is WIDTH characters wide.', ) parser.add_argument( '-H', '--height', type=int, help='Assume terminal is HEIGHT rows high.' ) parser.add_argument( '-N', '--name', type=str, help='Prefix output information with NAME.' ) parser.add_argument( '-f', '--force', action='store_true', help='Force output.' ) parser.add_argument( '-c', '--cursor', action='store_true', help='Use cursor positioning escape sequences.', ) # Data transfer modifiers parser.add_argument( '-L', '--rate-limit', type=str, help='Limit transfer to RATE bytes per second.', ) parser.add_argument( '-B', '--buffer-size', type=str, help='Use transfer buffer size of BYTES.', ) parser.add_argument( '-C', '--no-splice', action='store_true', help='Never use splice.' ) parser.add_argument( '-E', '--skip-errors', action='store_true', help='Ignore read errors.' ) parser.add_argument( '-Z', '--error-skip-block', type=str, help='Skip block size when ignoring errors.', ) parser.add_argument( '-S', '--stop-at-size', action='store_true', help='Stop transferring after SIZE bytes.', ) parser.add_argument( '-Y', '--sync', action='store_true', help='Synchronise buffer caches to disk after writes.', ) parser.add_argument( '-K', '--direct-io', action='store_true', help='Set O_DIRECT flag on all inputs/outputs.', ) parser.add_argument( '-X', '--discard', action='store_true', help='Discard input data instead of transferring it.', ) parser.add_argument( '-d', '--watchfd', type=str, help='Watch file descriptor of process.' ) parser.add_argument( '-R', '--remote', type=int, help='Remote control another running instance of pv.', ) # General options parser.add_argument( '-P', '--pidfile', type=pathlib.Path, help='Save process ID in FILE.' ) parser.add_argument( 'input', help='Input file path. Uses stdin if not specified.', default='-', nargs='*', ) parser.add_argument( '-o', '--output', default='-', help='Output file path. Uses stdout if not specified.', ) return parser def main(argv: list[str] | None = None) -> None: # noqa: C901 """ Main function for the `progressbar` command. Args: argv (list[str] | None): Command-line arguments passed to the script. Returns: None """ parser: argparse.ArgumentParser = create_argument_parser() args: argparse.Namespace = parser.parse_args(argv) with contextlib.ExitStack() as stack: output_stream: typing.IO[typing.Any] = _get_output_stream( args.output, args.line_mode, stack ) input_paths: list[BinaryIO | TextIO | Path | IO[typing.Any]] = [] total_size: int = 0 filesize_available: bool = True for filename in args.input: input_path: typing.IO[typing.Any] | pathlib.Path if filename == '-': if args.line_mode: input_path = sys.stdin else: input_path = sys.stdin.buffer filesize_available = False else: input_path = pathlib.Path(filename) if not input_path.exists(): parser.error(f'File not found: {filename}') if not args.size: total_size += input_path.stat().st_size input_paths.append(input_path) # Determine the size for the progress bar (if provided) if args.size: total_size = size_to_bytes(args.size) filesize_available = True if filesize_available: # Create the progress bar components widgets = [ progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.Timer(), ' ', progressbar.FileTransferSpeed(), ] else: widgets = [ progressbar.SimpleProgress(), ' ', progressbar.DataSize(), ' ', progressbar.Timer(), ] if args.eta: widgets.append(' ') widgets.append(progressbar.AdaptiveETA()) # Initialize the progress bar bar = progressbar.ProgressBar( # widgets=widgets, max_value=total_size or None, max_error=False, ) # Data processing and updating the progress bar buffer_size = ( size_to_bytes(args.buffer_size) if args.buffer_size else 1024 ) total_transferred = 0 bar.start() with contextlib.suppress(KeyboardInterrupt): for input_path in input_paths: if isinstance(input_path, pathlib.Path): input_stream = stack.enter_context( input_path.open('r' if args.line_mode else 'rb') ) else: input_stream = input_path while True: data: str | bytes if args.line_mode: data = input_stream.readline(buffer_size) else: data = input_stream.read(buffer_size) if not data: break output_stream.write(data) total_transferred += len(data) bar.update(total_transferred) bar.finish(dirty=True) def _get_output_stream( output: str | None, line_mode: bool, stack: contextlib.ExitStack, ) -> typing.IO[typing.Any]: if output and output != '-': mode = 'w' if line_mode else 'wb' return stack.enter_context(open(output, mode)) # noqa: SIM115 elif line_mode: return sys.stdout else: return sys.stdout.buffer if __name__ == '__main__': main() python-progressbar-4.5.0/progressbar/algorithms.py000066400000000000000000000032041466372462000224550ustar00rootroot00000000000000from __future__ import annotations import abc from datetime import timedelta class SmoothingAlgorithm(abc.ABC): @abc.abstractmethod def __init__(self, **kwargs): raise NotImplementedError @abc.abstractmethod def update(self, new_value: float, elapsed: timedelta) -> float: """Updates the algorithm with a new value and returns the smoothed value. """ raise NotImplementedError class ExponentialMovingAverage(SmoothingAlgorithm): """ The Exponential Moving Average (EMA) is an exponentially weighted moving average that reduces the lag that's typically associated with a simple moving average. It's more responsive to recent changes in data. """ def __init__(self, alpha: float = 0.5) -> None: self.alpha = alpha self.value = 0 def update(self, new_value: float, elapsed: timedelta) -> float: self.value = self.alpha * new_value + (1 - self.alpha) * self.value return self.value class DoubleExponentialMovingAverage(SmoothingAlgorithm): """ The Double Exponential Moving Average (DEMA) is essentially an EMA of an EMA, which reduces the lag that's typically associated with a simple EMA. It's more responsive to recent changes in data. """ def __init__(self, alpha: float = 0.5) -> None: self.alpha = alpha self.ema1 = 0 self.ema2 = 0 def update(self, new_value: float, elapsed: timedelta) -> float: self.ema1 = self.alpha * new_value + (1 - self.alpha) * self.ema1 self.ema2 = self.alpha * self.ema1 + (1 - self.alpha) * self.ema2 return 2 * self.ema1 - self.ema2 python-progressbar-4.5.0/progressbar/bar.py000066400000000000000000001203371466372462000210570ustar00rootroot00000000000000from __future__ import annotations import abc import contextlib import itertools import logging import math import os import sys import time import timeit import typing import warnings from copy import deepcopy from datetime import datetime from types import FrameType from python_utils import converters, types import progressbar.env import progressbar.terminal import progressbar.terminal.stream from . import ( base, utils, widgets, widgets as widgets_module, # Avoid name collision ) from .terminal import os_specific logger = logging.getLogger(__name__) # float also accepts integers and longs but we don't want an explicit union # due to type checking complexity NumberT = float ValueT = typing.Union[NumberT, typing.Type[base.UnknownLength], None] T = types.TypeVar('T') class ProgressBarMixinBase(abc.ABC): _started = False _finished = False _last_update_time: types.Optional[float] = None #: The terminal width. This should be automatically detected but will #: 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.MutableSequence[widgets_module.WidgetBase | str] #: When going beyond the max_value, raise an error if True or silently #: ignore otherwise max_error: bool #: Prefix the progressbar with the given string prefix: types.Optional[str] #: Suffix the progressbar with the given string suffix: types.Optional[str] #: Justify to the left if `True` or the right if `False` left_justify: bool #: The default keyword arguments for the `default_widgets` if no widgets #: are configured widget_kwargs: types.Dict[str, types.Any] #: Custom length function for multibyte characters such as CJK # mypy and pyright can't agree on what the correct one is... so we'll # need to use a helper function :( # custom_len: types.Callable[['ProgressBarMixinBase', str], int] custom_len: types.Callable[[str], int] #: The time the progress bar was started initial_start_time: types.Optional[datetime] #: The interval to poll for updates in seconds if there are updates poll_interval: types.Optional[float] #: The minimum interval to poll for updates in seconds even if there are #: no updates min_poll_interval: float #: Deprecated: The number of intervals that can fit on the screen with a #: minimum of 100 num_intervals: int = 0 #: Deprecated: The `next_update` is kept for compatibility with external #: libs: https://github.com/WoLpH/python-progressbar/issues/207 next_update: int = 0 #: Current progress (min_value <= value <= max_value) value: NumberT #: Previous progress value previous_value: types.Optional[NumberT] #: The minimum/start value for the progress bar min_value: NumberT #: Maximum (and final) value. Beyond this value an error will be raised #: unless the `max_error` parameter is `False`. max_value: ValueT #: The time the progressbar reached `max_value` or when `finish()` was #: called. end_time: types.Optional[datetime] #: The time `start()` was called or iteration started. start_time: types.Optional[datetime] #: Seconds between `start_time` and last call to `update()` seconds_elapsed: float #: Extra data for widgets with persistent state. This is used by #: sampling widgets for example. Since widgets can be shared between #: multiple progressbars we need to store the state with the progressbar. extra: types.Dict[str, types.Any] def get_last_update_time(self) -> types.Optional[datetime]: if self._last_update_time: return datetime.fromtimestamp(self._last_update_time) else: 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 last_update_time = property(get_last_update_time, set_last_update_time) def __init__(self, **kwargs: typing.Any): # noqa: B027 pass def start(self, **kwargs: typing.Any): self._started = True def update(self, value: ValueT = None): # noqa: B027 pass def finish(self): # pragma: no cover self._finished = True def __del__(self): if not self._finished and self._started: # pragma: no cover # We're not using contextlib.suppress here because during teardown # contextlib is not available anymore. try: # noqa: SIM105 self.finish() except AttributeError: pass def __getstate__(self): return self.__dict__ def data(self) -> types.Dict[str, types.Any]: # pragma: no cover raise NotImplementedError() def started(self) -> bool: return self._finished or self._started def finished(self) -> bool: return self._finished class ProgressBarBase(types.Iterable[NumberT], ProgressBarMixinBase): _index_counter = itertools.count() index: int = -1 label: str = '' def __init__(self, **kwargs: typing.Any): 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): # The file descriptor to write to. Defaults to `sys.stderr` fd: base.TextIO = sys.stderr #: Set the terminal to be ANSI compatible. If a terminal is ANSI #: compatible we will automatically enable `colors` and disable #: `line_breaks`. is_ansi_terminal: bool | None = False #: Whether the file descriptor is a terminal or not. This is used to #: determine whether to use ANSI escape codes or not. is_terminal: bool | None #: Whether to print line breaks. This is useful for logging the #: progressbar. When disabled the current line is overwritten. line_breaks: bool | None = 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`. enable_colors: progressbar.env.ColorSupport = progressbar.env.COLOR_SUPPORT def __init__( self, fd: base.TextIO = sys.stderr, is_terminal: bool | None = None, line_breaks: bool | None = None, enable_colors: progressbar.env.ColorSupport | None = None, line_offset: int = 0, **kwargs: typing.Any, ): if fd is sys.stdout: fd = utils.streams.original_stdout elif fd is sys.stderr: fd = utils.streams.original_stderr fd = self._apply_line_offset(fd, line_offset) self.fd = fd self.is_ansi_terminal = progressbar.env.is_ansi_terminal(fd) self.is_terminal = progressbar.env.is_terminal(fd, is_terminal) self.line_breaks = self._determine_line_breaks(line_breaks) self.enable_colors = self._determine_enable_colors(enable_colors) super().__init__(**kwargs) def _apply_line_offset( self, fd: base.TextIO, line_offset: int, ) -> base.TextIO: if line_offset: return progressbar.terminal.stream.LineOffsetStreamWrapper( line_offset, fd, ) else: return fd def _determine_line_breaks(self, line_breaks: bool | None) -> bool | None: if line_breaks is None: return progressbar.env.env_flag( 'PROGRESSBAR_LINE_BREAKS', not self.is_terminal, ) else: return line_breaks def _determine_enable_colors( self, enable_colors: progressbar.env.ColorSupport | None, ) -> progressbar.env.ColorSupport: """ Determines the color support for the progress bar. This method checks the `enable_colors` parameter and the environment variables `PROGRESSBAR_ENABLE_COLORS` and `FORCE_COLOR` to determine the color support. If `enable_colors` is: - `None`, it checks the environment variables and the terminal compatibility to ANSI. - `True`, it sets the color support to XTERM_256. - `False`, it sets the color support to NONE. - For different values that are not instances of `progressbar.env.ColorSupport`, it raises a ValueError. Args: enable_colors (progressbar.env.ColorSupport | None): The color support setting from the user. It can be None, True, False, or an instance of `progressbar.env.ColorSupport`. Returns: progressbar.env.ColorSupport: The determined color support. Raises: ValueError: If `enable_colors` is not None, True, False, or an instance of `progressbar.env.ColorSupport`. """ color_support: progressbar.env.ColorSupport if enable_colors is None: colors = ( progressbar.env.env_flag('PROGRESSBAR_ENABLE_COLORS'), progressbar.env.env_flag('FORCE_COLOR'), self.is_ansi_terminal, ) for color_enabled in colors: if color_enabled is not None: if color_enabled: color_support = progressbar.env.COLOR_SUPPORT else: color_support = progressbar.env.ColorSupport.NONE break else: color_support = progressbar.env.ColorSupport.NONE elif enable_colors is True: color_support = progressbar.env.ColorSupport.XTERM_256 elif enable_colors is False: color_support = progressbar.env.ColorSupport.NONE elif isinstance(enable_colors, progressbar.env.ColorSupport): color_support = enable_colors else: raise ValueError(f'Invalid color support value: {enable_colors}') return color_support def print(self, *args: types.Any, **kwargs: types.Any) -> None: print(*args, file=self.fd, **kwargs) def start(self, **kwargs: typing.Any): os_specific.set_console_mode() super().start() def update(self, *args: types.Any, **kwargs: types.Any) -> None: ProgressBarMixinBase.update(self, *args, **kwargs) line: str = converters.to_unicode(self._format_line()) if not self.enable_colors: line = utils.no_color(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 self.fd.write(types.cast(str, line.encode('ascii', 'replace'))) def finish( self, *args: types.Any, **kwargs: types.Any, ) -> None: # pragma: no cover os_specific.reset_console_mode() if self._finished: return end = kwargs.pop('end', '\n') ProgressBarMixinBase.finish(self, *args, **kwargs) if end and not self.line_breaks: self.fd.write(end) self.fd.flush() def _format_line(self): "Joins the widgets and justifies the line." widgets = ''.join(self._to_unicode(self._format_widgets())) if self.left_justify: return widgets.ljust(self.term_width) else: return widgets.rjust(self.term_width) def _format_widgets(self): result = [] expanding = [] width = self.term_width data = self.data() for index, widget in enumerate(self.widgets): if isinstance( widget, widgets.WidgetBase, ) and not widget.check_size(self): continue elif isinstance(widget, widgets.AutoWidthWidgetBase): result.append(widget) expanding.insert(0, index) elif isinstance(widget, str): result.append(widget) width -= self.custom_len(widget) # type: ignore else: widget_output = converters.to_unicode(widget(self, data)) result.append(widget_output) width -= self.custom_len(widget_output) # type: ignore count = len(expanding) while expanding: portion = max(int(math.ceil(width * 1.0 / count)), 0) index = expanding.pop() widget = result[index] count -= 1 widget_output = widget(self, data, portion) width -= self.custom_len(widget_output) # type: ignore result[index] = widget_output return result @classmethod def _to_unicode(cls, args: typing.Any): for arg in args: yield converters.to_unicode(arg) class ResizableMixin(ProgressBarMixinBase): def __init__(self, term_width: int | None = None, **kwargs: typing.Any): ProgressBarMixinBase.__init__(self, **kwargs) self.signal_set = False if term_width: self.term_width = term_width else: # pragma: no cover with contextlib.suppress(Exception): self._handle_resize() import signal self._prev_handle = signal.getsignal( signal.SIGWINCH # type: ignore ) signal.signal( signal.SIGWINCH, self._handle_resize, # type: ignore ) self.signal_set = True def _handle_resize( self, signum: int | None = None, frame: None | FrameType = None ): "Tries to catch resize signals sent from the terminal." w, h = utils.get_terminal_size() self.term_width = w def finish(self): # pragma: no cover ProgressBarMixinBase.finish(self) if self.signal_set: with contextlib.suppress(Exception): import signal signal.signal( signal.SIGWINCH, self._prev_handle, # type: ignore ) class StdRedirectMixin(DefaultFdMixin): redirect_stderr: bool = False redirect_stdout: bool = False stdout: utils.WrappingIO | base.IO[typing.Any] stderr: utils.WrappingIO | base.IO[typing.Any] _stdout: base.IO[typing.Any] _stderr: base.IO[typing.Any] def __init__( self, redirect_stderr: bool = False, redirect_stdout: bool = False, **kwargs, ): DefaultFdMixin.__init__(self, **kwargs) self.redirect_stderr = redirect_stderr self.redirect_stdout = redirect_stdout self._stdout = self.stdout = sys.stdout self._stderr = self.stderr = sys.stderr def start(self, *args: typing.Any, **kwargs: typing.Any): if self.redirect_stdout: utils.streams.wrap_stdout() if self.redirect_stderr: utils.streams.wrap_stderr() self._stdout = utils.streams.original_stdout self._stderr = utils.streams.original_stderr self.stdout = utils.streams.stdout self.stderr = utils.streams.stderr utils.streams.start_capturing(self) DefaultFdMixin.start(self, *args, **kwargs) def update(self, value: types.Optional[NumberT] = None): if not self.line_breaks and utils.streams.needs_clear(): self.fd.write('\r' + ' ' * self.term_width + '\r') utils.streams.flush() DefaultFdMixin.update(self, value=value) def finish(self, end='\n'): DefaultFdMixin.finish(self, end=end) utils.streams.stop_capturing(self) if self.redirect_stdout: utils.streams.unwrap_stdout() if self.redirect_stderr: utils.streams.unwrap_stderr() class ProgressBar( StdRedirectMixin, ResizableMixin, ProgressBarBase, ): """The ProgressBar class which updates and prints the bar. Args: min_value (int): The minimum/start value for the progress bar max_value (int): The maximum/end value for the progress bar. Defaults to `_DEFAULT_MAXVAL` widgets (list): The widgets to render, defaults to the result of `default_widget()` left_justify (bool): Justify to the left if `True` or the right if `False` initial_value (int): The value to start with poll_interval (float): The update interval in seconds. Note that if your widgets include timers or animations, the actual interval may be smaller (faster updates). Also note that updates never happens faster than `min_poll_interval` which can be used for reduced output in logs min_poll_interval (float): The minimum update interval in seconds. The bar will _not_ be updated faster than this, despite changes in the progress, unless `force=True`. This is limited to be at least `_MINIMUM_UPDATE_INTERVAL`. If available, it is also bound by the environment variable PROGRESSBAR_MINIMUM_UPDATE_INTERVAL widget_kwargs (dict): The default keyword arguments for widgets custom_len (function): Method to override how the line width is calculated. When using non-latin characters the width calculation might be off by default max_error (bool): When True the progressbar will raise an error if it goes beyond it's set max_value. Otherwise the max_value is simply raised when needed prefix (str): Prefix the progressbar with the given string suffix (str): Prefix the progressbar with the given string variables (dict): User-defined variables variables that can be used 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: >>> progress = ProgressBar().start() >>> for i in range(100): ... progress.update(i + 1) ... # do something >>> progress.finish() You can also use a ProgressBar as an iterator: >>> progress = ProgressBar() >>> some_iterable = range(100) >>> for i in progress(some_iterable): ... # do something ... pass Since the progress bar is incredibly customizable you can specify different widgets of any type in any order. You can even write your own widgets! However, since there are already a good number of widgets you should probably play around with them before moving on to create your own widgets. The term_width parameter represents the current terminal width. If the parameter is set to an integer then the progress bar will use that, otherwise it will attempt to determine the terminal width falling back to 80 columns if the width cannot be determined. When implementing a widget's update method you are passed a reference to the current progress bar. As a result, you have access to the ProgressBar's methods and attributes. Although there is nothing preventing you from changing the ProgressBar you should treat it as read only. """ _iterable: types.Optional[types.Iterator] _DEFAULT_MAXVAL: type[base.UnknownLength] = base.UnknownLength # 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, min_value: NumberT = 0, max_value: ValueT = None, widgets: types.Optional[ types.Sequence[widgets_module.WidgetBase | str] ] = None, left_justify: bool = True, initial_value: NumberT = 0, poll_interval: types.Optional[float] = None, widget_kwargs: types.Optional[types.Dict[str, types.Any]] = None, custom_len: types.Callable[[str], int] = utils.len_color, max_error=True, prefix=None, suffix=None, variables=None, min_poll_interval=None, **kwargs, ): # sourcery skip: low-code-quality """Initializes a progress bar with sane defaults.""" StdRedirectMixin.__init__(self, **kwargs) ResizableMixin.__init__(self, **kwargs) ProgressBarBase.__init__(self, **kwargs) if not max_value and kwargs.get('maxval') is not None: warnings.warn( 'The usage of `maxval` is deprecated, please use ' '`max_value` instead', DeprecationWarning, stacklevel=1, ) max_value = kwargs.get('maxval') if not poll_interval and kwargs.get('poll'): warnings.warn( 'The usage of `poll` is deprecated, please use ' '`poll_interval` instead', DeprecationWarning, stacklevel=1, ) poll_interval = kwargs.get('poll') if max_value and min_value > types.cast(NumberT, 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` self.max_value = max_value # type: ignore self.max_error = max_error # Only copy the widget if it's safe to copy. Most widgets are so we # assume this to be true self.widgets = [] for widget in widgets or []: if getattr(widget, 'copy', True): widget = deepcopy(widget) self.widgets.append(widget) self.prefix = prefix self.suffix = suffix self.widget_kwargs = widget_kwargs or {} self.left_justify = left_justify self.value = initial_value self._iterable = None self.custom_len = custom_len # type: ignore self.initial_start_time = kwargs.get('start_time') self.init() # Convert a given timedelta to a floating point number as internal # interval. We're not using timedelta's internally for two reasons: # 1. Backwards compatibility (most important one) # 2. Performance. Even though the amount of time it takes to compare a # timedelta with a float versus a float directly is negligible, this # comparison is run for _every_ update. With billions of updates # (downloading a 1GiB file for example) this adds up. poll_interval = utils.deltas_to_seconds(poll_interval, default=None) min_poll_interval = utils.deltas_to_seconds( min_poll_interval, default=None, ) self._MINIMUM_UPDATE_INTERVAL = ( utils.deltas_to_seconds(self._MINIMUM_UPDATE_INTERVAL) or self._MINIMUM_UPDATE_INTERVAL ) # Note that the _MINIMUM_UPDATE_INTERVAL sets the minimum in case of # low values. self.poll_interval = poll_interval self.min_poll_interval = max( min_poll_interval or self._MINIMUM_UPDATE_INTERVAL, self._MINIMUM_UPDATE_INTERVAL, float(os.environ.get('PROGRESSBAR_MINIMUM_UPDATE_INTERVAL', 0)), ) # type: ignore # 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) and widget.name not in self.variables ): self.variables[widget.name] = None @property def dynamic_messages(self): # pragma: no cover return self.variables @dynamic_messages.setter def dynamic_messages(self, value): # pragma: no cover self.variables = value def init(self): """ (re)initialize values to original state so the progressbar can be used (again). """ self.previous_value = None self.last_update_time = None self.start_time = None self.updates = 0 self.end_time = None self.extra = dict() self._last_update_timer = timeit.default_timer() @property def percentage(self) -> float | None: """Return current percentage, returns None if no max_value is given. >>> progress = ProgressBar() >>> progress.max_value = 10 >>> progress.min_value = 0 >>> progress.value = 0 >>> progress.percentage 0.0 >>> >>> progress.value = 1 >>> progress.percentage 10.0 >>> progress.value = 10 >>> progress.percentage 100.0 >>> progress.min_value = -10 >>> progress.percentage 100.0 >>> progress.value = 0 >>> progress.percentage 50.0 >>> progress.value = 5 >>> progress.percentage 75.0 >>> progress.value = -5 >>> progress.percentage 25.0 >>> progress.max_value = None >>> progress.percentage """ if self.max_value is None or self.max_value is base.UnknownLength: return None elif self.max_value: todo = self.value - self.min_value total = self.max_value - self.min_value # type: ignore percentage = 100.0 * todo / total else: percentage = 100.0 return percentage def data(self) -> types.Dict[str, types.Any]: """ Returns: dict: - `max_value`: The maximum value (can be None with iterators) - `start_time`: Start time of the widget - `last_update_time`: Last update time of the widget - `end_time`: End time of the widget - `value`: The current value - `previous_value`: The previous value - `updates`: The total update count - `total_seconds_elapsed`: The seconds since the bar started - `seconds_elapsed`: The seconds since the bar started modulo 60 - `minutes_elapsed`: The minutes since the bar started modulo 60 - `hours_elapsed`: The hours since the bar started modulo 24 - `days_elapsed`: The hours since the bar started - `time_elapsed`: The raw elapsed `datetime.timedelta` object - `percentage`: Percentage as a float or `None` if no max_value is available - `dynamic_messages`: Deprecated, use `variables` instead. - `variables`: Dictionary of user-defined variables for the :py:class:`~progressbar.widgets.Variable`'s. """ self._last_update_time = time.time() self._last_update_timer = timeit.default_timer() elapsed = self.last_update_time - self.start_time # type: ignore # For Python 2.7 and higher we have _`timedelta.total_seconds`, but we # want to support older versions as well total_seconds_elapsed = utils.deltas_to_seconds(elapsed) return dict( # The maximum value (can be None with iterators) max_value=self.max_value, # Start time of the widget start_time=self.start_time, # Last update time of the widget last_update_time=self.last_update_time, # End time of the widget end_time=self.end_time, # The current value value=self.value, # The previous value previous_value=self.previous_value, # The total update count updates=self.updates, # The seconds since the bar started total_seconds_elapsed=total_seconds_elapsed, # The seconds since the bar started modulo 60 seconds_elapsed=(elapsed.seconds % 60) + (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 hours_elapsed=(elapsed.seconds / (60 * 60)) % 24, # The hours since the bar started days_elapsed=(elapsed.seconds / (60 * 60 * 24)), # The raw elapsed `datetime.timedelta` object time_elapsed=elapsed, # Percentage as a float or `None` if no max_value is available percentage=self.percentage, # Dictionary of user-defined # :py:class:`progressbar.widgets.Variable`'s variables=self.variables, # Deprecated alias for `variables` dynamic_messages=self.variables, ) def default_widgets(self): if self.max_value: return [ widgets.Percentage(**self.widget_kwargs), ' ', widgets.SimpleProgress( format=f'({widgets.SimpleProgress.DEFAULT_FORMAT})', **self.widget_kwargs, ), ' ', widgets.Bar(**self.widget_kwargs), ' ', widgets.Timer(**self.widget_kwargs), ' ', widgets.SmoothingETA(**self.widget_kwargs), ] else: return [ widgets.AnimatedMarker(**self.widget_kwargs), ' ', widgets.BouncingBar(**self.widget_kwargs), ' ', widgets.Counter(**self.widget_kwargs), ' ', widgets.Timer(**self.widget_kwargs), ] def __call__(self, iterable, max_value=None): "Use a ProgressBar to iterate through an iterable." if max_value is not None: self.max_value = max_value elif self.max_value is None: try: self.max_value = len(iterable) except TypeError: # pragma: no cover self.max_value = base.UnknownLength self._iterable = iter(iterable) return self def __iter__(self): return self def __next__(self): try: if self._iterable is None: # pragma: no cover value = self.value else: value = next(self._iterable) if self.start_time is None: self.start() else: self.update(self.value + 1) except StopIteration: self.finish() raise except GeneratorExit: # pragma: no cover self.finish(dirty=True) raise else: return value def __exit__(self, exc_type, exc_value, traceback): self.finish(dirty=bool(exc_type)) def __enter__(self): return self # Create an alias so that Python 2.x won't complain about not being # an iterator. next = __next__ def __iadd__(self, value): "Updates the ProgressBar by adding a new value." return self.increment(value) def increment( self, value: NumberT = 1, *args: typing.Any, **kwargs: typing.Any ): self.update(self.value + value, *args, **kwargs) return self 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 return False elif self.poll_interval and delta > self.poll_interval: # Needs to redraw timers and animations return True # Update if value increment is not large enough to # add more bars to progressbar (according to current # terminal width) with contextlib.suppress(Exception): divisor: float = self.max_value / self.term_width # type: ignore value_divisor = self.value // divisor # type: ignore pvalue_divisor = self.previous_value // divisor # type: ignore if value_divisor != pvalue_divisor: return True # No need to redraw yet return False def update( self, value: ValueT = None, force: bool = False, **kwargs: typing.Any ): "Updates the ProgressBar to a new value." if self.start_time is None: self.start() if ( value is not None and value is not base.UnknownLength and isinstance(value, (int, float)) ): if self.max_value is base.UnknownLength: # Can't compare against unknown lengths so just update pass elif self.min_value > value: # type: ignore raise ValueError( f'Value {value} is too small. Should be ' f'between {self.min_value} and {self.max_value}', ) elif self.max_value < value: # type: ignore if self.max_error: raise ValueError( f'Value {value} is too large. Should be between ' f'{self.min_value} and {self.max_value}', ) else: value = typing.cast(NumberT, self.max_value) self.previous_value = self.value self.value = value # Save the updated values for dynamic messages variables_changed = self._update_variables(kwargs) if self._needs_update() or variables_changed or force: self._update_parents(value) def _update_variables(self, kwargs): variables_changed = False for key, value_ in kwargs.items(): if key not in self.variables: raise TypeError( 'update() got an unexpected variable name as argument ' '{key!r}', ) elif self.variables[key] != value_: self.variables[key] = kwargs[key] variables_changed = True return variables_changed def _update_parents(self, value: ValueT): self.updates += 1 ResizableMixin.update(self, value=value) ProgressBarBase.update(self, value=value) StdRedirectMixin.update(self, value=value) # type: ignore # Only flush if something was actually written self.fd.flush() def start( self, max_value: NumberT | None = None, init: bool = True, *args: typing.Any, **kwargs: typing.Any, ) -> ProgressBar: """Starts measuring time, and prints the bar at 0%. It returns self so you can use it like this: Args: max_value (int): The maximum value of the progressbar init (bool): (Re)Initialize the progressbar, this is useful if you wish to reuse the same progressbar but can be disabled if data needs to be persisted between runs >>> pbar = ProgressBar().start() >>> for i in range(100): ... # do something ... pbar.update(i + 1) >>> pbar.finish() """ if init: self.init() # Prevent multiple starts if self.start_time is not None: # pragma: no cover return self if max_value is not None: self.max_value = max_value if self.max_value is None: self.max_value = self._DEFAULT_MAXVAL StdRedirectMixin.start(self, max_value=max_value) ResizableMixin.start(self, max_value=max_value) ProgressBarBase.start(self, max_value=max_value) # Constructing the default widgets is only done when we know max_value if not self.widgets: self.widgets = self.default_widgets() self._init_prefix() self._init_suffix() self._calculate_poll_interval() self._verify_max_value() now = datetime.now() self.start_time = self.initial_start_time or now self.last_update_time = now self._last_update_timer = timeit.default_timer() self.update(self.min_value, force=True) return self def _init_suffix(self): if self.suffix: self.widgets.append( widgets.FormatLabel(self.suffix, new_style=True), ) # Unset the suffix variable after applying so an extra start() # won't keep copying it self.suffix = None def _init_prefix(self): if self.prefix: self.widgets.insert( 0, widgets.FormatLabel(self.prefix, new_style=True), ) # Unset the prefix variable after applying so an extra start() # won't keep copying it self.prefix = None def _verify_max_value(self): if ( self.max_value is not base.UnknownLength and self.max_value is not None and self.max_value < 0 # type: ignore ): raise ValueError(f'max_value out of range, got {self.max_value!r}') def _calculate_poll_interval(self) -> None: self.num_intervals = max(100, self.term_width) for widget in self.widgets: interval: int | float | None = utils.deltas_to_seconds( getattr(widget, 'INTERVAL', None), default=None, ) if interval is not None: self.poll_interval = min( self.poll_interval or interval, interval, ) def finish(self, end: str = '\n', dirty: bool = False): """ Puts the ProgressBar bar in the finished state. Also flushes and disables output buffering if this was the last progressbar running. Args: end (str): The string to end the progressbar with, defaults to a newline dirty (bool): When True the progressbar kept the current state and won't be set to 100 percent """ if not dirty: self.end_time = datetime.now() self.update(self.max_value, force=True) StdRedirectMixin.finish(self, end=end) ResizableMixin.finish(self) ProgressBarBase.finish(self) @property def currval(self): """ Legacy method to make progressbar-2 compatible with the original progressbar package. """ warnings.warn( 'The usage of `currval` is deprecated, please use ' '`value` instead', DeprecationWarning, stacklevel=1, ) return self.value class DataTransferBar(ProgressBar): """A progress bar with sensible defaults for downloads etc. This assumes that the values its given are numbers of bytes. """ def default_widgets(self): if self.max_value: return [ widgets.Percentage(), ' of ', widgets.DataSize('max_value'), ' ', widgets.Bar(), ' ', widgets.Timer(), ' ', widgets.SmoothingETA(), ] else: return [ widgets.AnimatedMarker(), ' ', widgets.DataSize(), ' ', widgets.Timer(), ] class NullBar(ProgressBar): """ Progress bar that does absolutely nothing. Useful for single verbosity flags. """ def start(self, *args: typing.Any, **kwargs: typing.Any): return self def update(self, *args: typing.Any, **kwargs: typing.Any): return self def finish(self, *args: typing.Any, **kwargs: typing.Any): return self python-progressbar-4.5.0/progressbar/base.py000066400000000000000000000010741466372462000212210ustar00rootroot00000000000000from __future__ import annotations import typing from typing import IO, TextIO class FalseMeta(type): @classmethod def __bool__(cls) -> bool: # pragma: no cover return False @classmethod def __cmp__(cls, other: typing.Any) -> int: # pragma: no cover return -1 __nonzero__ = __bool__ class UnknownLength(metaclass=FalseMeta): pass class Undefined(metaclass=FalseMeta): pass assert IO is not None assert TextIO is not None __all__ = ( 'FalseMeta', 'UnknownLength', 'Undefined', 'IO', 'TextIO', ) python-progressbar-4.5.0/progressbar/env.py000066400000000000000000000133651466372462000211050ustar00rootroot00000000000000from __future__ import annotations import contextlib import enum import os import re import typing @typing.overload def env_flag(name: str, default: bool) -> bool: ... @typing.overload def env_flag(name: str, default: bool | None = None) -> bool | None: ... def env_flag(name: str, default: bool | None = None) -> bool | None: """ Accepts environt variables formatted as y/n, yes/no, 1/0, true/false, on/off, and returns it as a boolean. If the environment variable is not defined, or has an unknown value, returns `default` """ v = os.getenv(name) if v and v.lower() in ('y', 'yes', 't', 'true', 'on', '1'): return True if v and v.lower() in ('n', 'no', 'f', 'false', 'off', '0'): return False return default class ColorSupport(enum.IntEnum): """Color support for the terminal.""" NONE = 0 XTERM = 16 XTERM_256 = 256 XTERM_TRUECOLOR = 16777216 WINDOWS = 8 @classmethod def from_env(cls) -> ColorSupport: """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` or `JPY_PARENT_PID` 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 JUPYTER: # Jupyter notebook always supports true color. return cls.XTERM_TRUECOLOR elif os.name == 'nt': # We can't reliably detect true color support on Windows, so we # will assume it is supported if the console is configured to # support it. from .terminal.os_specific import windows if ( windows.get_console_mode() & windows.WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT ): return cls.XTERM_TRUECOLOR else: return cls.WINDOWS # pragma: no cover 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 def is_ansi_terminal( fd: typing.IO[typing.Any], is_terminal: bool | None = None, ) -> bool | None: # pragma: no cover if is_terminal is None: # Jupyter Notebooks support progress bars if JUPYTER: is_terminal = True # This works for newer versions of pycharm only. With older versions # there is no way to check. elif os.environ.get('PYCHARM_HOSTED') == '1' and not os.environ.get( 'PYTEST_CURRENT_TEST' ): is_terminal = True if is_terminal is None: # check if we are writing to a terminal or not. typically a file object # is going to return False if the instance has been overridden and # isatty has not been defined we have no way of knowing so we will not # use ansi. ansi terminals will typically define one of the 2 # environment variables. with contextlib.suppress(Exception): is_tty: bool = fd.isatty() # Try and match any of the huge amount of Linux/Unix ANSI consoles if is_tty and ANSI_TERM_RE.match(os.environ.get('TERM', '')): is_terminal = True # ANSICON is a Windows ANSI compatible console elif 'ANSICON' in os.environ: is_terminal = True elif os.name == 'nt': from .terminal.os_specific import windows return bool( windows.get_console_mode() & windows.WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT, ) else: is_terminal = None return is_terminal def is_terminal( fd: typing.IO[typing.Any], is_terminal: bool | None = None, ) -> bool | None: if is_terminal is None: # Full ansi support encompasses what we expect from a terminal is_terminal = is_ansi_terminal(fd) or None if is_terminal is None: # Allow a environment variable override is_terminal = env_flag('PROGRESSBAR_IS_TERMINAL', None) if is_terminal is None: # pragma: no cover # Bare except because a lot can go wrong on different systems. If we do # get a TTY we know this is a valid terminal try: is_terminal = fd.isatty() except Exception: is_terminal = False return is_terminal # Enable Windows full color mode if possible if os.name == 'nt': pass # os_specific.set_console_mode() JUPYTER = bool( os.environ.get('JUPYTER_COLUMNS') or os.environ.get('JUPYTER_LINES') or os.environ.get('JPY_PARENT_PID') ) COLOR_SUPPORT = ColorSupport.from_env() ANSI_TERMS = ( '([xe]|bv)term', '(sco)?ansi', 'cygwin', 'konsole', 'linux', 'rxvt', 'screen', 'tmux', 'vt(10[02]|220|320)', ) ANSI_TERM_RE: re.Pattern[str] = re.compile( f"^({'|'.join(ANSI_TERMS)})", re.IGNORECASE ) python-progressbar-4.5.0/progressbar/multi.py000066400000000000000000000276241466372462000214520ustar00rootroot00000000000000from __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(typing.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: 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: typing.TextIO = 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, bar: bar.ProgressBar): """Add a progressbar to the multibar.""" if bar.label != key or not key: # pragma: no branch bar.label = key bar.fd = stream.LastLineStream(self.fd) bar.paused = True # Essentially `bar.print = self.print`, but `mypy` doesn't # like that bar.print = self.print # type: ignore # Just in case someone is using a progressbar with a custom # constructor and forgot to call the super constructor if bar.index == -1: bar.index = next(bar._index_counter) super().__setitem__(key, bar) 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, key): """Get (and create if needed) a progressbar from the multibar.""" try: return super().__getitem__(key) except KeyError: progress = bar.ProgressBar(**self.progressbar_kwargs) self[key] = progress return progress def _label_bar(self, bar: bar.ProgressBar): if bar in self._labeled: # pragma: no branch return assert bar.widgets, 'Cannot prepend label to empty progressbar' if self.prepend_label: # pragma: no branch self._labeled.add(bar) bar.widgets.insert(0, self.label_format.format(label=bar.label)) if self.append_label and bar not in self._labeled: # pragma: no branch self._labeled.add(bar) 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 # sourcery skip: list-comprehension output: list[str] = [] for bar_ in self.get_sorted_bars(): if not bar_.started() and not self.show_initial: continue output.extend( iter(self._render_bar(bar_, expired=expired, now=now)), ) 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), ) # pragma: no cover # Add empty lines to the end of the output if progressbars have # been added for _ 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: # pragma: no branch self.print( '\r' + current.strip(), offset=i + 1, end='', clear=False, flush=False, ) self._previous_output = output if flush: # pragma: no branch self.flush() def _render_bar( self, bar_: bar.ProgressBar, now, expired, ) -> typing.Iterable[str]: def update(force=True, write=True): # pragma: no cover self._label_bar(bar_) bar_.update(force=force) if write: yield typing.cast(stream.LastLineStream, bar_.fd).line if bar_.finished(): yield from self._render_finished_bar(bar_, now, expired, update) elif bar_.started(): update() else: if self.initial_format is None: bar_.start() update() else: yield self.initial_format.format(label=bar_.label) def _render_finished_bar( self, bar_: bar.ProgressBar, now, expired, update, ) -> typing.Iterable[str]: 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 and expired is not None and expired >= self._finished_at[bar_] ): del self[bar_.label] return if not self.show_finished: return if bar_.finished(): # pragma: no branch if self.finished_format is None: update(force=False) else: # pragma: no cover yield self.finished_format.format(label=bar_.label) 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(): # pragma: no branch 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 the progressbars # have finished. If they have, we can exit the loop for bar_ in self.values(): # pragma: no cover 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() def join(self, timeout=None): if self._thread is not None: self._thread_closed.set() self._thread.join(timeout=timeout) self._thread = None def stop(self, timeout: float | None = None): self._thread_finished.set() self.join(timeout=timeout) def get_sorted_bars(self): return sorted( self.values(), key=self.sort_keyfunc, reverse=self.sort_reverse, ) def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): self.join() python-progressbar-4.5.0/progressbar/py.typed000066400000000000000000000000001466372462000214200ustar00rootroot00000000000000python-progressbar-4.5.0/progressbar/shortcuts.py000066400000000000000000000006061466372462000223450ustar00rootroot00000000000000from . import bar def progressbar( iterator, min_value: int = 0, max_value=None, widgets=None, prefix=None, suffix=None, **kwargs, ): progressbar = bar.ProgressBar( min_value=min_value, max_value=max_value, widgets=widgets, prefix=prefix, suffix=suffix, **kwargs, ) yield from progressbar(iterator) python-progressbar-4.5.0/progressbar/terminal/000077500000000000000000000000001466372462000215465ustar00rootroot00000000000000python-progressbar-4.5.0/progressbar/terminal/__init__.py000066400000000000000000000001501466372462000236530ustar00rootroot00000000000000from __future__ import annotations from .base import * # noqa F403 from .stream import * # noqa F403 python-progressbar-4.5.0/progressbar/terminal/base.py000066400000000000000000000416121466372462000230360ustar00rootroot00000000000000from __future__ import annotations import abc import collections import colorsys import enum import threading from collections import defaultdict # Ruff is being stupid and doesn't understand `ClassVar` if it comes from the # `types` module from typing import ClassVar from python_utils import converters, types from .. import ( base as pbase, env, ) from .os_specific import getch ESC = '\x1b' class CSI: _code: str _template = ESC + '[{args}{code}' def __init__(self, code: str, *default_args) -> None: 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 = CSI('H', 1, 1) #: Cursor Up Ps Times (default = 1) (CUU) UP: CSI = CSI('A', 1) #: Cursor Down Ps Times (default = 1) (CUD) DOWN: CSI = CSI('B', 1) #: Cursor Forward Ps Times (default = 1) (CUF) RIGHT: CSI = CSI('C', 1) #: Cursor Backward Ps Times (default = 1) (CUB) LEFT: CSI = CSI('D', 1) #: Cursor Next Line Ps Times (default = 1) (CNL) #: Same as Cursor Down Ps Times NEXT_LINE: CSI = CSI('E', 1) #: Cursor Preceding Line Ps Times (default = 1) (CPL) #: Same as Cursor Up Ps Times PREVIOUS_LINE: CSI = CSI('F', 1) #: Cursor Character Absolute [column] (default = [row,1]) (CHA) COLUMN: CSI = CSI('G', 1) #: Erase in Display (ED) CLEAR_SCREEN: CSI = CSI('J', 0) #: Erase till end of screen CLEAR_SCREEN_TILL_END: CSINoArg = CSINoArg('0J') #: Erase till start of screen CLEAR_SCREEN_TILL_START: CSINoArg = CSINoArg('1J') #: Erase whole screen CLEAR_SCREEN_ALL: CSINoArg = CSINoArg('2J') #: Erase whole screen and history CLEAR_SCREEN_ALL_AND_HISTORY: CSINoArg = CSINoArg('3J') #: Erase in Line (EL) CLEAR_LINE_ALL: CSI = CSI('K') #: Erase in Line from Cursor to End of Line (default) CLEAR_LINE_RIGHT: CSINoArg = CSINoArg('0K') #: Erase in Line from Cursor to Beginning of Line CLEAR_LINE_LEFT: CSINoArg = CSINoArg('1K') #: Erase Line containing Cursor CLEAR_LINE: CSINoArg = CSINoArg('2K') #: Scroll up Ps lines (default = 1) (SU) #: Scroll down Ps lines (default = 1) (SD) SCROLL_UP: CSI = CSI('S') SCROLL_DOWN: CSI = CSI('T') #: Save Cursor Position (SCP) SAVE_CURSOR: CSINoArg = CSINoArg('s') #: Restore Cursor Position (RCP) RESTORE_CURSOR: CSINoArg = CSINoArg('u') #: Cursor Visibility (DECTCEM) HIDE_CURSOR: CSINoArg = CSINoArg('?25l') SHOW_CURSOR: CSINoArg = 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) # Report Cursor Position (CPR), response = [row;column] as row;columnR class _CPR(str): # pragma: no cover _response_lock = threading.Lock() def __call__(self, stream) -> tuple[int, int]: res: str = '' 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_list = res[2:-1].split(';') res_list = tuple( int(item) if item.isdigit() else item for item in res_list ) if len(res_list) == 1: return types.cast(types.Tuple[int, int], res_list[0]) return types.cast(types.Tuple[int, int], tuple(res_list)) def row(self, stream) -> int: row, _ = self(stream) return row def column(self, stream) -> int: _, column = self(stream) return column class WindowsColors(enum.Enum): BLACK = 0, 0, 0 BLUE = 0, 0, 128 GREEN = 0, 128, 0 CYAN = 0, 128, 128 RED = 128, 0, 0 MAGENTA = 128, 0, 128 YELLOW = 128, 128, 0 GREY = 192, 192, 192 INTENSE_BLACK = 128, 128, 128 INTENSE_BLUE = 0, 0, 255 INTENSE_GREEN = 0, 255, 0 INTENSE_CYAN = 0, 255, 255 INTENSE_RED = 255, 0, 0 INTENSE_MAGENTA = 255, 0, 255 INTENSE_YELLOW = 255, 255, 0 INTENSE_WHITE = 255, 255, 255 @staticmethod def from_rgb(rgb: types.Tuple[int, int, int]) -> WindowsColors: """ Find the closest WindowsColors to the given RGB color. >>> WindowsColors.from_rgb((0, 0, 0)) >>> WindowsColors.from_rgb((255, 255, 255)) >>> WindowsColors.from_rgb((0, 255, 0)) >>> WindowsColors.from_rgb((45, 45, 45)) >>> WindowsColors.from_rgb((128, 0, 128)) """ def color_distance(rgb1, rgb2): return sum((c1 - c2) ** 2 for c1, c2 in zip(rgb1, rgb2)) return min( WindowsColors, key=lambda color: color_distance(color.value, rgb), ) class WindowsColor: """ Windows compatible color class for when ANSI is not supported. Currently a no-op because it is not possible to buffer these colors. >>> WindowsColor(WindowsColors.RED)('test') 'test' """ __slots__ = ('color',) def __init__(self, color: Color) -> None: self.color = color def __call__(self, text): return text ## In the future we might want to use this, but it requires direct ## printing to stdout and all of our surrounding functions expect ## buffered output so it's not feasible right now. Additionally, ## recent Windows versions all support ANSI codes without issue so ## there is little need. # from progressbar.terminal.os_specific import windows # windows.print_color(text, WindowsColors.from_rgb(self.color.rgb)) class RGB(collections.namedtuple('RGB', ['red', 'green', 'blue'])): __slots__ = () def __str__(self): return self.rgb @property def rgb(self) -> str: return f'rgb({self.red}, {self.green}, {self.blue})' @property def hex(self) -> str: return f'#{self.red:02x}{self.green:02x}{self.blue:02x}' @property def to_ansi_16(self) -> int: # 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) -> int: 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 @property def to_windows(self): """ Convert an RGB color (0-255 per channel) to the closest color in the Windows 16 color scheme. """ return WindowsColors.from_rgb((self.red, self.green, self.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 HSL(collections.namedtuple('HSL', ['hue', 'saturation', 'lightness'])): """ Hue, Saturation, Lightness color. Hue is a value between 0 and 360, saturation and lightness are between 0(%) and 100(%). """ __slots__ = () @classmethod def from_rgb(cls, rgb: RGB) -> HSL: """ Convert a 0-255 RGB color to a 0-255 HLS color. """ hls = colorsys.rgb_to_hls( rgb.red / 255, rgb.green / 255, rgb.blue / 255, ) return cls( round(hls[0] * 360), round(hls[2] * 100), round(hls[1] * 100), ) def interpolate(self, end: HSL, step: float) -> HSL: return HSL( 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( 'Color', [ 'rgb', 'hls', 'name', 'xterm', ], ), ColorBase, ): """ Color base class. This class contains the colors in RGB (Red, Green, Blue), HSL (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 explicitly if you wish. """ __slots__ = () def __call__(self, value: str) -> str: return self.fg(value) @property def fg(self) -> SGRColor | WindowsColor: if env.COLOR_SUPPORT is env.ColorSupport.WINDOWS: return WindowsColor(self) else: return SGRColor(self, 38, 39) @property def bg(self) -> DummyColor | SGRColor: if env.COLOR_SUPPORT is env.ColorSupport.WINDOWS: return DummyColor() else: return SGRColor(self, 48, 49) @property def underline(self) -> DummyColor | SGRColor: if env.COLOR_SUPPORT is env.ColorSupport.WINDOWS: return DummyColor() else: return SGRColor(self, 58, 59) @property def ansi(self) -> types.Optional[str]: if ( env.COLOR_SUPPORT is env.ColorSupport.XTERM_TRUECOLOR ): # pragma: no branch return f'2;{self.rgb.red};{self.rgb.green};{self.rgb.blue}' if self.xterm: # pragma: no branch color = self.xterm elif ( env.COLOR_SUPPORT is env.ColorSupport.XTERM_256 ): # pragma: no branch color = self.rgb.to_ansi_256 elif env.COLOR_SUPPORT is env.ColorSupport.XTERM: # pragma: no branch color = self.rgb.to_ansi_16 else: # pragma: no branch return None 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 def __repr__(self) -> str: return f'{self.__class__.__name__}({self.name!r})' def __hash__(self) -> int: return hash(self.rgb) class Colors: by_name: ClassVar[defaultdict[str, types.List[Color]]] = ( collections.defaultdict(list) ) by_lowername: ClassVar[defaultdict[str, types.List[Color]]] = ( collections.defaultdict(list) ) by_hex: ClassVar[defaultdict[str, types.List[Color]]] = ( collections.defaultdict(list) ) by_rgb: ClassVar[defaultdict[RGB, types.List[Color]]] = ( collections.defaultdict(list) ) by_hls: ClassVar[defaultdict[HSL, types.List[Color]]] = ( collections.defaultdict(list) ) by_xterm: ClassVar[dict[int, Color]] = dict() @classmethod def register( cls, rgb: RGB, hls: types.Optional[HSL] = 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 = HSL.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 @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) -> None: assert colors self.colors = colors self.interpolate = interpolate def __call__(self, value: float) -> Color: return self.get_color(value) def get_color(self, value: float) -> Color: "Map a value from 0 to 1 to a color." if ( value == pbase.Undefined or value == pbase.UnknownLength or 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: if max_color_idx > 1: index = round( converters.remap(value, 0, 1, 0, max_color_idx - 1), ) else: index = 0 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 = types.Union[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, percentage: float | None = None, *, fg: OptionalColor = None, bg: OptionalColor = None, fg_none: Color | None = None, bg_none: Color | None = None, **kwargs: types.Any, ) -> str: """Apply colors/gradients to a string depending on the given percentage. When percentage is `None`, the `fg_none` and `bg_none` colors will be used. Otherwise, the `fg` and `bg` colors will be used. If the colors are gradients, the color will be interpolated depending on the percentage. """ 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) elif fg is not None or bg is not None: fg = get_color(percentage * 0.01, fg) bg = get_color(percentage * 0.01, bg) if fg is not None: # pragma: no branch text = fg.fg(text) if bg is not None: # pragma: no branch text = bg.bg(text) return text class DummyColor: def __call__(self, text): return text def __repr__(self) -> str: return 'DummyColor()' class SGR(CSI): _start_code: int _end_code: int _code = 'm' __slots__ = '_start_code', '_end_code' def __init__(self, start_code: int, end_code: int) -> None: self._start_code = start_code self._end_code = end_code @property def _start_template(self): return super().__call__(self._start_code) @property def _end_template(self): return super().__call__(self._end_code) def __call__(self, text, *args): return self._start_template + text + self._end_template class SGRColor(SGR): __slots__ = '_color', '_start_code', '_end_code' def __init__(self, color: Color, start_code: int, end_code: int) -> None: self._color = color super().__init__(start_code, end_code) @property def _start_template(self): return CSI.__call__(self, self._start_code, self._color.ansi) encircled: SGR = SGR(52, 54) framed: SGR = SGR(51, 54) overline: SGR = SGR(53, 55) bold: SGR = SGR(1, 22) gothic: SGR = SGR(20, 10) italic: SGR = SGR(3, 23) strike_through: SGR = SGR(9, 29) fast_blink: SGR = SGR(6, 25) slow_blink: SGR = SGR(5, 25) underline: SGR = SGR(4, 24) double_underline: SGR = SGR(21, 24) faint: SGR = SGR(2, 22) inverse: SGR = SGR(7, 27) python-progressbar-4.5.0/progressbar/terminal/colors.py000066400000000000000000000610241466372462000234240ustar00rootroot00000000000000from __future__ import annotations # Based on: https://www.ditig.com/256-colors-cheat-sheet import os from progressbar.terminal.base import HSL, RGB, ColorGradient, Colors black = Colors.register(RGB(0, 0, 0), HSL(0, 0, 0), 'Black', 0) maroon = Colors.register(RGB(128, 0, 0), HSL(0, 100, 25), 'Maroon', 1) green = Colors.register(RGB(0, 128, 0), HSL(120, 100, 25), 'Green', 2) olive = Colors.register(RGB(128, 128, 0), HSL(60, 100, 25), 'Olive', 3) navy = Colors.register(RGB(0, 0, 128), HSL(240, 100, 25), 'Navy', 4) purple = Colors.register(RGB(128, 0, 128), HSL(300, 100, 25), 'Purple', 5) teal = Colors.register(RGB(0, 128, 128), HSL(180, 100, 25), 'Teal', 6) silver = Colors.register(RGB(192, 192, 192), HSL(0, 0, 75), 'Silver', 7) grey = Colors.register(RGB(128, 128, 128), HSL(0, 0, 50), 'Grey', 8) red = Colors.register(RGB(255, 0, 0), HSL(0, 100, 50), 'Red', 9) lime = Colors.register(RGB(0, 255, 0), HSL(120, 100, 50), 'Lime', 10) yellow = Colors.register(RGB(255, 255, 0), HSL(60, 100, 50), 'Yellow', 11) blue = Colors.register(RGB(0, 0, 255), HSL(240, 100, 50), 'Blue', 12) fuchsia = Colors.register(RGB(255, 0, 255), HSL(300, 100, 50), 'Fuchsia', 13) aqua = Colors.register(RGB(0, 255, 255), HSL(180, 100, 50), 'Aqua', 14) white = Colors.register(RGB(255, 255, 255), HSL(0, 0, 100), 'White', 15) grey0 = Colors.register(RGB(0, 0, 0), HSL(0, 0, 0), 'Grey0', 16) navy_blue = Colors.register(RGB(0, 0, 95), HSL(240, 100, 18), 'NavyBlue', 17) dark_blue = Colors.register(RGB(0, 0, 135), HSL(240, 100, 26), 'DarkBlue', 18) blue3 = Colors.register(RGB(0, 0, 175), HSL(240, 100, 34), 'Blue3', 19) blue3 = Colors.register(RGB(0, 0, 215), HSL(240, 100, 42), 'Blue3', 20) blue1 = Colors.register(RGB(0, 0, 255), HSL(240, 100, 50), 'Blue1', 21) dark_green = Colors.register(RGB(0, 95, 0), HSL(120, 100, 18), 'DarkGreen', 22) deep_sky_blue4 = Colors.register( RGB(0, 95, 95), HSL(180, 100, 18), 'DeepSkyBlue4', 23, ) deep_sky_blue4 = Colors.register( RGB(0, 95, 135), HSL(97, 100, 26), 'DeepSkyBlue4', 24, ) deep_sky_blue4 = Colors.register( RGB(0, 95, 175), HSL(7, 100, 34), 'DeepSkyBlue4', 25, ) dodger_blue3 = Colors.register( RGB(0, 95, 215), HSL(13, 100, 42), 'DodgerBlue3', 26, ) dodger_blue2 = Colors.register( RGB(0, 95, 255), HSL(17, 100, 50), 'DodgerBlue2', 27, ) green4 = Colors.register(RGB(0, 135, 0), HSL(120, 100, 26), 'Green4', 28) spring_green4 = Colors.register( RGB(0, 135, 95), HSL(62, 100, 26), 'SpringGreen4', 29, ) turquoise4 = Colors.register( RGB(0, 135, 135), HSL(180, 100, 26), 'Turquoise4', 30, ) deep_sky_blue3 = Colors.register( RGB(0, 135, 175), HSL(93, 100, 34), 'DeepSkyBlue3', 31, ) deep_sky_blue3 = Colors.register( RGB(0, 135, 215), HSL(2, 100, 42), 'DeepSkyBlue3', 32, ) dodger_blue1 = Colors.register( RGB(0, 135, 255), HSL(8, 100, 50), 'DodgerBlue1', 33, ) green3 = Colors.register(RGB(0, 175, 0), HSL(120, 100, 34), 'Green3', 34) spring_green3 = Colors.register( RGB(0, 175, 95), HSL(52, 100, 34), 'SpringGreen3', 35, ) dark_cyan = Colors.register(RGB(0, 175, 135), HSL(66, 100, 34), 'DarkCyan', 36) light_sea_green = Colors.register( RGB(0, 175, 175), HSL(180, 100, 34), 'LightSeaGreen', 37, ) deep_sky_blue2 = Colors.register( RGB(0, 175, 215), HSL(91, 100, 42), 'DeepSkyBlue2', 38, ) deep_sky_blue1 = Colors.register( RGB(0, 175, 255), HSL(98, 100, 50), 'DeepSkyBlue1', 39, ) green3 = Colors.register(RGB(0, 215, 0), HSL(120, 100, 42), 'Green3', 40) spring_green3 = Colors.register( RGB(0, 215, 95), HSL(46, 100, 42), 'SpringGreen3', 41, ) spring_green2 = Colors.register( RGB(0, 215, 135), HSL(57, 100, 42), 'SpringGreen2', 42, ) cyan3 = Colors.register(RGB(0, 215, 175), HSL(68, 100, 42), 'Cyan3', 43) dark_turquoise = Colors.register( RGB(0, 215, 215), HSL(180, 100, 42), 'DarkTurquoise', 44, ) turquoise2 = Colors.register( RGB(0, 215, 255), HSL(89, 100, 50), 'Turquoise2', 45, ) green1 = Colors.register(RGB(0, 255, 0), HSL(120, 100, 50), 'Green1', 46) spring_green2 = Colors.register( RGB(0, 255, 95), HSL(42, 100, 50), 'SpringGreen2', 47, ) spring_green1 = Colors.register( RGB(0, 255, 135), HSL(51, 100, 50), 'SpringGreen1', 48, ) medium_spring_green = Colors.register( RGB(0, 255, 175), HSL(61, 100, 50), 'MediumSpringGreen', 49, ) cyan2 = Colors.register(RGB(0, 255, 215), HSL(70, 100, 50), 'Cyan2', 50) cyan1 = Colors.register(RGB(0, 255, 255), HSL(180, 100, 50), 'Cyan1', 51) dark_red = Colors.register(RGB(95, 0, 0), HSL(0, 100, 18), 'DarkRed', 52) deep_pink4 = Colors.register( RGB(95, 0, 95), HSL(300, 100, 18), 'DeepPink4', 53, ) purple4 = Colors.register(RGB(95, 0, 135), HSL(82, 100, 26), 'Purple4', 54) purple4 = Colors.register(RGB(95, 0, 175), HSL(72, 100, 34), 'Purple4', 55) purple3 = Colors.register(RGB(95, 0, 215), HSL(66, 100, 42), 'Purple3', 56) blue_violet = Colors.register( RGB(95, 0, 255), HSL(62, 100, 50), 'BlueViolet', 57, ) orange4 = Colors.register(RGB(95, 95, 0), HSL(60, 100, 18), 'Orange4', 58) grey37 = Colors.register(RGB(95, 95, 95), HSL(0, 0, 37), 'Grey37', 59) medium_purple4 = Colors.register( RGB(95, 95, 135), HSL(240, 17, 45), 'MediumPurple4', 60, ) slate_blue3 = Colors.register( RGB(95, 95, 175), HSL(240, 33, 52), 'SlateBlue3', 61, ) slate_blue3 = Colors.register( RGB(95, 95, 215), HSL(240, 60, 60), 'SlateBlue3', 62, ) royal_blue1 = Colors.register( RGB(95, 95, 255), HSL(240, 100, 68), 'RoyalBlue1', 63, ) chartreuse4 = Colors.register( RGB(95, 135, 0), HSL(7, 100, 26), 'Chartreuse4', 64, ) dark_sea_green4 = Colors.register( RGB(95, 135, 95), HSL(120, 17, 45), 'DarkSeaGreen4', 65, ) pale_turquoise4 = Colors.register( RGB(95, 135, 135), HSL(180, 17, 45), 'PaleTurquoise4', 66, ) steel_blue = Colors.register( RGB(95, 135, 175), HSL(210, 33, 52), 'SteelBlue', 67, ) steel_blue3 = Colors.register( RGB(95, 135, 215), HSL(220, 60, 60), 'SteelBlue3', 68, ) cornflower_blue = Colors.register( RGB(95, 135, 255), HSL(225, 100, 68), 'CornflowerBlue', 69, ) chartreuse3 = Colors.register( RGB(95, 175, 0), HSL(7, 100, 34), 'Chartreuse3', 70, ) dark_sea_green4 = Colors.register( RGB(95, 175, 95), HSL(120, 33, 52), 'DarkSeaGreen4', 71, ) cadet_blue = Colors.register( RGB(95, 175, 135), HSL(150, 33, 52), 'CadetBlue', 72, ) cadet_blue = Colors.register( RGB(95, 175, 175), HSL(180, 33, 52), 'CadetBlue', 73, ) sky_blue3 = Colors.register( RGB(95, 175, 215), HSL(200, 60, 60), 'SkyBlue3', 74, ) steel_blue1 = Colors.register( RGB(95, 175, 255), HSL(210, 100, 68), 'SteelBlue1', 75, ) chartreuse3 = Colors.register( RGB(95, 215, 0), HSL(3, 100, 42), 'Chartreuse3', 76, ) pale_green3 = Colors.register( RGB(95, 215, 95), HSL(120, 60, 60), 'PaleGreen3', 77, ) sea_green3 = Colors.register( RGB(95, 215, 135), HSL(140, 60, 60), 'SeaGreen3', 78, ) aquamarine3 = Colors.register( RGB(95, 215, 175), HSL(160, 60, 60), 'Aquamarine3', 79, ) medium_turquoise = Colors.register( RGB(95, 215, 215), HSL(180, 60, 60), 'MediumTurquoise', 80, ) steel_blue1 = Colors.register( RGB(95, 215, 255), HSL(195, 100, 68), 'SteelBlue1', 81, ) chartreuse2 = Colors.register( RGB(95, 255, 0), HSL(7, 100, 50), 'Chartreuse2', 82, ) sea_green2 = Colors.register( RGB(95, 255, 95), HSL(120, 100, 68), 'SeaGreen2', 83, ) sea_green1 = Colors.register( RGB(95, 255, 135), HSL(135, 100, 68), 'SeaGreen1', 84, ) sea_green1 = Colors.register( RGB(95, 255, 175), HSL(150, 100, 68), 'SeaGreen1', 85, ) aquamarine1 = Colors.register( RGB(95, 255, 215), HSL(165, 100, 68), 'Aquamarine1', 86, ) dark_slate_gray2 = Colors.register( RGB(95, 255, 255), HSL(180, 100, 68), 'DarkSlateGray2', 87, ) dark_red = Colors.register(RGB(135, 0, 0), HSL(0, 100, 26), 'DarkRed', 88) deep_pink4 = Colors.register( RGB(135, 0, 95), HSL(17, 100, 26), 'DeepPink4', 89, ) dark_magenta = Colors.register( RGB(135, 0, 135), HSL(300, 100, 26), 'DarkMagenta', 90, ) dark_magenta = Colors.register( RGB(135, 0, 175), HSL(86, 100, 34), 'DarkMagenta', 91, ) dark_violet = Colors.register( RGB(135, 0, 215), HSL(77, 100, 42), 'DarkViolet', 92, ) purple = Colors.register(RGB(135, 0, 255), HSL(71, 100, 50), 'Purple', 93) orange4 = Colors.register(RGB(135, 95, 0), HSL(2, 100, 26), 'Orange4', 94) light_pink4 = Colors.register( RGB(135, 95, 95), HSL(0, 17, 45), 'LightPink4', 95, ) plum4 = Colors.register(RGB(135, 95, 135), HSL(300, 17, 45), 'Plum4', 96) medium_purple3 = Colors.register( RGB(135, 95, 175), HSL(270, 33, 52), 'MediumPurple3', 97, ) medium_purple3 = Colors.register( RGB(135, 95, 215), HSL(260, 60, 60), 'MediumPurple3', 98, ) slate_blue1 = Colors.register( RGB(135, 95, 255), HSL(255, 100, 68), 'SlateBlue1', 99, ) yellow4 = Colors.register(RGB(135, 135, 0), HSL(60, 100, 26), 'Yellow4', 100) wheat4 = Colors.register(RGB(135, 135, 95), HSL(60, 17, 45), 'Wheat4', 101) grey53 = Colors.register(RGB(135, 135, 135), HSL(0, 0, 52), 'Grey53', 102) light_slate_grey = Colors.register( RGB(135, 135, 175), HSL(240, 20, 60), 'LightSlateGrey', 103, ) medium_purple = Colors.register( RGB(135, 135, 215), HSL(240, 50, 68), 'MediumPurple', 104, ) light_slate_blue = Colors.register( RGB(135, 135, 255), HSL(240, 100, 76), 'LightSlateBlue', 105, ) yellow4 = Colors.register(RGB(135, 175, 0), HSL(3, 100, 34), 'Yellow4', 106) dark_olive_green3 = Colors.register( RGB(135, 175, 95), HSL(90, 33, 52), 'DarkOliveGreen3', 107, ) dark_sea_green = Colors.register( RGB(135, 175, 135), HSL(120, 20, 60), 'DarkSeaGreen', 108, ) light_sky_blue3 = Colors.register( RGB(135, 175, 175), HSL(180, 20, 60), 'LightSkyBlue3', 109, ) light_sky_blue3 = Colors.register( RGB(135, 175, 215), HSL(210, 50, 68), 'LightSkyBlue3', 110, ) sky_blue2 = Colors.register( RGB(135, 175, 255), HSL(220, 100, 76), 'SkyBlue2', 111, ) chartreuse2 = Colors.register( RGB(135, 215, 0), HSL(2, 100, 42), 'Chartreuse2', 112, ) dark_olive_green3 = Colors.register( RGB(135, 215, 95), HSL(100, 60, 60), 'DarkOliveGreen3', 113, ) pale_green3 = Colors.register( RGB(135, 215, 135), HSL(120, 50, 68), 'PaleGreen3', 114, ) dark_sea_green3 = Colors.register( RGB(135, 215, 175), HSL(150, 50, 68), 'DarkSeaGreen3', 115, ) dark_slate_gray3 = Colors.register( RGB(135, 215, 215), HSL(180, 50, 68), 'DarkSlateGray3', 116, ) sky_blue1 = Colors.register( RGB(135, 215, 255), HSL(200, 100, 76), 'SkyBlue1', 117, ) chartreuse1 = Colors.register( RGB(135, 255, 0), HSL(8, 100, 50), 'Chartreuse1', 118, ) light_green = Colors.register( RGB(135, 255, 95), HSL(105, 100, 68), 'LightGreen', 119, ) light_green = Colors.register( RGB(135, 255, 135), HSL(120, 100, 76), 'LightGreen', 120, ) pale_green1 = Colors.register( RGB(135, 255, 175), HSL(140, 100, 76), 'PaleGreen1', 121, ) aquamarine1 = Colors.register( RGB(135, 255, 215), HSL(160, 100, 76), 'Aquamarine1', 122, ) dark_slate_gray1 = Colors.register( RGB(135, 255, 255), HSL(180, 100, 76), 'DarkSlateGray1', 123, ) red3 = Colors.register(RGB(175, 0, 0), HSL(0, 100, 34), 'Red3', 124) deep_pink4 = Colors.register( RGB(175, 0, 95), HSL(27, 100, 34), 'DeepPink4', 125, ) medium_violet_red = Colors.register( RGB(175, 0, 135), HSL(13, 100, 34), 'MediumVioletRed', 126, ) magenta3 = Colors.register( RGB(175, 0, 175), HSL(300, 100, 34), 'Magenta3', 127, ) dark_violet = Colors.register( RGB(175, 0, 215), HSL(88, 100, 42), 'DarkViolet', 128, ) purple = Colors.register(RGB(175, 0, 255), HSL(81, 100, 50), 'Purple', 129) dark_orange3 = Colors.register( RGB(175, 95, 0), HSL(2, 100, 34), 'DarkOrange3', 130, ) indian_red = Colors.register( RGB(175, 95, 95), HSL(0, 33, 52), 'IndianRed', 131, ) hot_pink3 = Colors.register( RGB(175, 95, 135), HSL(330, 33, 52), 'HotPink3', 132, ) medium_orchid3 = Colors.register( RGB(175, 95, 175), HSL(300, 33, 52), 'MediumOrchid3', 133, ) medium_orchid = Colors.register( RGB(175, 95, 215), HSL(280, 60, 60), 'MediumOrchid', 134, ) medium_purple2 = Colors.register( RGB(175, 95, 255), HSL(270, 100, 68), 'MediumPurple2', 135, ) dark_goldenrod = Colors.register( RGB(175, 135, 0), HSL(6, 100, 34), 'DarkGoldenrod', 136, ) light_salmon3 = Colors.register( RGB(175, 135, 95), HSL(30, 33, 52), 'LightSalmon3', 137, ) rosy_brown = Colors.register( RGB(175, 135, 135), HSL(0, 20, 60), 'RosyBrown', 138, ) grey63 = Colors.register(RGB(175, 135, 175), HSL(300, 20, 60), 'Grey63', 139) medium_purple2 = Colors.register( RGB(175, 135, 215), HSL(270, 50, 68), 'MediumPurple2', 140, ) medium_purple1 = Colors.register( RGB(175, 135, 255), HSL(260, 100, 76), 'MediumPurple1', 141, ) gold3 = Colors.register(RGB(175, 175, 0), HSL(60, 100, 34), 'Gold3', 142) dark_khaki = Colors.register( RGB(175, 175, 95), HSL(60, 33, 52), 'DarkKhaki', 143, ) navajo_white3 = Colors.register( RGB(175, 175, 135), HSL(60, 20, 60), 'NavajoWhite3', 144, ) grey69 = Colors.register(RGB(175, 175, 175), HSL(0, 0, 68), 'Grey69', 145) light_steel_blue3 = Colors.register( RGB(175, 175, 215), HSL(240, 33, 76), 'LightSteelBlue3', 146, ) light_steel_blue = Colors.register( RGB(175, 175, 255), HSL(240, 100, 84), 'LightSteelBlue', 147, ) yellow3 = Colors.register(RGB(175, 215, 0), HSL(1, 100, 42), 'Yellow3', 148) dark_olive_green3 = Colors.register( RGB(175, 215, 95), HSL(80, 60, 60), 'DarkOliveGreen3', 149, ) dark_sea_green3 = Colors.register( RGB(175, 215, 135), HSL(90, 50, 68), 'DarkSeaGreen3', 150, ) dark_sea_green2 = Colors.register( RGB(175, 215, 175), HSL(120, 33, 76), 'DarkSeaGreen2', 151, ) light_cyan3 = Colors.register( RGB(175, 215, 215), HSL(180, 33, 76), 'LightCyan3', 152, ) light_sky_blue1 = Colors.register( RGB(175, 215, 255), HSL(210, 100, 84), 'LightSkyBlue1', 153, ) green_yellow = Colors.register( RGB(175, 255, 0), HSL(8, 100, 50), 'GreenYellow', 154, ) dark_olive_green2 = Colors.register( RGB(175, 255, 95), HSL(90, 100, 68), 'DarkOliveGreen2', 155, ) pale_green1 = Colors.register( RGB(175, 255, 135), HSL(100, 100, 76), 'PaleGreen1', 156, ) dark_sea_green2 = Colors.register( RGB(175, 255, 175), HSL(120, 100, 84), 'DarkSeaGreen2', 157, ) dark_sea_green1 = Colors.register( RGB(175, 255, 215), HSL(150, 100, 84), 'DarkSeaGreen1', 158, ) pale_turquoise1 = Colors.register( RGB(175, 255, 255), HSL(180, 100, 84), 'PaleTurquoise1', 159, ) red3 = Colors.register(RGB(215, 0, 0), HSL(0, 100, 42), 'Red3', 160) deep_pink3 = Colors.register( RGB(215, 0, 95), HSL(33, 100, 42), 'DeepPink3', 161, ) deep_pink3 = Colors.register( RGB(215, 0, 135), HSL(22, 100, 42), 'DeepPink3', 162, ) magenta3 = Colors.register(RGB(215, 0, 175), HSL(11, 100, 42), 'Magenta3', 163) magenta3 = Colors.register( RGB(215, 0, 215), HSL(300, 100, 42), 'Magenta3', 164, ) magenta2 = Colors.register(RGB(215, 0, 255), HSL(90, 100, 50), 'Magenta2', 165) dark_orange3 = Colors.register( RGB(215, 95, 0), HSL(6, 100, 42), 'DarkOrange3', 166, ) indian_red = Colors.register( RGB(215, 95, 95), HSL(0, 60, 60), 'IndianRed', 167, ) hot_pink3 = Colors.register( RGB(215, 95, 135), HSL(340, 60, 60), 'HotPink3', 168, ) hot_pink2 = Colors.register( RGB(215, 95, 175), HSL(320, 60, 60), 'HotPink2', 169, ) orchid = Colors.register(RGB(215, 95, 215), HSL(300, 60, 60), 'Orchid', 170) medium_orchid1 = Colors.register( RGB(215, 95, 255), HSL(285, 100, 68), 'MediumOrchid1', 171, ) orange3 = Colors.register(RGB(215, 135, 0), HSL(7, 100, 42), 'Orange3', 172) light_salmon3 = Colors.register( RGB(215, 135, 95), HSL(20, 60, 60), 'LightSalmon3', 173, ) light_pink3 = Colors.register( RGB(215, 135, 135), HSL(0, 50, 68), 'LightPink3', 174, ) pink3 = Colors.register(RGB(215, 135, 175), HSL(330, 50, 68), 'Pink3', 175) plum3 = Colors.register(RGB(215, 135, 215), HSL(300, 50, 68), 'Plum3', 176) violet = Colors.register(RGB(215, 135, 255), HSL(280, 100, 76), 'Violet', 177) gold3 = Colors.register(RGB(215, 175, 0), HSL(8, 100, 42), 'Gold3', 178) light_goldenrod3 = Colors.register( RGB(215, 175, 95), HSL(40, 60, 60), 'LightGoldenrod3', 179, ) tan = Colors.register(RGB(215, 175, 135), HSL(30, 50, 68), 'Tan', 180) misty_rose3 = Colors.register( RGB(215, 175, 175), HSL(0, 33, 76), 'MistyRose3', 181, ) thistle3 = Colors.register( RGB(215, 175, 215), HSL(300, 33, 76), 'Thistle3', 182, ) plum2 = Colors.register(RGB(215, 175, 255), HSL(270, 100, 84), 'Plum2', 183) yellow3 = Colors.register(RGB(215, 215, 0), HSL(60, 100, 42), 'Yellow3', 184) khaki3 = Colors.register(RGB(215, 215, 95), HSL(60, 60, 60), 'Khaki3', 185) light_goldenrod2 = Colors.register( RGB(215, 215, 135), HSL(60, 50, 68), 'LightGoldenrod2', 186, ) light_yellow3 = Colors.register( RGB(215, 215, 175), HSL(60, 33, 76), 'LightYellow3', 187, ) grey84 = Colors.register(RGB(215, 215, 215), HSL(0, 0, 84), 'Grey84', 188) light_steel_blue1 = Colors.register( RGB(215, 215, 255), HSL(240, 100, 92), 'LightSteelBlue1', 189, ) yellow2 = Colors.register(RGB(215, 255, 0), HSL(9, 100, 50), 'Yellow2', 190) dark_olive_green1 = Colors.register( RGB(215, 255, 95), HSL(75, 100, 68), 'DarkOliveGreen1', 191, ) dark_olive_green1 = Colors.register( RGB(215, 255, 135), HSL(80, 100, 76), 'DarkOliveGreen1', 192, ) dark_sea_green1 = Colors.register( RGB(215, 255, 175), HSL(90, 100, 84), 'DarkSeaGreen1', 193, ) honeydew2 = Colors.register( RGB(215, 255, 215), HSL(120, 100, 92), 'Honeydew2', 194, ) light_cyan1 = Colors.register( RGB(215, 255, 255), HSL(180, 100, 92), 'LightCyan1', 195, ) red1 = Colors.register(RGB(255, 0, 0), HSL(0, 100, 50), 'Red1', 196) deep_pink2 = Colors.register( RGB(255, 0, 95), HSL(37, 100, 50), 'DeepPink2', 197, ) deep_pink1 = Colors.register( RGB(255, 0, 135), HSL(28, 100, 50), 'DeepPink1', 198, ) deep_pink1 = Colors.register( RGB(255, 0, 175), HSL(18, 100, 50), 'DeepPink1', 199, ) magenta2 = Colors.register(RGB(255, 0, 215), HSL(9, 100, 50), 'Magenta2', 200) magenta1 = Colors.register( RGB(255, 0, 255), HSL(300, 100, 50), 'Magenta1', 201, ) orange_red1 = Colors.register( RGB(255, 95, 0), HSL(2, 100, 50), 'OrangeRed1', 202, ) indian_red1 = Colors.register( RGB(255, 95, 95), HSL(0, 100, 68), 'IndianRed1', 203, ) indian_red1 = Colors.register( RGB(255, 95, 135), HSL(345, 100, 68), 'IndianRed1', 204, ) hot_pink = Colors.register( RGB(255, 95, 175), HSL(330, 100, 68), 'HotPink', 205, ) hot_pink = Colors.register( RGB(255, 95, 215), HSL(315, 100, 68), 'HotPink', 206, ) medium_orchid1 = Colors.register( RGB(255, 95, 255), HSL(300, 100, 68), 'MediumOrchid1', 207, ) dark_orange = Colors.register( RGB(255, 135, 0), HSL(1, 100, 50), 'DarkOrange', 208, ) salmon1 = Colors.register(RGB(255, 135, 95), HSL(15, 100, 68), 'Salmon1', 209) light_coral = Colors.register( RGB(255, 135, 135), HSL(0, 100, 76), 'LightCoral', 210, ) pale_violet_red1 = Colors.register( RGB(255, 135, 175), HSL(340, 100, 76), 'PaleVioletRed1', 211, ) orchid2 = Colors.register( RGB(255, 135, 215), HSL(320, 100, 76), 'Orchid2', 212, ) orchid1 = Colors.register( RGB(255, 135, 255), HSL(300, 100, 76), 'Orchid1', 213, ) orange1 = Colors.register(RGB(255, 175, 0), HSL(1, 100, 50), 'Orange1', 214) sandy_brown = Colors.register( RGB(255, 175, 95), HSL(30, 100, 68), 'SandyBrown', 215, ) light_salmon1 = Colors.register( RGB(255, 175, 135), HSL(20, 100, 76), 'LightSalmon1', 216, ) light_pink1 = Colors.register( RGB(255, 175, 175), HSL(0, 100, 84), 'LightPink1', 217, ) pink1 = Colors.register(RGB(255, 175, 215), HSL(330, 100, 84), 'Pink1', 218) plum1 = Colors.register(RGB(255, 175, 255), HSL(300, 100, 84), 'Plum1', 219) gold1 = Colors.register(RGB(255, 215, 0), HSL(0, 100, 50), 'Gold1', 220) light_goldenrod2 = Colors.register( RGB(255, 215, 95), HSL(45, 100, 68), 'LightGoldenrod2', 221, ) light_goldenrod2 = Colors.register( RGB(255, 215, 135), HSL(40, 100, 76), 'LightGoldenrod2', 222, ) navajo_white1 = Colors.register( RGB(255, 215, 175), HSL(30, 100, 84), 'NavajoWhite1', 223, ) misty_rose1 = Colors.register( RGB(255, 215, 215), HSL(0, 100, 92), 'MistyRose1', 224, ) thistle1 = Colors.register( RGB(255, 215, 255), HSL(300, 100, 92), 'Thistle1', 225, ) yellow1 = Colors.register(RGB(255, 255, 0), HSL(60, 100, 50), 'Yellow1', 226) light_goldenrod1 = Colors.register( RGB(255, 255, 95), HSL(60, 100, 68), 'LightGoldenrod1', 227, ) khaki1 = Colors.register(RGB(255, 255, 135), HSL(60, 100, 76), 'Khaki1', 228) wheat1 = Colors.register(RGB(255, 255, 175), HSL(60, 100, 84), 'Wheat1', 229) cornsilk1 = Colors.register( RGB(255, 255, 215), HSL(60, 100, 92), 'Cornsilk1', 230, ) grey100 = Colors.register(RGB(255, 255, 255), HSL(0, 0, 100), 'Grey100', 231) grey3 = Colors.register(RGB(8, 8, 8), HSL(0, 0, 3), 'Grey3', 232) grey7 = Colors.register(RGB(18, 18, 18), HSL(0, 0, 7), 'Grey7', 233) grey11 = Colors.register(RGB(28, 28, 28), HSL(0, 0, 10), 'Grey11', 234) grey15 = Colors.register(RGB(38, 38, 38), HSL(0, 0, 14), 'Grey15', 235) grey19 = Colors.register(RGB(48, 48, 48), HSL(0, 0, 18), 'Grey19', 236) grey23 = Colors.register(RGB(58, 58, 58), HSL(0, 0, 22), 'Grey23', 237) grey27 = Colors.register(RGB(68, 68, 68), HSL(0, 0, 26), 'Grey27', 238) grey30 = Colors.register(RGB(78, 78, 78), HSL(0, 0, 30), 'Grey30', 239) grey35 = Colors.register(RGB(88, 88, 88), HSL(0, 0, 34), 'Grey35', 240) grey39 = Colors.register(RGB(98, 98, 98), HSL(0, 0, 37), 'Grey39', 241) grey42 = Colors.register(RGB(108, 108, 108), HSL(0, 0, 40), 'Grey42', 242) grey46 = Colors.register(RGB(118, 118, 118), HSL(0, 0, 46), 'Grey46', 243) grey50 = Colors.register(RGB(128, 128, 128), HSL(0, 0, 50), 'Grey50', 244) grey54 = Colors.register(RGB(138, 138, 138), HSL(0, 0, 54), 'Grey54', 245) grey58 = Colors.register(RGB(148, 148, 148), HSL(0, 0, 58), 'Grey58', 246) grey62 = Colors.register(RGB(158, 158, 158), HSL(0, 0, 61), 'Grey62', 247) grey66 = Colors.register(RGB(168, 168, 168), HSL(0, 0, 65), 'Grey66', 248) grey70 = Colors.register(RGB(178, 178, 178), HSL(0, 0, 69), 'Grey70', 249) grey74 = Colors.register(RGB(188, 188, 188), HSL(0, 0, 73), 'Grey74', 250) grey78 = Colors.register(RGB(198, 198, 198), HSL(0, 0, 77), 'Grey78', 251) grey82 = Colors.register(RGB(208, 208, 208), HSL(0, 0, 81), 'Grey82', 252) grey85 = Colors.register(RGB(218, 218, 218), HSL(0, 0, 85), 'Grey85', 253) grey89 = Colors.register(RGB(228, 228, 228), HSL(0, 0, 89), 'Grey89', 254) grey93 = Colors.register(RGB(238, 238, 238), HSL(0, 0, 93), 'Grey93', 255) dark_gradient: ColorGradient = ColorGradient( red1, orange_red1, dark_orange, orange1, yellow1, yellow2, green_yellow, green1, ) light_gradient: ColorGradient = ColorGradient( red1, orange_red1, dark_orange, orange1, gold3, dark_olive_green3, yellow4, green3, ) bg_gradient: ColorGradient = 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. _colorfgbg: list[str] = os.environ.get('COLORFGBG', '15;0').split(';') if _colorfgbg[-1] == str(white.xterm): # pragma: no cover # Light background gradient: ColorGradient = light_gradient primary = black else: # Default, expect a dark background gradient: ColorGradient = dark_gradient primary = white python-progressbar-4.5.0/progressbar/terminal/os_specific/000077500000000000000000000000001466372462000240345ustar00rootroot00000000000000python-progressbar-4.5.0/progressbar/terminal/os_specific/__init__.py000066400000000000000000000011021466372462000261370ustar00rootroot00000000000000import os if os.name == 'nt': from .windows import ( get_console_mode as _get_console_mode, getch as _getch, reset_console_mode as _reset_console_mode, set_console_mode as _set_console_mode, ) else: from .posix import getch as _getch def _reset_console_mode() -> None: pass def _set_console_mode() -> bool: return False def _get_console_mode() -> int: return 0 getch = _getch reset_console_mode = _reset_console_mode set_console_mode = _set_console_mode get_console_mode = _get_console_mode python-progressbar-4.5.0/progressbar/terminal/os_specific/posix.py000066400000000000000000000005321466372462000255500ustar00rootroot00000000000000import sys import termios import tty def getch() -> str: fd = sys.stdin.fileno() old_settings = termios.tcgetattr(fd) # type: ignore try: tty.setraw(sys.stdin.fileno()) # type: ignore ch = sys.stdin.read(1) finally: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) # type: ignore return ch python-progressbar-4.5.0/progressbar/terminal/os_specific/windows.py000066400000000000000000000106131466372462000261010ustar00rootroot00000000000000# ruff: noqa: N801 """ Windows specific code for the terminal. Note that the naming convention here is non-pythonic because we are matching the Windows API naming. """ from __future__ import annotations import ctypes import enum from ctypes.wintypes import ( BOOL as _BOOL, CHAR as _CHAR, DWORD as _DWORD, HANDLE as _HANDLE, SHORT as _SHORT, UINT as _UINT, WCHAR as _WCHAR, WORD as _WORD, ) _kernel32 = ctypes.windll.Kernel32 # type: ignore _STD_INPUT_HANDLE = _DWORD(-10) _STD_OUTPUT_HANDLE = _DWORD(-11) class WindowsConsoleModeFlags(enum.IntFlag): ENABLE_ECHO_INPUT = 0x0004 ENABLE_EXTENDED_FLAGS = 0x0080 ENABLE_INSERT_MODE = 0x0020 ENABLE_LINE_INPUT = 0x0002 ENABLE_MOUSE_INPUT = 0x0010 ENABLE_PROCESSED_INPUT = 0x0001 ENABLE_QUICK_EDIT_MODE = 0x0040 ENABLE_WINDOW_INPUT = 0x0008 ENABLE_VIRTUAL_TERMINAL_INPUT = 0x0200 ENABLE_PROCESSED_OUTPUT = 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 DISABLE_NEWLINE_AUTO_RETURN = 0x0008 ENABLE_LVB_GRID_WORLDWIDE = 0x0010 def __str__(self) -> str: return f'{self.name} (0x{self.value:04X})' _GetConsoleMode = _kernel32.GetConsoleMode _GetConsoleMode.restype = _BOOL _SetConsoleMode = _kernel32.SetConsoleMode _SetConsoleMode.restype = _BOOL _GetStdHandle = _kernel32.GetStdHandle _GetStdHandle.restype = _HANDLE _ReadConsoleInput = _kernel32.ReadConsoleInputA _ReadConsoleInput.restype = _BOOL _h_console_input = _GetStdHandle(_STD_INPUT_HANDLE) _input_mode = _DWORD() _GetConsoleMode(_HANDLE(_h_console_input), ctypes.byref(_input_mode)) _h_console_output = _GetStdHandle(_STD_OUTPUT_HANDLE) _output_mode = _DWORD() _GetConsoleMode(_HANDLE(_h_console_output), 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() -> None: _SetConsoleMode(_HANDLE(_h_console_input), _DWORD(_input_mode.value)) _SetConsoleMode(_HANDLE(_h_console_output), _DWORD(_output_mode.value)) def set_console_mode() -> bool: mode = ( _input_mode.value | WindowsConsoleModeFlags.ENABLE_VIRTUAL_TERMINAL_INPUT ) _SetConsoleMode(_HANDLE(_h_console_input), _DWORD(mode)) mode = ( _output_mode.value | WindowsConsoleModeFlags.ENABLE_PROCESSED_OUTPUT | WindowsConsoleModeFlags.ENABLE_VIRTUAL_TERMINAL_PROCESSING ) return bool(_SetConsoleMode(_HANDLE(_h_console_output), _DWORD(mode))) def get_console_mode() -> int: return _input_mode.value def set_text_color(color) -> None: _kernel32.SetConsoleTextAttribute(_h_console_output, color) def print_color(text, color) -> None: set_text_color(color) print(text) # noqa: T201 set_text_color(7) # Reset to default color, grey def getch(): lp_buffer = (_INPUT_RECORD * 2)() n_length = _DWORD(2) lp_number_of_events_read = _DWORD() _ReadConsoleInput( _HANDLE(_h_console_input), lp_buffer, n_length, ctypes.byref(lp_number_of_events_read), ) char = lp_buffer[1].Event.KeyEvent.uChar.AsciiChar.decode('ascii') if char == '\x00': return None return char python-progressbar-4.5.0/progressbar/terminal/stream.py000066400000000000000000000071451466372462000234220ustar00rootroot00000000000000from __future__ import annotations import sys import typing from types import TracebackType from typing import Iterable, Iterator from progressbar import base class TextIOOutputWrapper(base.TextIO): # pragma: no cover def __init__(self, stream: base.TextIO) -> None: 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 = -1) -> list[str]: return self.stream.readlines(__hint) def seek(self, __offset: int, __whence: int = 0) -> 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 = 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: int = 0, stream: typing.TextIO = sys.stderr ) -> None: self.lines = lines super().__init__(stream) def write(self, data: str) -> int: data = data.rstrip('\n') # 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) # Move the cursor down self.stream.write(self.DOWN * self.lines) self.flush() return len(data) class LastLineStream(TextIOOutputWrapper): line: str = '' def seekable(self) -> bool: return False def readable(self) -> bool: return True def read(self, __n: int = -1) -> str: if __n < 0: return self.line else: return self.line[:__n] def readline(self, __limit: int = -1) -> str: if __limit < 0: return self.line else: return self.line[:__limit] def write(self, data: str) -> int: self.line = data return len(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 __iter__(self) -> typing.Generator[str, typing.Any, typing.Any]: yield self.line def writelines(self, __lines: Iterable[str]) -> None: line = '' # Walk through the lines and take the last one for line in __lines: # noqa: B007 pass self.line = line python-progressbar-4.5.0/progressbar/utils.py000066400000000000000000000304701466372462000214510ustar00rootroot00000000000000from __future__ import annotations import atexit import contextlib import datetime import io import logging import os import re import sys from types import TracebackType from typing import Iterable, Iterator from python_utils import types from python_utils.converters import scale_1024 from python_utils.terminal import get_terminal_size from python_utils.time import epoch, format_time, timedelta_to_seconds from progressbar import base, env, terminal if types.TYPE_CHECKING: from .bar import ProgressBar, ProgressBarMixinBase # Make sure these are available for import assert timedelta_to_seconds is not None assert get_terminal_size is not None assert format_time is not None assert scale_1024 is not None assert epoch is not None StringT = types.TypeVar('StringT', bound=types.StringTypes) def deltas_to_seconds( *deltas: None | datetime.timedelta | float, default: types.Optional[types.Type[ValueError]] = ValueError, ) -> int | float | None: """ Convert timedeltas and seconds as int to seconds as float while coalescing. >>> deltas_to_seconds(datetime.timedelta(seconds=1, milliseconds=234)) 1.234 >>> deltas_to_seconds(123) 123.0 >>> deltas_to_seconds(1.234) 1.234 >>> deltas_to_seconds(None, 1.234) 1.234 >>> deltas_to_seconds(0, 1.234) 0.0 >>> deltas_to_seconds() Traceback (most recent call last): ... ValueError: No valid deltas passed to `deltas_to_seconds` >>> deltas_to_seconds(None) Traceback (most recent call last): ... ValueError: No valid deltas passed to `deltas_to_seconds` >>> deltas_to_seconds(default=0.0) 0.0 """ for delta in deltas: if delta is None: continue if isinstance(delta, datetime.timedelta): return timedelta_to_seconds(delta) elif not isinstance(delta, float): return float(delta) else: return delta if default is ValueError: raise ValueError('No valid deltas passed to `deltas_to_seconds`') else: # mypy doesn't understand the `default is ValueError` check return default # type: ignore def no_color(value: StringT) -> StringT: """ Return the `value` without ANSI escape codes. >>> no_color(b'\u001b[1234]abc') b'abc' >>> str(no_color('\u001b[1234]abc')) 'abc' >>> str(no_color('\u001b[1234]abc')) 'abc' >>> no_color(123) Traceback (most recent call last): ... TypeError: `value` must be a string or bytes, got 123 """ if isinstance(value, bytes): pattern: bytes = bytes(terminal.ESC, 'ascii') + b'\\[.*?[@-~]' return re.sub(pattern, b'', value) # type: ignore elif isinstance(value, str): return re.sub('\x1b\\[.*?[@-~]', '', value) # type: ignore else: raise TypeError(f'`value` must be a string or bytes, got {value!r}') def len_color(value: types.StringTypes) -> int: """ Return the length of `value` without ANSI escape codes. >>> len_color(b'\u001b[1234]abc') 3 >>> len_color('\u001b[1234]abc') 3 >>> len_color('\u001b[1234]abc') 3 """ return len(no_color(value)) class WrappingIO: buffer: io.StringIO target: base.IO capturing: bool listeners: set needs_clear: bool = False def __init__( self, target: base.IO, capturing: bool = False, listeners: types.Optional[types.Set[ProgressBar]] = None, ) -> None: self.buffer = io.StringIO() self.target = target self.capturing = capturing self.listeners = listeners or set() self.needs_clear = False def write(self, value: str) -> int: ret = 0 if self.capturing: ret += self.buffer.write(value) if '\n' in value: # pragma: no branch self.needs_clear = True for listener in self.listeners: # pragma: no branch listener.update() else: ret += self.target.write(value) if '\n' in value: # pragma: no branch self.flush_target() return ret def flush(self) -> None: self.buffer.flush() def _flush(self) -> None: if value := self.buffer.getvalue(): self.flush() self.target.write(value) self.buffer.seek(0) self.buffer.truncate(0) self.needs_clear = False # when explicitly flushing, always flush the target as well self.flush_target() def flush_target(self) -> None: # pragma: no cover if not self.target.closed and getattr(self.target, 'flush', None): self.target.flush() def __enter__(self) -> WrappingIO: return self def fileno(self) -> int: return self.target.fileno() def isatty(self) -> bool: return self.target.isatty() def read(self, n: int = -1) -> str: return self.target.read(n) def readable(self) -> bool: return self.target.readable() def readline(self, limit: int = -1) -> str: return self.target.readline(limit) def readlines(self, hint: int = -1) -> list[str]: return self.target.readlines(hint) def seek(self, offset: int, whence: int = os.SEEK_SET) -> int: return self.target.seek(offset, whence) def seekable(self) -> bool: return self.target.seekable() def tell(self) -> int: return self.target.tell() def truncate(self, size: types.Optional[int] = None) -> int: return self.target.truncate(size) def writable(self) -> bool: return self.target.writable() def writelines(self, lines: Iterable[str]) -> None: return self.target.writelines(lines) def close(self) -> None: self.flush() self.target.close() def __next__(self) -> str: return self.target.__next__() def __iter__(self) -> Iterator[str]: return self.target.__iter__() def __exit__( self, __t: type[BaseException] | None, __value: BaseException | None, __traceback: TracebackType | None, ) -> None: self.close() class StreamWrapper: """Wrap stdout and stderr globally.""" stdout: base.TextIO | WrappingIO stderr: base.TextIO | WrappingIO original_excepthook: types.Callable[ [ types.Type[BaseException], BaseException, TracebackType | None, ], None, ] wrapped_stdout: int = 0 wrapped_stderr: int = 0 wrapped_excepthook: int = 0 capturing: int = 0 listeners: set def __init__(self) -> None: self.stdout = self.original_stdout = sys.stdout self.stderr = self.original_stderr = sys.stderr self.original_excepthook = sys.excepthook self.wrapped_stdout = 0 self.wrapped_stderr = 0 self.wrapped_excepthook = 0 self.capturing = 0 self.listeners = set() if env.env_flag('WRAP_STDOUT', default=False): # pragma: no cover self.wrap_stdout() if env.env_flag('WRAP_STDERR', default=False): # pragma: no cover self.wrap_stderr() def start_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: if bar: # pragma: no branch self.listeners.add(bar) self.capturing += 1 self.update_capturing() def stop_capturing(self, bar: ProgressBarMixinBase | None = None) -> None: if bar: # pragma: no branch with contextlib.suppress(KeyError): self.listeners.remove(bar) self.capturing -= 1 self.update_capturing() def update_capturing(self) -> None: # pragma: no cover if isinstance(self.stdout, WrappingIO): self.stdout.capturing = self.capturing > 0 if isinstance(self.stderr, WrappingIO): self.stderr.capturing = self.capturing > 0 if self.capturing <= 0: self.flush() def wrap(self, stdout: bool = False, stderr: bool = False) -> None: if stdout: self.wrap_stdout() if stderr: self.wrap_stderr() def wrap_stdout(self) -> WrappingIO: self.wrap_excepthook() if not self.wrapped_stdout: self.stdout = sys.stdout = WrappingIO( # type: ignore self.original_stdout, listeners=self.listeners, ) self.wrapped_stdout += 1 return sys.stdout # type: ignore def wrap_stderr(self) -> WrappingIO: self.wrap_excepthook() if not self.wrapped_stderr: self.stderr = sys.stderr = WrappingIO( # type: ignore self.original_stderr, listeners=self.listeners, ) self.wrapped_stderr += 1 return sys.stderr # type: ignore def unwrap_excepthook(self) -> None: if self.wrapped_excepthook: self.wrapped_excepthook -= 1 sys.excepthook = self.original_excepthook def wrap_excepthook(self) -> None: if not self.wrapped_excepthook: logger.debug('wrapping excepthook') self.wrapped_excepthook += 1 sys.excepthook = self.excepthook def unwrap(self, stdout: bool = False, stderr: bool = False) -> None: if stdout: self.unwrap_stdout() if stderr: self.unwrap_stderr() def unwrap_stdout(self) -> None: if self.wrapped_stdout > 1: self.wrapped_stdout -= 1 else: sys.stdout = self.original_stdout self.wrapped_stdout = 0 def unwrap_stderr(self) -> None: if self.wrapped_stderr > 1: self.wrapped_stderr -= 1 else: sys.stderr = self.original_stderr self.wrapped_stderr = 0 def needs_clear(self) -> bool: # pragma: no cover stdout_needs_clear = getattr(self.stdout, 'needs_clear', False) stderr_needs_clear = getattr(self.stderr, 'needs_clear', False) return stderr_needs_clear or stdout_needs_clear def flush(self) -> None: 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: type[BaseException], exc_value: BaseException, exc_traceback: types.TracebackType | None, ) -> None: self.original_excepthook(exc_type, exc_value, exc_traceback) self.flush() class AttributeDict(dict): """ A dict that can be accessed with .attribute. >>> attrs = AttributeDict(spam=123) # Reading >>> attrs['spam'] 123 >>> attrs.spam 123 # Read after update using attribute >>> attrs.spam = 456 >>> attrs['spam'] 456 >>> attrs.spam 456 # Read after update using dict access >>> attrs['spam'] = 123 >>> attrs['spam'] 123 >>> attrs.spam 123 # Read after update using dict access >>> del attrs.spam >>> attrs['spam'] Traceback (most recent call last): ... KeyError: 'spam' >>> attrs.spam Traceback (most recent call last): ... AttributeError: No such attribute: spam >>> del attrs.spam Traceback (most recent call last): ... AttributeError: No such attribute: spam """ def __getattr__(self, name: str) -> int: if name in self: return self[name] else: raise AttributeError(f'No such attribute: {name}') def __setattr__(self, name: str, value: int) -> None: self[name] = value def __delattr__(self, name: str) -> None: if name in self: del self[name] else: raise AttributeError(f'No such attribute: {name}') logger: logging.Logger = logging.getLogger(__name__) streams = StreamWrapper() atexit.register(streams.flush) python-progressbar-4.5.0/progressbar/widgets.py000066400000000000000000001426621466372462000217660ustar00rootroot00000000000000from __future__ import annotations import abc import contextlib import datetime import functools import logging import typing # Ruff is being stupid and doesn't understand `ClassVar` if it comes from the # `types` module from typing import ClassVar from python_utils import containers, converters, types from . import algorithms, base, terminal, utils from .terminal import colors if types.TYPE_CHECKING: from .bar import NumberT, ProgressBarMixinBase logger = logging.getLogger(__name__) MAX_DATE = datetime.date.max MAX_TIME = datetime.time.max MAX_DATETIME = datetime.datetime.max Data = types.Dict[str, types.Any] FormatString = typing.Optional[str] T = typing.TypeVar('T') def string_or_lambda(input_): if isinstance(input_, str): def render_input(progress, data, width): return input_ % data return render_input else: return input_ def create_wrapper(wrapper): """Convert a wrapper tuple or format string to a format string. >>> create_wrapper('') >>> print(create_wrapper('a{}b')) a{}b >>> print(create_wrapper(('a', 'b'))) a{}b """ if isinstance(wrapper, tuple) and len(wrapper) == 2: a, b = wrapper wrapper = (a or '') + '{}' + (b or '') elif not wrapper: return None if isinstance(wrapper, str): assert '{}' in wrapper, 'Expected string with {} for formatting' else: raise RuntimeError( # noqa: TRY004 'Pass either a begin/end string as a tuple or a template string ' 'with `{}`', ) return wrapper def wrapper(function, wrapper_): """Wrap the output of a function in a template string or a tuple with begin/end strings. """ wrapper_ = create_wrapper(wrapper_) if not wrapper_: return function @functools.wraps(function) def wrap(*args, **kwargs): return wrapper_.format(function(*args, **kwargs)) return wrap def create_marker(marker, wrap=None): def _marker(progress, data, width): if ( progress.max_value is not base.UnknownLength and progress.max_value > 0 ): length = int(progress.value / progress.max_value * width) return marker * length else: return marker if isinstance(marker, str): marker = converters.to_unicode(marker) # Ruff is silly at times... the format is not compatible with the check marker_length_error = 'Markers are required to be 1 char' assert utils.len_color(marker) == 1, marker_length_error return wrapper(_marker, wrap) else: return wrapper(marker, wrap) class FormatWidgetMixin(abc.ABC): """Mixin to format widgets using a formatstring. Variables available: - max_value: The maximum value (can be None with iterators) - value: The current value - total_seconds_elapsed: The seconds since the bar started - seconds_elapsed: The seconds since the bar started modulo 60 - minutes_elapsed: The minutes since the bar started modulo 60 - hours_elapsed: The hours since the bar started modulo 24 - days_elapsed: The hours since the bar started - time_elapsed: Shortcut for HH:MM:SS time since the bar started including days - percentage: Percentage as a float """ def __init__(self, format: str, new_style: bool = False, **kwargs): self.new_style = new_style self.format = format def get_format( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ) -> str: return format or self.format def __call__( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ) -> str: """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 except (TypeError, KeyError): logger.exception( 'Error while formatting %r with data: %r', format_, data, ) raise class WidthWidgetMixin(abc.ABC): """Mixing to make sure widgets are only visible if the screen is within a specified size range so the progressbar fits on both large and small screens. Variables available: - min_width: Only display the widget if at least `min_width` is left - max_width: Only display the widget if at most `max_width` is left >>> class Progress: ... term_width = 0 >>> WidthWidgetMixin(5, 10).check_size(Progress) False >>> Progress.term_width = 5 >>> WidthWidgetMixin(5, 10).check_size(Progress) True >>> Progress.term_width = 10 >>> WidthWidgetMixin(5, 10).check_size(Progress) True >>> Progress.term_width = 11 >>> WidthWidgetMixin(5, 10).check_size(Progress) False """ def __init__(self, min_width=None, max_width=None, **kwargs): self.min_width = min_width self.max_width = max_width def check_size(self, progress: ProgressBarMixinBase): max_width = self.max_width min_width = self.min_width if min_width and min_width > progress.term_width: return False elif max_width and max_width < progress.term_width: # noqa: SIM103 return False else: return True class TGradientColors(typing.TypedDict): fg: types.Optional[terminal.OptionalColor | None] bg: types.Optional[terminal.OptionalColor | None] class TFixedColors(typing.TypedDict): fg_none: types.Optional[terminal.Color | None] bg_none: types.Optional[terminal.Color | None] class WidgetBase(WidthWidgetMixin, metaclass=abc.ABCMeta): """The base class for all widgets. The ProgressBar will call the widget's update value when the widget should be updated. The widget's size may change between calls, but the widget may display incorrectly if the size changes drastically and repeatedly. The INTERVAL timedelta informs the ProgressBar that it should be updated more often because it is time sensitive. The widgets are only visible if the screen is within a specified size range so the progressbar fits on both large and small screens. WARNING: Widgets can be shared between multiple progressbars so any state information specific to a progressbar should be stored within the progressbar instead of the widget. Variables available: - min_width: Only display the widget if at least `min_width` is left - max_width: Only display the widget if at most `max_width` is left - weight: Widgets with a higher `weight` will be calculated before widgets with a lower one - copy: Copy this widget when initializing the progress bar so the progressbar can be reused. Some widgets such as the FormatCustomText require the shared state so this needs to be optional """ copy = True @abc.abstractmethod def __call__(self, progress: ProgressBarMixinBase, data: Data) -> str: """Updates the widget. progress - a reference to the calling ProgressBar """ _fixed_colors: ClassVar[TFixedColors] = TFixedColors( fg_none=None, bg_none=None, ) _gradient_colors: ClassVar[TGradientColors] = TGradientColors( fg=None, bg=None, ) # _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict() # _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | None]] = ( # dict()) _len: typing.Callable[[str | bytes], int] = len @functools.cached_property def uses_colors(self): for value in self._gradient_colors.values(): # pragma: no branch if value is not None: # pragma: no branch return True return any(value is not None for value in self._fixed_colors.values()) 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. This widget is much like the \\hfill command in TeX, it will expand to fill the line. You can use more than one in the same line, and they will all have the same width, and together will fill the line. """ @abc.abstractmethod def __call__( self, progress: ProgressBarMixinBase, data: Data, width: int = 0, ) -> str: """Updates the widget providing the total width the widget must fill. progress - a reference to the calling ProgressBar width - The total width the widget must fill """ class TimeSensitiveWidgetBase(WidgetBase, metaclass=abc.ABCMeta): """The base class for all time sensitive widgets. Some widgets like timers would become out of date unless updated at least every `INTERVAL` """ INTERVAL = datetime.timedelta(milliseconds=100) class FormatLabel(FormatWidgetMixin, WidgetBase): """Displays a formatted label. >>> label = FormatLabel('%(value)s', min_width=5, max_width=10) >>> class Progress: ... pass >>> label = FormatLabel('{value} :: {value:^6}', new_style=True) >>> str(label(Progress, dict(value='test'))) 'test :: test ' """ mapping: ClassVar[types.Dict[str, types.Tuple[str, types.Any]]] = dict( finished=('end_time', None), last_update=('last_update_time', None), max=('max_value', None), seconds=('seconds_elapsed', None), start=('start_time', None), elapsed=('total_seconds_elapsed', utils.format_time), value=('value', None), ) def __init__(self, format: str, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): for name, (key, transform) in self.mapping.items(): with contextlib.suppress(KeyError, ValueError, IndexError): if transform is None: data[name] = data[key] else: data[name] = transform(data[key]) return FormatWidgetMixin.__call__(self, progress, data, format) class Timer(FormatLabel, TimeSensitiveWidgetBase): """WidgetBase which displays the elapsed seconds.""" def __init__(self, format='Elapsed Time: %(elapsed)s', **kwargs): if '%s' in format and '%(elapsed)s' not in format: format = format.replace('%s', '%(elapsed)s') FormatLabel.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) # This is exposed as a static method for backwards compatibility format_time = staticmethod(utils.format_time) class SamplesMixin(TimeSensitiveWidgetBase, metaclass=abc.ABCMeta): """ Mixing for widgets that average multiple measurements. Note that samples can be either an integer or a timedelta to indicate a certain amount of time >>> class progress: ... last_update_time = datetime.datetime.now() ... value = 1 ... extra = dict() >>> samples = SamplesMixin(samples=2) >>> samples(progress, None, True) (None, None) >>> progress.last_update_time += datetime.timedelta(seconds=1) >>> samples(progress, None, True) == (datetime.timedelta(seconds=1), 0) True >>> progress.last_update_time += datetime.timedelta(seconds=1) >>> samples(progress, None, True) == (datetime.timedelta(seconds=1), 0) True >>> samples = SamplesMixin(samples=datetime.timedelta(seconds=1)) >>> _, value = samples(progress, None) >>> value SliceableDeque([1, 1]) >>> samples(progress, None, True) == (datetime.timedelta(seconds=1), 0) True """ def __init__( self, samples=datetime.timedelta(seconds=2), key_prefix=None, **kwargs, ): self.samples = samples self.key_prefix = (key_prefix or self.__class__.__name__) + '_' TimeSensitiveWidgetBase.__init__(self, **kwargs) def get_sample_times(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault( f'{self.key_prefix}sample_times', containers.SliceableDeque(), ) def get_sample_values(self, progress: ProgressBarMixinBase, data: Data): return progress.extra.setdefault( f'{self.key_prefix}sample_values', containers.SliceableDeque(), ) def __call__( self, progress: ProgressBarMixinBase, data: Data, delta: bool = False, ): 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 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) sample_values.append(progress.value) if isinstance(self.samples, datetime.timedelta): minimum_time = progress.last_update_time - self.samples minimum_value = sample_values[-1] while ( sample_times[2:] and minimum_time > sample_times[1] and minimum_value > sample_values[1] ): sample_times.pop(0) sample_values.pop(0) elif len(sample_times) > self.samples: sample_times.pop(0) sample_values.pop(0) if delta: if delta_time := sample_times[-1] - sample_times[0]: delta_value = sample_values[-1] - sample_values[0] return delta_time, delta_value else: return None, None else: return sample_times, sample_values class ETA(Timer): """WidgetBase which attempts to estimate the time of arrival.""" def __init__( self, format_not_started='ETA: --:--:--', format_finished='Time: %(elapsed)8s', format='ETA: %(eta)8s', format_zero='ETA: 00:00:00', format_na='ETA: N/A', **kwargs, ): if '%s' in format and '%(eta)s' not in format: format = format.replace('%s', '%(eta)s') Timer.__init__(self, **kwargs) self.format_not_started = format_not_started self.format_finished = format_finished self.format = format self.format_zero = format_zero self.format_NA = format_na 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'] return remaining * per_item else: return 0 def __call__( self, progress: ProgressBarMixinBase, data: Data, value=None, elapsed=None, ): """Updates the widget to show the ETA or total time when finished.""" if value is None: value = data['value'] if elapsed is None: elapsed = data['time_elapsed'] eta_na = False try: data['eta_seconds'] = self._calculate_eta( progress, data, value=value, elapsed=elapsed, ) except TypeError: data['eta_seconds'] = None eta_na = True data['eta'] = None if data['eta_seconds']: with contextlib.suppress(ValueError, OverflowError, OSError): data['eta'] = utils.format_time(data['eta_seconds']) if data['value'] == progress.min_value: fmt = self.format_not_started elif progress.end_time: fmt = self.format_finished elif data['eta']: fmt = self.format elif eta_na: fmt = self.format_NA else: fmt = self.format_zero return Timer.__call__(self, progress, data, format=fmt) class AbsoluteETA(ETA): """Widget which attempts to estimate the absolute time of arrival.""" def _calculate_eta( self, progress: ProgressBarMixinBase, data: Data, value, elapsed, ): eta_seconds = ETA._calculate_eta(self, progress, data, value, elapsed) now = datetime.datetime.now() try: return now + datetime.timedelta(seconds=eta_seconds) except OverflowError: # pragma: no cover return datetime.datetime.max def __init__( self, format_not_started='Estimated finish time: ----/--/-- --:--:--', format_finished='Finished at: %(elapsed)s', format='Estimated finish time: %(eta)s', **kwargs, ): ETA.__init__( self, format_not_started=format_not_started, format_finished=format_finished, format=format, **kwargs, ) class AdaptiveETA(ETA, SamplesMixin): """WidgetBase which attempts to estimate the time of arrival. Uses a sampled average of the speed based on the 10 last updates. Very convenient for resuming the progress halfway. """ exponential_smoothing: bool exponential_smoothing_factor: float def __init__( self, exponential_smoothing=True, exponential_smoothing_factor=0.1, **kwargs, ): self.exponential_smoothing = exponential_smoothing self.exponential_smoothing_factor = exponential_smoothing_factor ETA.__init__(self, **kwargs) SamplesMixin.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, value=None, elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, progress, data, delta=True, ) if not elapsed: value = None elapsed = 0 return ETA.__call__(self, progress, data, value=value, elapsed=elapsed) class SmoothingETA(ETA): """ WidgetBase which attempts to estimate the time of arrival using an exponential moving average (EMA) of the speed. EMA applies more weight to recent data points and less to older ones, and doesn't require storing all past values. This approach works well with varying data points and smooths out fluctuations effectively. """ smoothing_algorithm: algorithms.SmoothingAlgorithm smoothing_parameters: dict[str, float] def __init__( self, smoothing_algorithm: type[ algorithms.SmoothingAlgorithm ] = algorithms.ExponentialMovingAverage, smoothing_parameters: dict[str, float] | None = None, **kwargs, ): self.smoothing_parameters = smoothing_parameters or {} self.smoothing_algorithm = smoothing_algorithm( **(self.smoothing_parameters or {}), ) ETA.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, value=None, elapsed=None, ): if value is None: # pragma: no branch value = data['value'] if elapsed is None: # pragma: no branch elapsed = data['time_elapsed'] self.smoothing_algorithm.update(value, elapsed) return ETA.__call__(self, progress, data, value=value, elapsed=elapsed) class DataSize(FormatWidgetMixin, WidgetBase): """ Widget for showing an amount of data transferred/processed. Automatically formats the value (assumed to be a count of bytes) with an appropriate sized unit, based on the IEC binary prefixes (powers of 1024). """ def __init__( self, variable='value', format='%(scaled)5.1f %(prefix)s%(unit)s', unit='B', prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), **kwargs, ): self.variable = variable self.unit = unit self.prefixes = prefixes FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): value = data[self.variable] if value is not None: scaled, power = utils.scale_1024(value, len(self.prefixes)) else: scaled = power = 0 data['scaled'] = scaled data['prefix'] = self.prefixes[power] data['unit'] = self.unit return FormatWidgetMixin.__call__(self, progress, data, format) class FileTransferSpeed(FormatWidgetMixin, TimeSensitiveWidgetBase): """ Widget for showing the current transfer speed (useful for file transfers). """ def __init__( self, format='%(scaled)5.1f %(prefix)s%(unit)-s/s', inverse_format='%(scaled)5.1f s/%(prefix)s%(unit)-s', unit='B', prefixes=('', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'), **kwargs, ): self.unit = unit self.prefixes = prefixes self.inverse_format = inverse_format FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) def _speed(self, value, elapsed): speed = float(value) / elapsed return utils.scale_1024(speed, len(self.prefixes)) def __call__( self, progress: ProgressBarMixinBase, data, value=None, total_seconds_elapsed=None, ): """Updates the widget with the current SI prefixed speed.""" if value is None: value = data['value'] elapsed = utils.deltas_to_seconds( total_seconds_elapsed, data['total_seconds_elapsed'], ) if ( value is not None and elapsed is not None and elapsed > 2e-6 and value > 2e-6 ): # =~ 0 scaled, power = self._speed(value, elapsed) else: scaled = power = 0 data['unit'] = self.unit if power == 0 and scaled < 0.1: if scaled > 0: scaled = 1 / scaled data['scaled'] = scaled data['prefix'] = self.prefixes[0] return FormatWidgetMixin.__call__( self, progress, data, self.inverse_format, ) else: data['scaled'] = scaled data['prefix'] = self.prefixes[power] return FormatWidgetMixin.__call__(self, progress, data) class AdaptiveTransferSpeed(FileTransferSpeed, SamplesMixin): """Widget for showing the transfer speed based on the last X samples.""" def __init__(self, **kwargs): FileTransferSpeed.__init__(self, **kwargs) SamplesMixin.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data, value=None, total_seconds_elapsed=None, ): elapsed, value = SamplesMixin.__call__( self, progress, data, delta=True, ) return FileTransferSpeed.__call__(self, progress, data, value, elapsed) class AnimatedMarker(TimeSensitiveWidgetBase): """An animated marker for the progress bar which defaults to appear as if it were rotating. """ def __init__( self, markers='|/-\\', default=None, fill='', marker_wrap=None, fill_wrap=None, **kwargs, ): self.markers = markers self.marker_wrap = create_wrapper(marker_wrap) self.default = default or markers[0] self.fill_wrap = create_wrapper(fill_wrap) self.fill = create_marker(fill, self.fill_wrap) if fill else None WidgetBase.__init__(self, **kwargs) def __call__(self, progress: ProgressBarMixinBase, data: Data, width=None): """Updates the widget to show the next marker or the first marker when finished. """ if progress.end_time: return self.default marker = self.markers[data['updates'] % len(self.markers)] if self.marker_wrap: marker = self.marker_wrap.format(marker) if self.fill: # Cut the last character so we can replace it with our marker fill = self.fill( progress, data, width - progress.custom_len(marker), # type: ignore ) else: fill = '' # Python 3 returns an int when indexing bytes if isinstance(marker, int): # pragma: no cover marker = bytes(marker) fill = fill.encode() else: # cast fill to the same type as marker fill = type(marker)(fill) return fill + marker # type: ignore # Alias for backwards compatibility RotatingMarker = AnimatedMarker class Counter(FormatWidgetMixin, WidgetBase): """Displays the current count.""" def __init__(self, format='%(value)d', **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, format=None, ): return FormatWidgetMixin.__call__(self, progress, data, format) class ColoredMixin: _fixed_colors: ClassVar[TFixedColors] = TFixedColors( fg_none=colors.yellow, bg_none=None, ) _gradient_colors: ClassVar[TGradientColors] = TGradientColors( fg=colors.gradient, bg=None, ) # _fixed_colors: ClassVar[dict[str, terminal.Color | None]] = dict( # fg_none=colors.yellow, bg_none=None) # _gradient_colors: ClassVar[dict[str, terminal.OptionalColor | # None]] = dict(fg=colors.gradient, # bg=None) 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 FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) def get_format( self, progress: ProgressBarMixinBase, data: Data, format=None, ): # If percentage is not available, display N/A% percentage = data.get('percentage', base.Undefined) if not percentage and percentage != 0: output = self.na else: output = FormatWidgetMixin.get_format(self, progress, data, format) return self._apply_colors(output, data) class SimpleProgress(FormatWidgetMixin, ColoredMixin, WidgetBase): """Returns progress as a count of the total (e.g.: "5 of 47").""" max_width_cache: dict[ str | tuple[ NumberT | types.Type[base.UnknownLength] | None, NumberT | types.Type[base.UnknownLength] | None, ], types.Optional[int], ] DEFAULT_FORMAT = '%(value_s)s of %(max_value_s)s' def __init__(self, format=DEFAULT_FORMAT, **kwargs): FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, format=format, **kwargs) self.max_width_cache = dict() # Pyright isn't happy when we set the key in the initialiser 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' # if value is not available it's the zeroth iteration if data.get('value'): data['value_s'] = data['value'] else: data['value_s'] = 0 formatted = FormatWidgetMixin.__call__( self, progress, data, format=format, ) # Guess the maximum width from the min and max value key = progress.min_value, progress.max_value max_width: types.Optional[int] = self.max_width_cache.get( key, self.max_width, ) if not max_width: temporary_data = data.copy() for value in key: if value is None: # pragma: no cover continue temporary_data['value'] = value if width := progress.custom_len( # pragma: no branch FormatWidgetMixin.__call__( self, progress, temporary_data, format=format, ), ): max_width = max(max_width or 0, width) self.max_width_cache[key] = max_width # Adjust the output to have a consistent size in all cases if max_width: # pragma: no branch formatted = formatted.rjust(max_width) return self._apply_colors(formatted, data) class Bar(AutoWidthWidgetBase): """A progress bar which stretches to fill the line.""" fg: terminal.OptionalColor | None = colors.gradient bg: terminal.OptionalColor | None = None def __init__( self, marker='#', left='|', right='|', fill=' ', fill_left=True, marker_wrap=None, **kwargs, ): """Creates a customizable progress bar. The callable takes the same parameters as the `__call__` method marker - string or callable object to use as a marker left - string or callable object to use as a left border right - string or callable object to use as a right border fill - character to use for the empty part of the progress bar fill_left - whether to fill from the left or the right """ self.marker = create_marker(marker, marker_wrap) self.left = string_or_lambda(left) self.right = string_or_lambda(right) self.fill = string_or_lambda(fill) self.fill_left = fill_left AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, width: int = 0, color=True, ): """Updates the progress bar and its subcomponents.""" left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) marker = converters.to_unicode(self.marker(progress, data, width)) 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 self.fill_left: marker = marker.ljust(width, fill) else: marker = marker.rjust(width, fill) if color: marker = self._apply_colors(marker, data) return left + marker + right class ReverseBar(Bar): """A bar which has a marker that goes from right to left.""" def __init__( self, marker='#', left='|', right='|', fill=' ', fill_left=False, **kwargs, ): """Creates a customizable progress bar. marker - string or updatable object to use as a marker left - string or updatable object to use as a left border right - string or updatable object to use as a right border fill - character to use for the empty part of the progress bar fill_left - whether to fill from the left or the right """ Bar.__init__( self, marker=marker, left=left, right=right, fill=fill, fill_left=fill_left, **kwargs, ) class BouncingBar(Bar, TimeSensitiveWidgetBase): """A bar which has a marker which bounces from side to side.""" INTERVAL = datetime.timedelta(milliseconds=100) def __call__( self, progress: ProgressBarMixinBase, data: Data, width: int = 0, color=True, ): """Updates the progress bar and its subcomponents.""" left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) marker = converters.to_unicode(self.marker(progress, data, width)) fill = converters.to_unicode(self.fill(progress, data, width)) if width: # pragma: no branch value = int( data['total_seconds_elapsed'] / self.INTERVAL.total_seconds(), ) a = value % width b = width - a - 1 if value % (width * 2) >= width: a, b = b, a if self.fill_left: marker = a * fill + marker + b * fill else: marker = b * fill + marker + a * fill return left + marker + right class FormatCustomText(FormatWidgetMixin, WidgetBase): mapping: types.Dict[str, types.Any] = dict() # noqa: RUF012 copy = False def __init__( self, format: str, mapping: types.Optional[types.Dict[str, types.Any]] = None, **kwargs, ): self.format = format self.mapping = mapping or self.mapping FormatWidgetMixin.__init__(self, format=format, **kwargs) WidgetBase.__init__(self, **kwargs) def update_mapping(self, **mapping: types.Dict[str, types.Any]): self.mapping.update(mapping) def __call__( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): return FormatWidgetMixin.__call__( self, progress, self.mapping, format or self.format, ) class VariableMixin: """Mixin to display a custom user variable.""" def __init__(self, name, **kwargs): if not isinstance(name, str): raise TypeError('Variable(): argument must be a string') if len(name.split()) > 1: raise ValueError('Variable(): argument must be single word') self.name = name class MultiRangeBar(Bar, VariableMixin): """ A bar with multiple sub-ranges, each represented by a different symbol. The various ranges are represented on a user-defined variable, formatted as .. code-block:: python [['Symbol1', amount1], ['Symbol2', amount2], ...] """ def __init__(self, name, markers, **kwargs): VariableMixin.__init__(self, name) Bar.__init__(self, **kwargs) self.markers = [string_or_lambda(marker) for marker in markers] def get_values(self, progress: ProgressBarMixinBase, data: Data): return data['variables'][self.name] or [] def __call__( self, progress: ProgressBarMixinBase, data: Data, width: int = 0, color=True, ): """Updates the progress bar and its subcomponents.""" left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) values = self.get_values(progress, data) values_sum = sum(values) if width and values_sum: middle = '' values_accumulated = 0 width_accumulated = 0 for marker, value in zip(self.markers, values): marker = converters.to_unicode(marker(progress, data, width)) assert progress.custom_len(marker) == 1 values_accumulated += value item_width = int(values_accumulated / values_sum * width) item_width -= width_accumulated width_accumulated += item_width middle += item_width * marker else: fill = converters.to_unicode(self.fill(progress, data, width)) assert progress.custom_len(fill) == 1 middle = fill * width return left + middle + right class MultiProgressBar(MultiRangeBar): def __init__( self, name, # NOTE: the markers are not whitespace even though some # terminals don't show the characters correctly! markers=' ▁▂▃▄▅▆▇█', **kwargs, ): MultiRangeBar.__init__( self, name=name, markers=list(reversed(markers)), **kwargs, ) def get_values(self, progress: ProgressBarMixinBase, data: Data): ranges = [0.0] * len(self.markers) for value in data['variables'][self.name] or []: if not isinstance(value, (int, float)): # Progress is (value, max) progress_value, progress_max = value value = float(progress_value) / float(progress_max) if not 0 <= value <= 1: raise ValueError( 'Range value needs to be in the range [0..1], ' f'got {value}', ) range_ = value * (len(ranges) - 1) pos = int(range_) frac = range_ % 1 ranges[pos] += 1 - frac if frac: ranges[pos + 1] += frac if self.fill_left: # pragma: no branch ranges = list(reversed(ranges)) return ranges class GranularMarkers: smooth = ' ▏▎▍▌▋▊▉█' bar = ' ▁▂▃▄▅▆▇█' snake = ' ▖▌▛█' fade_in = ' ░▒▓█' dots = ' ⡀⡄⡆⡇⣇⣧⣷⣿' growing_circles = ' .oO' class GranularBar(AutoWidthWidgetBase): """A progressbar that can display progress at a sub-character granularity by using multiple marker characters. Examples of markers: - Smooth: ` ▏▎▍▌▋▊▉█` (default) - Bar: ` ▁▂▃▄▅▆▇█` - Snake: ` ▖▌▛█` - Fade in: ` ░▒▓█` - Dots: ` ⡀⡄⡆⡇⣇⣧⣷⣿` - Growing circles: ` .oO` The markers can be accessed through GranularMarkers. GranularMarkers.dots for example """ def __init__( self, markers=GranularMarkers.smooth, left='|', right='|', **kwargs, ): """Creates a customizable progress bar. markers - string of characters to use as granular progress markers. The first character should represent 0% and the last 100%. Ex: ` .oO`. left - string or callable object to use as a left border right - string or callable object to use as a right border """ self.markers = markers self.left = string_or_lambda(left) self.right = string_or_lambda(right) AutoWidthWidgetBase.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, width: int = 0, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) max_value = progress.max_value if ( max_value is not base.UnknownLength and typing.cast(float, max_value) > 0 ): percent = progress.value / max_value # type: ignore else: percent = 0 num_chars = percent * width marker = self.markers[-1] * int(num_chars) if marker_idx := int((num_chars % 1) * (len(self.markers) - 1)): 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) marker = marker.ljust(width, self.markers[0]) return left + marker + right class FormatLabelBar(FormatLabel, Bar): """A bar which has a formatted label in the center.""" def __init__(self, format, **kwargs): FormatLabel.__init__(self, format, **kwargs) Bar.__init__(self, **kwargs) def __call__( # type: ignore self, progress: ProgressBarMixinBase, data: Data, width: int = 0, format: FormatString = None, ): center = FormatLabel.__call__(self, progress, data, format=format) 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 ( self._apply_colors( bar[:center_left], data, ) + self._apply_colors( center, data, ) + self._apply_colors( bar[center_right:], data, ) ) class PercentageLabelBar(Percentage, FormatLabelBar): """A bar which displays the current percentage in the center.""" # %3d adds an extra space that makes it look off-center # %2d keeps the label somewhat consistently in-place def __init__(self, format='%(percentage)2d%%', na='N/A%%', **kwargs): Percentage.__init__(self, format, na=na, **kwargs) FormatLabelBar.__init__(self, format, **kwargs) def __call__( # type: ignore self, progress: ProgressBarMixinBase, data: Data, width: int = 0, format: FormatString = None, ): return super().__call__(progress, data, width, format=format) class Variable(FormatWidgetMixin, VariableMixin, WidgetBase): """Displays a custom variable.""" def __init__( self, name, format='{name}: {formatted_value}', width=6, precision=3, **kwargs, ): """Creates a Variable associated with the given name.""" self.format = format self.width = width self.precision = precision VariableMixin.__init__(self, name=name) WidgetBase.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): value = data['variables'][self.name] context = data.copy() context['value'] = value context['name'] = self.name context['width'] = self.width context['precision'] = self.precision try: # Make sure to try and cast the value first, otherwise the # formatting will generate warnings/errors on newer Python releases value = float(value) fmt = '{value:{width}.{precision}}' context['formatted_value'] = fmt.format(**context) except (TypeError, ValueError): if value: context['formatted_value'] = '{value:{width}}'.format( **context, ) else: context['formatted_value'] = '-' * self.width return self.format.format(**context) class DynamicMessage(Variable): """Kept for backwards compatibility, please use `Variable` instead.""" class CurrentTime(FormatWidgetMixin, TimeSensitiveWidgetBase): """Widget which displays the current (date)time with seconds resolution.""" INTERVAL = datetime.timedelta(seconds=1) def __init__( self, format='Current Time: %(current_time)s', microseconds=False, **kwargs, ): self.microseconds = microseconds FormatWidgetMixin.__init__(self, format=format, **kwargs) TimeSensitiveWidgetBase.__init__(self, **kwargs) def __call__( self, progress: ProgressBarMixinBase, data: Data, format: types.Optional[str] = None, ): data['current_time'] = self.current_time() data['current_datetime'] = self.current_datetime() return FormatWidgetMixin.__call__(self, progress, data, format=format) def current_datetime(self): now = datetime.datetime.now() if not self.microseconds: now = now.replace(microsecond=0) return now def current_time(self): return self.current_datetime().time() class JobStatusBar(Bar, VariableMixin): """ Widget which displays the job status as markers on the bar. The status updates can be given either as a boolean or as a string. If it's a string, it will be displayed as-is. If it's a boolean, it will be displayed as a marker (default: '█' for success, 'X' for failure) configurable through the `success_marker` and `failure_marker` parameters. Args: name: The name of the variable to use for the status updates. left: The left border of the bar. right: The right border of the bar. fill: The fill character of the bar. fill_left: Whether to fill the bar from the left or the right. success_fg_color: The foreground color to use for successful jobs. success_bg_color: The background color to use for successful jobs. success_marker: The marker to use for successful jobs. failure_fg_color: The foreground color to use for failed jobs. failure_bg_color: The background color to use for failed jobs. failure_marker: The marker to use for failed jobs. """ success_fg_color: terminal.Color | None = colors.green success_bg_color: terminal.Color | None = None success_marker: str = '█' failure_fg_color: terminal.Color | None = colors.red failure_bg_color: terminal.Color | None = None failure_marker: str = 'X' job_markers: list[str] def __init__( self, name: str, left='|', right='|', fill=' ', fill_left=True, success_fg_color=colors.green, success_bg_color=None, success_marker='█', failure_fg_color=colors.red, failure_bg_color=None, failure_marker='X', **kwargs, ): VariableMixin.__init__(self, name) self.name = name self.job_markers = [] self.left = string_or_lambda(left) self.right = string_or_lambda(right) self.fill = string_or_lambda(fill) self.success_fg_color = success_fg_color self.success_bg_color = success_bg_color self.success_marker = success_marker self.failure_fg_color = failure_fg_color self.failure_bg_color = failure_bg_color self.failure_marker = failure_marker Bar.__init__( self, left=left, right=right, fill=fill, fill_left=fill_left, **kwargs, ) def __call__( self, progress: ProgressBarMixinBase, data: Data, width: int = 0, color=True, ): left = converters.to_unicode(self.left(progress, data, width)) right = converters.to_unicode(self.right(progress, data, width)) width -= progress.custom_len(left) + progress.custom_len(right) status: str | bool | None = data['variables'].get(self.name) if width and status is not None: if status is True: marker = self.success_marker fg_color = self.success_fg_color bg_color = self.success_bg_color elif status is False: # pragma: no branch marker = self.failure_marker fg_color = self.failure_fg_color bg_color = self.failure_bg_color else: # pragma: no cover marker = status fg_color = bg_color = None marker = converters.to_unicode(marker) if fg_color: # pragma: no branch marker = fg_color.fg(marker) if bg_color: # pragma: no cover marker = bg_color.bg(marker) self.job_markers.append(marker) marker = ''.join(self.job_markers) width -= progress.custom_len(marker) fill = converters.to_unicode(self.fill(progress, data, width)) fill = self._apply_colors(fill * width, data) if self.fill_left: # pragma: no branch marker += fill else: # pragma: no cover marker = fill + marker else: marker = '' return left + marker + right python-progressbar-4.5.0/pyproject.toml000066400000000000000000000134131466372462000203200ustar00rootroot00000000000000 [project] authors = [{ name = 'Rick van Hattem (Wolph)', email = 'wolph@wol.ph' }] dynamic = ['version'] keywords = [ 'REPL', 'animated', 'bar', 'color', 'console', 'duration', 'efficient', 'elapsed', 'eta', 'feedback', 'live', 'meter', 'monitor', 'monitoring', 'multi-threaded', 'progress', 'progress-bar', 'progressbar', 'progressmeter', 'python', 'rate', 'simple', 'speed', 'spinner', 'stats', 'terminal', 'throughput', 'time', 'visual', ] license = { text = 'BSD-3-Clause' } name = 'progressbar2' requires-python = '>=3.8' classifiers = [ 'Development Status :: 5 - Production/Stable', 'Development Status :: 6 - Mature', 'Environment :: Console', 'Environment :: MacOS X', 'Environment :: Other Environment', 'Environment :: Win32 (MS Windows)', 'Environment :: X11 Applications', 'Framework :: IPython', 'Framework :: Jupyter', 'Intended Audience :: Developers', 'Intended Audience :: Education', 'Intended Audience :: End Users/Desktop', 'Intended Audience :: Other Audience', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: BSD License', 'Natural Language :: English', 'Operating System :: MacOS :: MacOS X', 'Operating System :: MacOS', 'Operating System :: Microsoft :: MS-DOS', 'Operating System :: Microsoft :: Windows', 'Operating System :: Microsoft', 'Operating System :: POSIX :: BSD :: FreeBSD', 'Operating System :: POSIX :: BSD', 'Operating System :: POSIX :: Linux', 'Operating System :: POSIX :: SunOS/Solaris', 'Operating System :: POSIX', 'Operating System :: Unix', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: IronPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Programming Language :: Python :: Implementation', 'Programming Language :: Python', 'Programming Language :: Unix Shell', 'Topic :: Desktop Environment', 'Topic :: Education :: Computer Aided Instruction (CAI)', 'Topic :: Education :: Testing', 'Topic :: Office/Business', 'Topic :: Other/Nonlisted Topic', 'Topic :: Software Development :: Build Tools', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Pre-processors', 'Topic :: Software Development :: User Interfaces', 'Topic :: System :: Installation/Setup', 'Topic :: System :: Logging', 'Topic :: System :: Monitoring', 'Topic :: System :: Shells', 'Topic :: Terminals', 'Topic :: Utilities', ] description = 'A Python Progressbar library to provide visual (yet text based) progress to long running operations.' readme = 'README.rst' dependencies = ['python-utils >= 3.8.1'] [tool.setuptools.dynamic] version = { attr = 'progressbar.__about__.__version__' } [tool.setuptools.packages.find] exclude = ['docs*', 'tests*'] [tool.setuptools] include-package-data = true [project.scripts] progressbar = 'progressbar.__main__:main' [project.optional-dependencies] docs = ['sphinx>=1.8.5', 'sphinx-autodoc-typehints>=1.6.0'] tests = [ 'dill>=0.3.6', 'flake8>=3.7.7', 'freezegun>=0.3.11', 'pytest-cov>=2.6.1', 'pytest-mypy', 'pytest>=4.6.9', 'sphinx>=1.8.5', 'pywin32; sys_platform == "win32"', ] [project.urls] bugs = 'https://github.com/wolph/python-progressbar/issues' documentation = 'https://progressbar-2.readthedocs.io/en/latest/' repository = 'https://github.com/wolph/python-progressbar/' [build-system] build-backend = 'setuptools.build_meta' requires = ['setuptools', 'setuptools-scm'] [tool.codespell] skip = '*/htmlcov,./docs/_build,*.asc' ignore-words-list = 'datas,numbert' [tool.black] line-length = 79 skip-string-normalization = true [tool.mypy] packages = ['progressbar', 'tests'] exclude = [ '^docs$', '^tests/original_examples.py$', '^examples.py$', ] [tool.coverage.run] branch = true source = [ 'progressbar', 'tests', ] omit = [ '*/mock/*', '*/nose/*', '.tox/*', '*/os_specific/*', ] [tool.coverage.paths] source = [ 'progressbar', ] [tool.coverage.report] fail_under = 100 exclude_lines = [ 'pragma: no cover', '@abc.abstractmethod', 'def __repr__', 'if self.debug:', 'if settings.DEBUG', 'raise AssertionError', 'raise NotImplementedError', 'if 0:', 'if __name__ == .__main__.:', 'if types.TYPE_CHECKING:', '@typing.overload', 'if os.name == .nt.:', ] [tool.pyright] include= ['progressbar'] exclude= ['examples'] ignore= ['docs'] #strict = [ # 'progressbar/algorithms.py', # 'progressbar/env.py', # 'progressbar/shortcuts.py', ## 'progressbar/multi.py', ## 'progressbar/__init__.py', # 'progressbar/terminal/__init__.py', ## 'progressbar/terminal/stream.py', # 'progressbar/terminal/os_specific/__init__.py', # 'progressbar/terminal/os_specific/posix.py', ## 'progressbar/terminal/os_specific/windows.py', ## 'progressbar/terminal/base.py', ## 'progressbar/terminal/colors.py', ## 'progressbar/widgets.py', ## 'progressbar/utils.py', # 'progressbar/__about__.py', ## 'progressbar/bar.py', # 'progressbar/__main__.py', # 'progressbar/base.py', #] reportIncompatibleMethodOverride = false reportUnnecessaryIsInstance = false reportUnnecessaryCast = false reportUnnecessaryTypeAssertion = false reportUnnecessaryComparison = false reportUnnecessaryContains = false python-progressbar-4.5.0/pytest.ini000066400000000000000000000007171466372462000174400ustar00rootroot00000000000000[pytest] python_files = progressbar/*.py tests/*.py addopts = --cov progressbar --cov-report=html --cov-report=term-missing --cov-report=xml --cov-config=./pyproject.toml --no-cov-on-fail --doctest-modules norecursedirs = .* _* build dist docs progressbar/terminal/os_specific tmp* filterwarnings = ignore::DeprecationWarning markers = no_freezegun: Disable automatic freezegun wrapping python-progressbar-4.5.0/ruff.toml000066400000000000000000000057171466372462000172530ustar00rootroot00000000000000# We keep the ruff configuration separate so it can easily be shared across # all projects target-version = 'py38' #src = ['progressbar'] exclude = [ '.venv', '.tox', 'test.py', ] lint.ignore = [ 'A001', # Variable {name} is shadowing a Python builtin 'A002', # Argument {name} is shadowing a Python builtin 'A003', # Class attribute {name} is shadowing a Python builtin 'B023', # function-uses-loop-variable 'B024', # `FormatWidgetMixin` is an abstract base class, but it has no abstract methods 'D205', # blank-line-after-summary 'D212', # multi-line-summary-first-line 'RET505', # Unnecessary `else` after `return` statement 'TRY003', # Avoid specifying long messages outside the exception class 'RET507', # Unnecessary `elif` after `continue` statement 'C405', # Unnecessary {obj_type} literal (rewrite as a set literal) 'C406', # Unnecessary {obj_type} literal (rewrite as a dict literal) 'C408', # Unnecessary {obj_type} call (rewrite as a literal) 'SIM114', # Combine `if` branches using logical `or` operator 'RET506', # Unnecessary `else` after `raise` statement 'Q001', # Remove bad quotes 'Q002', # Remove bad quotes 'COM812', # Missing trailing comma in a list 'ISC001', # String concatenation with implicit str conversion 'SIM108', # Ternary operators are not always more readable ] line-length = 79 lint.select = [ 'A', # flake8-builtins 'ASYNC', # flake8 async checker 'B', # flake8-bugbear 'C4', # flake8-comprehensions 'C90', # mccabe 'COM', # flake8-commas ## Require docstrings for all public methods, would be good to enable at some point # 'D', # pydocstyle 'E', # pycodestyle error ('W' for warning) 'F', # pyflakes 'FA', # flake8-future-annotations 'I', # isort 'ICN', # flake8-import-conventions 'INP', # flake8-no-pep420 'ISC', # flake8-implicit-str-concat 'N', # pep8-naming 'NPY', # NumPy-specific rules 'PERF', # perflint, 'PIE', # flake8-pie 'Q', # flake8-quotes 'RET', # flake8-return 'RUF', # Ruff-specific rules 'SIM', # flake8-simplify 'T20', # flake8-print 'TD', # flake8-todos 'TRY', # tryceratops 'UP', # pyupgrade ] [lint.per-file-ignores] 'tests/*' = ['INP001', 'T201', 'T203'] 'examples.py' = ['T201', 'N806'] 'docs/conf.py' = ['E501', 'INP001'] 'docs/_theme/flask_theme_support.py' = ['RUF012', 'INP001'] [lint.pydocstyle] convention = 'google' ignore-decorators = [ 'typing.overload', 'typing.override', ] [lint.isort] case-sensitive = true combine-as-imports = true force-wrap-aliases = true [lint.flake8-quotes] docstring-quotes = 'single' inline-quotes = 'single' multiline-quotes = 'single' [format] line-ending = 'lf' indent-style = 'space' quote-style = 'single' docstring-code-format = true skip-magic-trailing-comma = false exclude = [ '__init__.py', ] [lint.pycodestyle] max-line-length = 79 [lint.flake8-pytest-style] mark-parentheses = true python-progressbar-4.5.0/tests/000077500000000000000000000000001466372462000165445ustar00rootroot00000000000000python-progressbar-4.5.0/tests/conftest.py000066400000000000000000000023631466372462000207470ustar00rootroot00000000000000from __future__ import annotations import logging import time import timeit from datetime import datetime import freezegun import pytest import progressbar LOG_LEVELS: dict[str, int] = { '0': logging.ERROR, '1': logging.WARNING, '2': logging.INFO, '3': logging.DEBUG, } def pytest_configure(config) -> None: logging.basicConfig( level=LOG_LEVELS.get(config.option.verbose, logging.DEBUG), ) @pytest.fixture(autouse=True) def small_interval(monkeypatch) -> None: # Remove the update limit for tests by default monkeypatch.setattr( progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1e-6, ) monkeypatch.setattr(timeit, 'default_timer', time.time) @pytest.fixture(autouse=True) def sleep_faster(monkeypatch): # The timezone offset in seconds, add 10 seconds to make sure we don't # accidentally get the wrong hour offset_seconds = (datetime.now() - datetime.utcnow()).seconds + 10 offset_hours = int(offset_seconds / 3600) freeze_time = freezegun.freeze_time(tz_offset=offset_hours) with freeze_time as fake_time: monkeypatch.setattr('time.sleep', fake_time.tick) monkeypatch.setattr('timeit.default_timer', time.time) yield freeze_time python-progressbar-4.5.0/tests/original_examples.py000066400000000000000000000154431466372462000226270ustar00rootroot00000000000000#!/usr/bin/python import sys import time from progressbar import ( ETA, AdaptiveETA, AnimatedMarker, Bar, BouncingBar, Counter, FileTransferSpeed, FormatLabel, Percentage, ProgressBar, ReverseBar, RotatingMarker, SimpleProgress, Timer, UnknownLength, ) examples = [] def example(fn): try: name = 'Example %d' % int(fn.__name__[7:]) except Exception: name = fn.__name__ def wrapped(): try: sys.stdout.write(f'Running: {name}\n') fn() sys.stdout.write('\n') except KeyboardInterrupt: sys.stdout.write('\nSkipping example.\n\n') examples.append(wrapped) return wrapped @example def example0() -> None: pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=300).start() for i in range(300): time.sleep(0.01) pbar.update(i + 1) pbar.finish() @example def example1() -> None: widgets = [ 'Test: ', Percentage(), ' ', Bar(marker=RotatingMarker()), ' ', ETA(), ' ', FileTransferSpeed(), ] pbar = ProgressBar(widgets=widgets, maxval=10000).start() for i in range(1000): # do something pbar.update(10 * i + 1) pbar.finish() @example def example2() -> None: 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) else: return FileTransferSpeed.update(self, pbar) widgets = [ CrazyFileTransferSpeed(), ' <<<', Bar(), '>>> ', Percentage(), ' ', ETA(), ] pbar = ProgressBar(widgets=widgets, maxval=10000) # maybe do something pbar.start() for i in range(2000): # do something pbar.update(5 * i + 1) pbar.finish() @example def example3() -> None: widgets = [Bar('>'), ' ', ETA(), ' ', ReverseBar('<')] pbar = ProgressBar(widgets=widgets, maxval=10000).start() for i in range(1000): # do something pbar.update(10 * i + 1) pbar.finish() @example def example4() -> None: widgets = [ 'Test: ', Percentage(), ' ', Bar(marker='0', left='[', right=']'), ' ', ETA(), ' ', FileTransferSpeed(), ] pbar = ProgressBar(widgets=widgets, maxval=500) pbar.start() for i in range(100, 500 + 1, 50): time.sleep(0.2) pbar.update(i) pbar.finish() @example def example5() -> None: pbar = ProgressBar(widgets=[SimpleProgress()], maxval=17).start() for i in range(17): time.sleep(0.2) pbar.update(i + 1) pbar.finish() @example def example6() -> None: pbar = ProgressBar().start() for i in range(100): time.sleep(0.01) pbar.update(i + 1) pbar.finish() @example def example7() -> None: pbar = ProgressBar() # Progressbar can guess maxval automatically. for _i in pbar(range(80)): time.sleep(0.01) @example def example8() -> None: pbar = ProgressBar(maxval=80) # Progressbar can't guess maxval. for _i in pbar(i for i in range(80)): time.sleep(0.01) @example def example9() -> None: pbar = ProgressBar(widgets=['Working: ', AnimatedMarker()]) for _i in pbar(i for i in range(50)): time.sleep(0.08) @example def example10() -> None: widgets = ['Processed: ', Counter(), ' lines (', Timer(), ')'] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(150)): time.sleep(0.1) @example def example11() -> None: widgets = [FormatLabel('Processed: %(value)d lines (in: %(elapsed)s)')] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(150)): time.sleep(0.1) @example def example12() -> None: widgets = ['Balloon: ', AnimatedMarker(markers='.oO@* ')] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(24)): time.sleep(0.3) @example def example13() -> None: # You may need python 3.x to see this correctly try: widgets = ['Arrows: ', AnimatedMarker(markers='←↖↑↗→↘↓↙')] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(24)): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @example def example14() -> None: # You may need python 3.x to see this correctly try: widgets = ['Arrows: ', AnimatedMarker(markers='◢◣◤◥')] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(24)): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @example def example15() -> None: # You may need python 3.x to see this correctly try: widgets = ['Wheels: ', AnimatedMarker(markers='◐◓◑◒')] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(24)): time.sleep(0.3) except UnicodeError: sys.stdout.write('Unicode error: skipping example') @example def example16() -> None: widgets = [FormatLabel('Bouncer: value %(value)d - '), BouncingBar()] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(180)): time.sleep(0.05) @example def example17() -> None: widgets = [ FormatLabel('Animated Bouncer: value %(value)d - '), BouncingBar(marker=RotatingMarker()), ] pbar = ProgressBar(widgets=widgets) for _i in pbar(i for i in range(180)): time.sleep(0.05) @example def example18() -> None: widgets = [Percentage(), ' ', Bar(), ' ', ETA(), ' ', AdaptiveETA()] pbar = ProgressBar(widgets=widgets, maxval=500) pbar.start() for i in range(500): time.sleep(0.01 + (i < 100) * 0.01 + (i > 400) * 0.9) pbar.update(i + 1) pbar.finish() @example def example19() -> None: pbar = ProgressBar() for _i in pbar([]): pass pbar.finish() @example def example20() -> None: """Widgets that behave differently when length is unknown""" widgets = [ '[When length is unknown at first]', ' Progress: ', SimpleProgress(), ', Percent: ', Percentage(), ' ', ETA(), ' ', AdaptiveETA(), ] pbar = ProgressBar(widgets=widgets, maxval=UnknownLength) pbar.start() for i in range(20): time.sleep(0.5) if i == 10: pbar.maxval = 20 pbar.update(i + 1) pbar.finish() if __name__ == '__main__': try: for example in examples: example() except KeyboardInterrupt: sys.stdout.write('\nQuitting examples.\n') python-progressbar-4.5.0/tests/requirements.txt000066400000000000000000000000131466372462000220220ustar00rootroot00000000000000-e.[tests] python-progressbar-4.5.0/tests/test_algorithms.py000066400000000000000000000025511466372462000223310ustar00rootroot00000000000000from datetime import timedelta import pytest from progressbar import algorithms def test_ema_initialization() -> None: ema = algorithms.ExponentialMovingAverage() assert ema.alpha == 0.5 assert ema.value == 0 @pytest.mark.parametrize( 'alpha, new_value, expected', [ (0.5, 10, 5), (0.1, 20, 2), (0.9, 30, 27), (0.3, 15, 4.5), (0.7, 40, 28), (0.5, 0, 0), (0.2, 100, 20), (0.8, 50, 40), ], ) def test_ema_update(alpha, new_value: float, expected) -> None: ema = algorithms.ExponentialMovingAverage(alpha) result = ema.update(new_value, timedelta(seconds=1)) assert result == expected def test_dema_initialization() -> None: dema = algorithms.DoubleExponentialMovingAverage() assert dema.alpha == 0.5 assert dema.ema1 == 0 assert dema.ema2 == 0 @pytest.mark.parametrize( 'alpha, new_value, expected', [ (0.5, 10, 7.5), (0.1, 20, 3.8), (0.9, 30, 29.7), (0.3, 15, 7.65), (0.5, 0, 0), (0.2, 100, 36.0), (0.8, 50, 48.0), ], ) def test_dema_update(alpha, new_value: float, expected) -> None: dema = algorithms.DoubleExponentialMovingAverage(alpha) result = dema.update(new_value, timedelta(seconds=1)) assert result == expected # Additional test functions can be added here as needed. python-progressbar-4.5.0/tests/test_backwards_compatibility.py000066400000000000000000000006011466372462000250440ustar00rootroot00000000000000import time import progressbar def test_progressbar_1_widgets() -> None: widgets = [ progressbar.AdaptiveETA(format='Time left: %s'), progressbar.Timer(format='Time passed: %s'), progressbar.Bar(), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=100).start() for i in range(1, 101): bar.update(i) time.sleep(0.1) python-progressbar-4.5.0/tests/test_color.py000066400000000000000000000252301466372462000212750ustar00rootroot00000000000000from __future__ import annotations import os from typing import ClassVar import pytest import progressbar import progressbar.terminal from progressbar import env, terminal, widgets from progressbar.terminal import Colors, apply_colors, colors ENVIRONMENT_VARIABLES = [ 'PROGRESSBAR_ENABLE_COLORS', 'FORCE_COLOR', 'COLORTERM', 'TERM', 'JUPYTER_COLUMNS', 'JUPYTER_LINES', 'JPY_PARENT_PID', ] @pytest.fixture(autouse=True) def clear_env(monkeypatch: pytest.MonkeyPatch) -> None: # Clear all environment variables that might affect the tests for variable in ENVIRONMENT_VARIABLES: monkeypatch.delenv(variable, raising=False) monkeypatch.setattr(env, 'JUPYTER', False) @pytest.mark.parametrize( 'variable', [ 'PROGRESSBAR_ENABLE_COLORS', 'FORCE_COLOR', ], ) def test_color_environment_variables( monkeypatch: pytest.MonkeyPatch, variable: str, ) -> None: if os.name == 'nt': # Windows has special handling so we need to disable that to make the # tests work properly monkeypatch.setattr(os, 'name', 'posix') monkeypatch.setattr( env, 'COLOR_SUPPORT', env.ColorSupport.XTERM_256, ) monkeypatch.setenv(variable, 'true') bar = progressbar.ProgressBar() assert not env.is_ansi_terminal(bar.fd) assert not bar.is_ansi_terminal assert bar.enable_colors monkeypatch.setenv(variable, 'false') bar = progressbar.ProgressBar() assert not bar.enable_colors monkeypatch.setenv(variable, '') bar = progressbar.ProgressBar() assert not bar.enable_colors @pytest.mark.parametrize( 'variable', [ 'FORCE_COLOR', 'PROGRESSBAR_ENABLE_COLORS', 'COLORTERM', 'TERM', ], ) @pytest.mark.parametrize( 'value', [ '', 'truecolor', '24bit', '256', 'xterm-256', 'xterm', ], ) def test_color_support_from_env(monkeypatch, variable, value) -> None: if os.name == 'nt': # Windows has special handling so we need to disable that to make the # tests work properly monkeypatch.setattr(os, 'name', 'posix') monkeypatch.setenv(variable, value) env.ColorSupport.from_env() @pytest.mark.parametrize( 'variable', [ 'JUPYTER_COLUMNS', 'JUPYTER_LINES', ], ) def test_color_support_from_env_jupyter(monkeypatch, variable) -> None: monkeypatch.setattr(env, 'JUPYTER', True) assert env.ColorSupport.from_env() == env.ColorSupport.XTERM_TRUECOLOR # Sanity check monkeypatch.setattr(env, 'JUPYTER', False) if os.name == 'nt': assert env.ColorSupport.from_env() == env.ColorSupport.WINDOWS else: assert env.ColorSupport.from_env() == env.ColorSupport.NONE def test_enable_colors_flags() -> None: 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=env.ColorSupport.XTERM_TRUECOLOR, ) assert bar.enable_colors with pytest.raises(ValueError): progressbar.ProgressBar(enable_colors=12345) class _TestFixedColorSupport(progressbar.widgets.WidgetBase): _fixed_colors: ClassVar[widgets.TFixedColors] = widgets.TFixedColors( fg_none=progressbar.widgets.colors.yellow, bg_none=None, ) def __call__(self, *args, **kwargs) -> None: pass class _TestFixedGradientSupport(progressbar.widgets.WidgetBase): _gradient_colors: ClassVar[widgets.TGradientColors] = ( widgets.TGradientColors( fg=progressbar.widgets.colors.gradient, bg=None, ) ) def __call__(self, *args, **kwargs) -> None: pass @pytest.mark.parametrize( 'widget', [ progressbar.Percentage, progressbar.SimpleProgress, _TestFixedColorSupport, _TestFixedGradientSupport, ], ) def test_color_widgets(widget) -> None: assert widget().uses_colors print(f'{widget} has colors? {widget.uses_colors}') def test_color_gradient() -> None: gradient = terminal.ColorGradient(colors.red) assert gradient.get_color(0) == gradient.get_color(-1) assert gradient.get_color(1) == gradient.get_color(2) assert gradient.get_color(0.5) == colors.red gradient = terminal.ColorGradient(colors.red, colors.yellow) assert gradient.get_color(0) == colors.red assert gradient.get_color(1) == colors.yellow assert gradient.get_color(0.5) != colors.red assert gradient.get_color(0.5) != colors.yellow gradient = terminal.ColorGradient( colors.red, colors.yellow, interpolate=False, ) assert gradient.get_color(0) == colors.red assert gradient.get_color(1) == colors.yellow assert gradient.get_color(0.5) == colors.red @pytest.mark.parametrize( 'widget', [ progressbar.Counter, ], ) def test_no_color_widgets(widget) -> None: assert not widget().uses_colors print(f'{widget} has colors? {widget.uses_colors}') assert widget( fixed_colors=_TestFixedColorSupport._fixed_colors, ).uses_colors assert widget( gradient_colors=_TestFixedGradientSupport._gradient_colors, ).uses_colors def test_colors(monkeypatch) -> None: for colors_ in Colors.by_rgb.values(): for color in colors_: rgb = color.rgb assert rgb.rgb assert rgb.hex assert rgb.to_ansi_16 is not None assert rgb.to_ansi_256 is not None assert rgb.to_windows is not None with monkeypatch.context() as context: context.setattr(env, 'COLOR_SUPPORT', env.ColorSupport.XTERM) assert color.underline context.setattr(env, 'COLOR_SUPPORT', env.ColorSupport.WINDOWS) assert color.underline assert color.fg assert color.bg assert str(color) assert str(rgb) assert color('test') def test_color() -> None: color = colors.red if os.name != 'nt': assert color('x') == color.fg('x') != 'x' assert color.fg('x') != color.bg('x') != 'x' assert color.fg('x') != color.underline('x') != 'x' # Color hashes are based on the RGB value assert hash(color) == hash(terminal.Color(color.rgb, None, None, None)) Colors.register(color.rgb) @pytest.mark.parametrize( 'rgb,hls', [ (terminal.RGB(0, 0, 0), terminal.HSL(0, 0, 0)), (terminal.RGB(255, 255, 255), terminal.HSL(0, 0, 100)), (terminal.RGB(255, 0, 0), terminal.HSL(0, 100, 50)), (terminal.RGB(0, 255, 0), terminal.HSL(120, 100, 50)), (terminal.RGB(0, 0, 255), terminal.HSL(240, 100, 50)), (terminal.RGB(255, 255, 0), terminal.HSL(60, 100, 50)), (terminal.RGB(0, 255, 255), terminal.HSL(180, 100, 50)), (terminal.RGB(255, 0, 255), terminal.HSL(300, 100, 50)), (terminal.RGB(128, 128, 128), terminal.HSL(0, 0, 50)), (terminal.RGB(128, 0, 0), terminal.HSL(0, 100, 25)), (terminal.RGB(128, 128, 0), terminal.HSL(60, 100, 25)), (terminal.RGB(0, 128, 0), terminal.HSL(120, 100, 25)), (terminal.RGB(128, 0, 128), terminal.HSL(300, 100, 25)), (terminal.RGB(0, 128, 128), terminal.HSL(180, 100, 25)), (terminal.RGB(0, 0, 128), terminal.HSL(240, 100, 25)), (terminal.RGB(192, 192, 192), terminal.HSL(0, 0, 75)), ], ) def test_rgb_to_hls(rgb, hls) -> None: assert terminal.HSL.from_rgb(rgb) == hls @pytest.mark.parametrize( 'text, fg, bg, fg_none, bg_none, percentage, expected', [ ('test', None, None, None, None, None, 'test'), ('test', None, None, None, None, 1, 'test'), ( 'test', None, None, None, colors.red, None, '\x1b[48;5;9mtest\x1b[49m', ), ( 'test', None, colors.green, None, colors.red, None, '\x1b[48;5;9mtest\x1b[49m', ), ('test', None, colors.red, None, None, 1, '\x1b[48;5;9mtest\x1b[49m'), ('test', None, colors.red, None, None, None, 'test'), ( 'test', colors.green, None, colors.red, None, None, '\x1b[38;5;9mtest\x1b[39m', ), ( 'test', colors.green, colors.red, None, None, 1, '\x1b[48;5;9m\x1b[38;5;2mtest\x1b[39m\x1b[49m', ), ('test', colors.red, None, None, None, 1, '\x1b[38;5;9mtest\x1b[39m'), ('test', colors.red, None, None, None, None, 'test'), ('test', colors.red, colors.red, None, None, None, 'test'), ( 'test', colors.red, colors.yellow, None, None, 1, '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', ), ( 'test', colors.red, colors.yellow, None, None, 1, '\x1b[48;5;11m\x1b[38;5;9mtest\x1b[39m\x1b[49m', ), ], ) def test_apply_colors( text: str, fg, bg, fg_none, bg_none, percentage: float | None, expected, monkeypatch, ) -> None: monkeypatch.setattr( env, 'COLOR_SUPPORT', env.ColorSupport.XTERM_256, ) assert ( apply_colors( text, fg=fg, bg=bg, fg_none=fg_none, bg_none=bg_none, percentage=percentage, ) == expected ) def test_windows_colors(monkeypatch) -> None: monkeypatch.setattr(env, 'COLOR_SUPPORT', env.ColorSupport.WINDOWS) assert ( apply_colors( 'test', fg=colors.red, bg=colors.red, fg_none=colors.red, bg_none=colors.red, percentage=1, ) == 'test' ) colors.red.underline('test') def test_ansi_color(monkeypatch) -> None: color = progressbar.terminal.Color( colors.red.rgb, colors.red.hls, 'red-ansi', None, ) for color_support in { env.ColorSupport.NONE, env.ColorSupport.XTERM, env.ColorSupport.XTERM_256, env.ColorSupport.XTERM_TRUECOLOR, }: monkeypatch.setattr( env, 'COLOR_SUPPORT', color_support, ) assert color.ansi is not None or color_support == env.ColorSupport.NONE def test_sgr_call() -> None: assert progressbar.terminal.encircled('test') == '\x1b[52mtest\x1b[54m' python-progressbar-4.5.0/tests/test_custom_widgets.py000066400000000000000000000043471466372462000232250ustar00rootroot00000000000000import time import pytest import progressbar class CrazyFileTransferSpeed(progressbar.FileTransferSpeed): "It's bigger between 45 and 80 percent" def update(self, pbar): if 45 < pbar.percentage() < 80: value = progressbar.FileTransferSpeed.update(self, pbar) return f'Bigger Now {value}' else: return progressbar.FileTransferSpeed.update(self, pbar) def test_crazy_file_transfer_speed_widget() -> None: widgets = [ # CrazyFileTransferSpeed(), ' <<<', progressbar.Bar(), '>>> ', progressbar.Percentage(), ' ', progressbar.ETA(), ] p = progressbar.ProgressBar(widgets=widgets, max_value=1000) # maybe do something p.start() for i in range(0, 200, 5): # do something time.sleep(0.1) p.update(i + 1) p.finish() def test_variable_widget_widget() -> None: widgets = [ ' [', progressbar.Timer(), '] ', progressbar.Bar(), ' (', progressbar.ETA(), ') ', progressbar.Variable('loss'), progressbar.Variable('text'), progressbar.Variable('error', precision=None), progressbar.Variable('missing'), progressbar.Variable('predefined'), ] p = progressbar.ProgressBar( widgets=widgets, max_value=1000, variables=dict(predefined='predefined'), ) p.start() print('time', time, time.sleep) for i in range(0, 200, 5): time.sleep(0.1) p.update(i + 1, loss=0.5, text='spam', error=1) i += 1 p.update(i, text=None) i += 1 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() def test_format_custom_text_widget() -> None: widget = progressbar.FormatCustomText( 'Spam: %(spam).1f kg, eggs: %(eggs)d', dict( spam=0.25, eggs=3, ), ) bar = progressbar.ProgressBar( widgets=[ widget, ], ) for i in bar(range(5)): widget.update_mapping(eggs=i * 2) assert widget.mapping['eggs'] == bar.widgets[0].mapping['eggs'] python-progressbar-4.5.0/tests/test_data.py000066400000000000000000000011221466372462000210620ustar00rootroot00000000000000import pytest import progressbar @pytest.mark.parametrize( 'value,expected', [ (None, ' 0.0 B'), (1, ' 1.0 B'), (2**10 - 1, '1023.0 B'), (2**10 + 0, ' 1.0 KiB'), (2**20, ' 1.0 MiB'), (2**30, ' 1.0 GiB'), (2**40, ' 1.0 TiB'), (2**50, ' 1.0 PiB'), (2**60, ' 1.0 EiB'), (2**70, ' 1.0 ZiB'), (2**80, ' 1.0 YiB'), (2**90, '1024.0 YiB'), ], ) def test_data_size(value, expected) -> None: widget = progressbar.DataSize() assert widget(None, dict(value=value)) == expected python-progressbar-4.5.0/tests/test_data_transfer_bar.py000066400000000000000000000005701466372462000236200ustar00rootroot00000000000000import progressbar from progressbar import DataTransferBar def test_known_length() -> None: dtb = DataTransferBar().start(max_value=50) for i in range(50): dtb.update(i) dtb.finish() def test_unknown_length() -> None: dtb = DataTransferBar().start(max_value=progressbar.UnknownLength) for i in range(50): dtb.update(i) dtb.finish() python-progressbar-4.5.0/tests/test_dill_pickle.py000066400000000000000000000005441466372462000224330ustar00rootroot00000000000000import dill # type: ignore import progressbar def test_dill() -> None: bar = progressbar.ProgressBar() assert bar._started is False assert bar._finished is False assert dill.pickles(bar) is False assert bar._started is False # Should be false because it never should have started/initialized assert bar._finished is False python-progressbar-4.5.0/tests/test_empty.py000066400000000000000000000003441466372462000213140ustar00rootroot00000000000000import progressbar def test_empty_list() -> None: for x in progressbar.ProgressBar()([]): print(x) def test_empty_iterator() -> None: for x in progressbar.ProgressBar(max_value=0)(iter([])): print(x) python-progressbar-4.5.0/tests/test_end.py000066400000000000000000000021231466372462000207210ustar00rootroot00000000000000import pytest import progressbar @pytest.fixture(autouse=True) def large_interval(monkeypatch) -> None: # Remove the update limit for tests by default monkeypatch.setattr( progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 0.1, ) def test_end() -> None: m = 24514315 p = progressbar.ProgressBar( widgets=[progressbar.Percentage(), progressbar.Bar()], max_value=m, ) for x in range(0, m, 8192): p.update(x) data = p.data() assert data['percentage'] < 100.0 p.finish() data = p.data() assert data['percentage'] >= 100.0 assert p.value == m def test_end_100(monkeypatch) -> None: assert progressbar.ProgressBar._MINIMUM_UPDATE_INTERVAL == 0.1 p = progressbar.ProgressBar( widgets=[progressbar.Percentage(), progressbar.Bar()], max_value=103, ) for x in range(102): p.update(x) data = p.data() import pprint pprint.pprint(data) assert data['percentage'] < 100.0 p.finish() data = p.data() assert data['percentage'] >= 100.0 python-progressbar-4.5.0/tests/test_failure.py000066400000000000000000000064301466372462000216070ustar00rootroot00000000000000import logging import time import pytest import progressbar def test_missing_format_values(caplog) -> None: caplog.set_level(logging.CRITICAL, logger='progressbar.widgets') with pytest.raises(KeyError): p = progressbar.ProgressBar( widgets=[progressbar.widgets.FormatLabel('%(x)s')], ) p.update(5) def test_max_smaller_than_min() -> None: with pytest.raises(ValueError): progressbar.ProgressBar(min_value=10, max_value=5) def test_no_max_value() -> None: """Looping up to 5 without max_value? No problem""" p = progressbar.ProgressBar() p.start() for i in range(5): time.sleep(1) p.update(i) def test_correct_max_value() -> None: """Looping up to 5 when max_value is 10? No problem""" p = progressbar.ProgressBar(max_value=10) for i in range(5): time.sleep(1) p.update(i) def test_minus_max_value() -> None: """negative max_value, shouldn't work""" p = progressbar.ProgressBar(min_value=-2, max_value=-1) with pytest.raises(ValueError): p.update(-1) def test_zero_max_value() -> None: """max_value of zero, it could happen""" p = progressbar.ProgressBar(max_value=0) p.update(0) with pytest.raises(ValueError): p.update(1) def test_one_max_value() -> None: """max_value of one, another corner case""" p = progressbar.ProgressBar(max_value=1) p.update(0) p.update(0) p.update(1) with pytest.raises(ValueError): p.update(2) def test_changing_max_value() -> None: """Changing max_value? No problem""" p = progressbar.ProgressBar(max_value=10)(range(20), max_value=20) for _i in p: time.sleep(1) def test_backwards() -> None: """progressbar going backwards""" p = progressbar.ProgressBar(max_value=1) p.update(1) p.update(0) def test_incorrect_max_value() -> None: """Looping up to 10 when max_value is 5? This is madness!""" p = progressbar.ProgressBar(max_value=5) for i in range(5): time.sleep(1) p.update(i) with pytest.raises(ValueError): for i in range(5, 10): time.sleep(1) p.update(i) def test_deprecated_maxval() -> None: with pytest.warns(DeprecationWarning): progressbar.ProgressBar(maxval=5) def test_deprecated_poll() -> None: with pytest.warns(DeprecationWarning): progressbar.ProgressBar(poll=5) def test_deprecated_currval() -> None: with pytest.warns(DeprecationWarning): bar = progressbar.ProgressBar(max_value=5) bar.update(2) assert bar.currval == 2 def test_unexpected_update_keyword_arg() -> None: p = progressbar.ProgressBar(max_value=10) with pytest.raises(TypeError): for i in range(10): time.sleep(1) p.update(i, foo=10) def test_variable_not_str() -> None: with pytest.raises(TypeError): progressbar.Variable(1) def test_variable_too_many_strs() -> None: with pytest.raises(ValueError): progressbar.Variable('too long') def test_negative_value() -> None: bar = progressbar.ProgressBar(max_value=10) with pytest.raises(ValueError): bar.update(value=-1) def test_increment() -> None: bar = progressbar.ProgressBar(max_value=10) bar.increment() del bar python-progressbar-4.5.0/tests/test_flush.py000066400000000000000000000006231466372462000212770ustar00rootroot00000000000000import time import progressbar def test_flush() -> None: """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) p.update(i) print('need update?', p._needs_update()) if i > 5: time.sleep(0.1) print('post-updates', p.updates) python-progressbar-4.5.0/tests/test_iterators.py000066400000000000000000000027761466372462000222050ustar00rootroot00000000000000import time import pytest import progressbar def test_list() -> None: """Progressbar can guess max_value automatically.""" p = progressbar.ProgressBar() for _i in p(range(10)): time.sleep(0.001) def test_iterator_with_max_value() -> None: """Progressbar can't guess max_value.""" p = progressbar.ProgressBar(max_value=10) for _i in p(iter(range(10))): time.sleep(0.001) def test_iterator_without_max_value_error() -> None: """Progressbar can't guess max_value.""" p = progressbar.ProgressBar() for _i in p(iter(range(10))): time.sleep(0.001) assert p.max_value is progressbar.UnknownLength def test_iterator_without_max_value() -> None: """Progressbar can't guess max_value.""" p = progressbar.ProgressBar( widgets=[ progressbar.AnimatedMarker(), progressbar.FormatLabel('%(value)d'), progressbar.BouncingBar(), progressbar.BouncingBar(marker=progressbar.RotatingMarker()), ], ) for _i in p(iter(range(10))): time.sleep(0.001) def test_iterator_with_incorrect_max_value() -> None: """Progressbar can't guess max_value.""" p = progressbar.ProgressBar(max_value=10) with pytest.raises(ValueError): for _i in p(iter(range(20))): time.sleep(0.001) def test_adding_value() -> None: p = progressbar.ProgressBar(max_value=10) p.start() p.update(5) p += 2 p.increment(2) with pytest.raises(ValueError): p += 5 python-progressbar-4.5.0/tests/test_job_status.py000066400000000000000000000006161466372462000223350ustar00rootroot00000000000000import time import pytest import progressbar @pytest.mark.parametrize( 'status', [ True, False, None, ], ) def test_status(status) -> None: with progressbar.ProgressBar( widgets=[progressbar.widgets.JobStatusBar('status')], ) as bar: for _ in range(5): bar.increment(status=status, force=True) time.sleep(0.1) python-progressbar-4.5.0/tests/test_large_values.py000066400000000000000000000006311466372462000226260ustar00rootroot00000000000000import time import progressbar def test_large_max_value() -> None: with progressbar.ProgressBar(max_value=1e10) as bar: for i in range(10): bar.update(i) time.sleep(0.1) def test_value_beyond_max_value() -> None: with progressbar.ProgressBar(max_value=10, max_error=False) as bar: for i in range(20): bar.update(i) time.sleep(0.01) python-progressbar-4.5.0/tests/test_misc.py000066400000000000000000000005511466372462000211110ustar00rootroot00000000000000from progressbar import __about__ def test_about() -> None: assert __about__.__title__ assert __about__.__package_name__ assert __about__.__author__ assert __about__.__description__ assert __about__.__email__ assert __about__.__version__ assert __about__.__license__ assert __about__.__copyright__ assert __about__.__url__ python-progressbar-4.5.0/tests/test_monitor_progress.py000066400000000000000000000245671466372462000236060ustar00rootroot00000000000000from __future__ import annotations # fmt: off import os import pprint import progressbar pytest_plugins = 'pytester' SCRIPT = """ import sys sys.path.append({progressbar_path!r}) import time import timeit import freezegun import progressbar with freezegun.freeze_time() as fake_time: timeit.default_timer = time.time with progressbar.ProgressBar(widgets={widgets}, **{kwargs!r}) as bar: bar._MINIMUM_UPDATE_INTERVAL = 1e-9 for i in bar({items}): {loop_code} """ def _non_empty_lines(lines): return [line for line in lines if line.strip()] def _create_script( widgets=None, items: list[int] | None=None, loop_code: str='fake_time.tick(1)', term_width: int=60, **kwargs, ) -> str: if items is None: items = list(range(9)) kwargs['term_width'] = term_width # Reindent the loop code indent = '\n ' loop_code = loop_code.strip('\n').split('\n') dedent = len(loop_code[0]) - len(loop_code[0].lstrip()) for i, line in enumerate(loop_code): loop_code[i] = line[dedent:] script = SCRIPT.format( items=items, widgets=widgets, kwargs=kwargs, loop_code=indent.join(loop_code), progressbar_path=os.path.dirname( os.path.dirname(progressbar.__file__), ), ) print('# Script:') print('#' * 78) print(script) print('#' * 78) return script def test_list_example(testdir) -> None: """Run the simple example code in a python subprocess and then compare its stderr to what we expect to see from it. We run it in a subprocess to best capture its stderr. We expect to see match_lines in order in the output. This test is just a sanity check to ensure that the progress bar progresses from 1 to 10, it does not make sure that the""" result = testdir.runpython( testdir.makepyfile( _create_script( term_width=65, ), ), ) result.stderr.lines = [ line.rstrip() for line in _non_empty_lines(result.stderr.lines) ] pprint.pprint(result.stderr.lines, width=70) result.stderr.fnmatch_lines([ ' 0% (0 of 9) | | Elapsed Time: ?:00:00 ETA: --:--:--', ' 11% (1 of 9) |# | Elapsed Time: ?:00:01 ETA: ?:00:08', ' 22% (2 of 9) |## | Elapsed Time: ?:00:02 ETA: ?:00:07', ' 33% (3 of 9) |#### | Elapsed Time: ?:00:03 ETA: ?:00:06', ' 44% (4 of 9) |##### | Elapsed Time: ?:00:04 ETA: ?:00:05', ' 55% (5 of 9) |###### | Elapsed Time: ?:00:05 ETA: ?:00:04', ' 66% (6 of 9) |######## | Elapsed Time: ?:00:06 ETA: ?:00:03', ' 77% (7 of 9) |######### | Elapsed Time: ?:00:07 ETA: ?:00:02', ' 88% (8 of 9) |########## | Elapsed Time: ?:00:08 ETA: ?:00:01', '100% (9 of 9) |############| Elapsed Time: ?:00:09 Time: ?:00:09', ]) def test_generator_example(testdir) -> None: """Run the simple example code in a python subprocess and then compare its stderr to what we expect to see from it. We run it in a subprocess to best capture its stderr. We expect to see match_lines in order in the output. This test is just a sanity check to ensure that the progress bar progresses from 1 to 10, it does not make sure that the""" result = testdir.runpython( testdir.makepyfile( _create_script( items='iter(range(9))', ), ), ) result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) 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) def test_rapid_updates(testdir) -> None: """Run some example code that updates 10 times, then sleeps .1 seconds, this is meant to test that the progressbar progresses normally with this sample code, since there were issues with it in the past""" result = testdir.runpython( testdir.makepyfile( _create_script( term_width=60, items=list(range(10)), loop_code=""" if i < 5: fake_time.tick(1) else: fake_time.tick(2) """, ), ), ) result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) result.stderr.fnmatch_lines( [ ' 0% (0 of 10) | | Elapsed Time: 0:00:00 ETA: --:--:--', ' 10% (1 of 10) | | Elapsed Time: 0:00:01 ETA: 0:00:09', ' 20% (2 of 10) |# | Elapsed Time: 0:00:02 ETA: 0:00:08', ' 30% (3 of 10) |# | Elapsed Time: 0:00:03 ETA: 0:00:07', ' 40% (4 of 10) |## | Elapsed Time: 0:00:04 ETA: 0:00:06', ' 50% (5 of 10) |### | Elapsed Time: 0:00:05 ETA: 0:00:05', ' 60% (6 of 10) |### | Elapsed Time: 0:00:07 ETA: 0:00:04', ' 70% (7 of 10) |#### | Elapsed Time: 0:00:09 ETA: 0:00:03', ' 80% (8 of 10) |#### | Elapsed Time: 0:00:11 ETA: 0:00:02', ' 90% (9 of 10) |##### | Elapsed Time: 0:00:13 ETA: 0:00:01', '100% (10 of 10) |#####| Elapsed Time: 0:00:15 Time: 0:00:15', ], ) def test_non_timed(testdir) -> None: result = testdir.runpython( testdir.makepyfile( _create_script( widgets='[progressbar.Percentage(), progressbar.Bar()]', items=list(range(5)), ), ), ) result.stderr.lines = _non_empty_lines(result.stderr.lines) pprint.pprint(result.stderr.lines, width=70) result.stderr.fnmatch_lines( [ ' 0%| |', ' 20%|########## |', ' 40%|##################### |', ' 60%|################################ |', ' 80%|########################################### |', '100%|######################################################|', ], ) def test_line_breaks(testdir) -> None: result = testdir.runpython( testdir.makepyfile( _create_script( widgets='[progressbar.Percentage(), progressbar.Bar()]', line_breaks=True, items=list(range(5)), ), ), ) pprint.pprint(result.stderr.str(), width=70) assert result.stderr.str() == '\n'.join( ( ' 0%| |', ' 20%|########## |', ' 40%|##################### |', ' 60%|################################ |', ' 80%|########################################### |', '100%|######################################################|', '100%|######################################################|', ), ) def test_no_line_breaks(testdir) -> None: result = testdir.runpython( testdir.makepyfile( _create_script( widgets='[progressbar.Percentage(), progressbar.Bar()]', line_breaks=False, items=list(range(5)), ), ), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ '', ' 0%| |', ' 20%|########## |', ' 40%|##################### |', ' 60%|################################ |', ' 80%|########################################### |', '100%|######################################################|', '', '100%|######################################################|', ] def test_percentage_label_bar(testdir) -> None: result = testdir.runpython( testdir.makepyfile( _create_script( widgets='[progressbar.PercentageLabelBar()]', line_breaks=False, items=list(range(5)), ), ), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ '', '| 0% |', '|########### 20% |', '|####################### 40% |', '|###########################60%#### |', '|###########################80%################ |', '|###########################100%###########################|', '', '|###########################100%###########################|', ] def test_granular_bar(testdir) -> None: result = testdir.runpython( testdir.makepyfile( _create_script( widgets='[progressbar.GranularBar(markers=" .oO")]', line_breaks=False, items=list(range(5)), ), ), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == [ '', '| |', '|OOOOOOOOOOO. |', '|OOOOOOOOOOOOOOOOOOOOOOO |', '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOo |', '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO. |', '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', '', '|OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO|', ] def test_colors(testdir) -> None: kwargs = dict( items=range(1), widgets=['\033[92mgreen\033[0m'], ) result = testdir.runpython( testdir.makepyfile(_create_script(enable_colors=True, **kwargs)), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == ['\x1b[92mgreen\x1b[0m'] * 3 result = testdir.runpython( testdir.makepyfile(_create_script(enable_colors=False, **kwargs)), ) pprint.pprint(result.stderr.lines, width=70) assert result.stderr.lines == ['green'] * 3 python-progressbar-4.5.0/tests/test_multibar.py000066400000000000000000000147621466372462000220060ustar00rootroot00000000000000import random import threading import time import pytest import progressbar N = 10 BARS = 3 SLEEP = 0.002 def test_multi_progress_bar_out_of_range() -> None: widgets = [ progressbar.MultiProgressBar('multivalues'), ] bar = progressbar.ProgressBar(widgets=widgets, max_value=10) with pytest.raises(ValueError): bar.update(multivalues=[123]) with pytest.raises(ValueError): bar.update(multivalues=[-1]) def test_multibar() -> None: multibar = progressbar.MultiBar( sort_keyfunc=lambda bar: bar.label, remove_finished=0.005, ) multibar.show_initial = False multibar.render(force=True) multibar.show_initial = True multibar.render(force=True) 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.prepend_label = False multibar.append_label = True append_bar = progressbar.ProgressBar(max_value=N) append_bar.start() multibar._label_bar(append_bar) multibar['append'] = append_bar multibar.render(force=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[f'bar {i}'],), ) thread.start() for bar in list(multibar.values()): for j in range(N): bar.update(j) time.sleep(SLEEP) multibar.render(force=True) multibar.remove_finished = False multibar.show_finished = False append_bar.finish() multibar.render(force=True) 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) -> None: with progressbar.MultiBar() as multibar: for i in range(BARS): label = f'bar {i}' multibar[label] = progressbar.ProgressBar(max_value=N) for bar in multibar.values(): for _j in bar(range(N)): assert bar.started() time.sleep(SLEEP) for bar in multibar.values(): assert bar.finished() def test_offset_bar() -> None: with progressbar.ProgressBar(line_offset=2) as bar: for i in range(N): bar.update(i) def test_multibar_show_finished() -> None: multibar = progressbar.MultiBar(show_finished=True) multibar['bar'] = progressbar.ProgressBar(max_value=N) multibar.render(force=True) with progressbar.MultiBar(show_finished=False) as multibar: multibar.finished_format = 'finished: {label}' for i in range(3): multibar[f'bar {i}'] = progressbar.ProgressBar(max_value=N) for bar in multibar.values(): for i in range(N): bar.update(i) time.sleep(SLEEP) multibar.render(force=True) def test_multibar_show_initial() -> None: multibar = progressbar.MultiBar(show_initial=False) multibar['bar'] = progressbar.ProgressBar(max_value=N) multibar.render(force=True) def test_multibar_empty_key() -> None: multibar = progressbar.MultiBar() multibar[''] = progressbar.ProgressBar(max_value=N) for name in multibar: assert name == '' bar = multibar[name] bar.update(1) multibar.render(force=True) def test_multibar_print() -> None: bars = 5 n = 10 def print_sometimes(bar, probability): 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() < probability: 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}'] bar.max_error = False # Create a thread and pass the progressbar # Print never, sometimes and always threading.Thread(target=print_sometimes, args=(bar, 0)).start() threading.Thread(target=print_sometimes, args=(bar, 0.5)).start() threading.Thread(target=print_sometimes, args=(bar, 1)).start() for i in range(5): multibar.print(f'{i}', flush=False) multibar.update(force=True, flush=False) multibar.update(force=True, flush=True) def test_multibar_no_format() -> None: with progressbar.MultiBar( initial_format=None, finished_format=None ) as multibar: bar = multibar['a'] for i in bar(range(5)): bar.print(i) def test_multibar_finished() -> None: multibar = progressbar.MultiBar(initial_format=None, finished_format=None) bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) bar2 = multibar['bar2'] multibar.render(force=True) multibar.print('Hi') multibar.render(force=True, flush=False) for i in range(6): bar.update(i) bar2.update(i) multibar.render(force=True) def test_multibar_finished_format() -> None: multibar = progressbar.MultiBar( finished_format='Finished {label}', show_finished=True ) bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) bar2 = multibar['bar2'] multibar.render(force=True) multibar.print('Hi') multibar.render(force=True, flush=False) bar.start() bar2.start() multibar.render(force=True) multibar.print('Hi') multibar.render(force=True, flush=False) for i in range(6): bar.update(i) bar2.update(i) multibar.render(force=True) def test_multibar_threads() -> None: multibar = progressbar.MultiBar(finished_format=None, show_finished=True) bar = multibar['bar'] = progressbar.ProgressBar(max_value=5) multibar.start() time.sleep(0.1) bar.update(3) time.sleep(0.1) multibar.join() bar.finish() multibar.join() multibar.render(force=True) python-progressbar-4.5.0/tests/test_progressbar.py000066400000000000000000000036011466372462000225060ustar00rootroot00000000000000import contextlib import os import time import original_examples # type: ignore import pytest import progressbar # Import hack to allow for parallel Tox try: import examples except ImportError: import sys _project_dir: str = os.path.dirname(os.path.dirname(__file__)) sys.path.append(_project_dir) import examples sys.path.remove(_project_dir) def test_examples(monkeypatch) -> None: for example in examples.examples: with contextlib.suppress(ValueError): example() @pytest.mark.filterwarnings('ignore:.*maxval.*:DeprecationWarning') @pytest.mark.parametrize('example', original_examples.examples) def test_original_examples(example, monkeypatch) -> None: monkeypatch.setattr(progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', 1) monkeypatch.setattr(time, 'sleep', lambda t: None) example() @pytest.mark.parametrize('example', examples.examples) def test_examples_nullbar(monkeypatch, example) -> None: # Patch progressbar to use null bar instead of regular progress bar monkeypatch.setattr(progressbar, 'ProgressBar', progressbar.NullBar) assert progressbar.ProgressBar._MINIMUM_UPDATE_INTERVAL < 0.0001 example() def test_reuse() -> None: bar = progressbar.ProgressBar() bar.start() for i in range(10): bar.update(i) bar.finish() bar.start(init=True) for i in range(10): bar.update(i) bar.finish() bar.start(init=False) for i in range(10): bar.update(i) bar.finish() def test_dirty() -> None: 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() def test_negative_maximum() -> None: with pytest.raises(ValueError), progressbar.ProgressBar( max_value=-1 ) as progress: progress.start() python-progressbar-4.5.0/tests/test_progressbar_command.py000066400000000000000000000067141466372462000242140ustar00rootroot00000000000000import io import pytest import progressbar.__main__ as main def test_size_to_bytes() -> None: assert main.size_to_bytes('1') == 1 assert main.size_to_bytes('1k') == 1024 assert main.size_to_bytes('1m') == 1048576 assert main.size_to_bytes('1g') == 1073741824 assert main.size_to_bytes('1p') == 1125899906842624 assert main.size_to_bytes('1024') == 1024 assert main.size_to_bytes('1024k') == 1048576 assert main.size_to_bytes('1024m') == 1073741824 assert main.size_to_bytes('1024g') == 1099511627776 assert main.size_to_bytes('1024p') == 1152921504606846976 def test_filename_to_bytes(tmp_path) -> None: file = tmp_path / 'test' file.write_text('test') assert main.size_to_bytes(f'@{file}') == 4 with pytest.raises(FileNotFoundError): main.size_to_bytes(f'@{tmp_path / "nonexistent"}') def test_create_argument_parser() -> None: parser = main.create_argument_parser() args = parser.parse_args( [ '-p', '-t', '-e', '-r', '-a', '-b', '-8', '-T', '-n', '-q', 'input', '-o', 'output', ] ) assert args.progress is True assert args.timer is True assert args.eta is True assert args.rate is True assert args.average_rate is True assert args.bytes is True assert args.bits is True assert args.buffer_percent is True assert args.last_written is None assert args.format is None assert args.numeric is True assert args.quiet is True assert args.input == ['input'] assert args.output == 'output' def test_main_binary(capsys) -> None: # Call the main function with different command line arguments main.main( [ '-p', '-t', '-e', '-r', '-a', '-b', '-8', '-T', '-n', '-q', __file__, ] ) captured = capsys.readouterr() assert 'test_main(capsys):' in captured.out def test_main_lines(capsys) -> None: # Call the main function with different command line arguments main.main( [ '-p', '-t', '-e', '-r', '-a', '-b', '-8', '-T', '-n', '-q', '-l', '-s', f'@{__file__}', __file__, ] ) captured = capsys.readouterr() assert 'test_main(capsys):' in captured.out class Input(io.StringIO): buffer: io.BytesIO @classmethod def create(cls, text: str) -> 'Input': instance = cls(text) instance.buffer = io.BytesIO(text.encode()) return instance def test_main_lines_output(monkeypatch, tmp_path) -> None: text = 'my input' monkeypatch.setattr('sys.stdin', Input.create(text)) output_filename = tmp_path / 'output' main.main(['-l', '-o', str(output_filename)]) assert output_filename.read_text() == text def test_main_bytes_output(monkeypatch, tmp_path) -> None: text = 'my input' monkeypatch.setattr('sys.stdin', Input.create(text)) output_filename = tmp_path / 'output' main.main(['-o', str(output_filename)]) assert output_filename.read_text() == f'{text}' def test_missing_input(tmp_path) -> None: with pytest.raises(SystemExit): main.main([str(tmp_path / 'output')]) python-progressbar-4.5.0/tests/test_samples.py000066400000000000000000000071321466372462000216240ustar00rootroot00000000000000import time from datetime import datetime, timedelta from python_utils.containers import SliceableDeque import progressbar from progressbar import widgets def test_numeric_samples() -> None: samples = 5 samples_widget = widgets.SamplesMixin(samples=samples) bar = progressbar.ProgressBar(widgets=[samples_widget]) # Force updates in all cases samples_widget.INTERVAL = timedelta(0) start = datetime(2000, 1, 1) bar.value = 1 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (None, None) for i in range(2, 6): bar.value = i bar.last_update_time = start + timedelta(seconds=i) assert samples_widget(bar, None, True) == (timedelta(0, i - 1), i - 1) bar.value = 8 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) bar.value = 10 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 7), 7) bar.value = 20 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 16), 16) assert samples_widget(bar, None)[1] == SliceableDeque( [4, 5, 8, 10, 20], ) def test_timedelta_samples() -> None: samples = timedelta(seconds=5) samples_widget = widgets.SamplesMixin(samples=samples) bar = progressbar.ProgressBar(widgets=[samples_widget]) # Force updates in all cases samples_widget.INTERVAL = timedelta(0) start = datetime(2000, 1, 1) bar.value = 1 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (None, None) for i in range(2, 6): time.sleep(1) bar.value = i bar.last_update_time = start + timedelta(seconds=i) assert samples_widget(bar, None, True) == (timedelta(0, i - 1), i - 1) bar.value = 8 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) bar.last_update_time = start + timedelta(seconds=bar.value) bar.value = 8 assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) bar.value = 10 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 6), 6) bar.value = 20 bar.last_update_time = start + timedelta(seconds=bar.value) assert samples_widget(bar, None, True) == (timedelta(0, 10), 10) assert samples_widget(bar, None)[1] == [10, 20] def test_timedelta_no_update() -> None: samples = timedelta(seconds=0.1) samples_widget = widgets.SamplesMixin(samples=samples) bar = progressbar.ProgressBar(widgets=[samples_widget]) bar.update() assert samples_widget(bar, None, True) == (None, None) assert samples_widget(bar, None, False)[1] == [0] assert samples_widget(bar, None, True) == (None, None) assert samples_widget(bar, None, False)[1] == [0] time.sleep(1) assert samples_widget(bar, None, True) == (None, None) assert samples_widget(bar, None, False)[1] == [0] bar.update(1) assert samples_widget(bar, None, True) == (timedelta(0, 1), 1) assert samples_widget(bar, None, False)[1] == [0, 1] time.sleep(1) bar.update(2) assert samples_widget(bar, None, True) == (timedelta(0, 1), 1) assert samples_widget(bar, None, False)[1] == [1, 2] time.sleep(0.01) bar.update(3) assert samples_widget(bar, None, True) == (timedelta(0, 1), 1) assert samples_widget(bar, None, False)[1] == [1, 2] python-progressbar-4.5.0/tests/test_speed.py000066400000000000000000000016411466372462000212570ustar00rootroot00000000000000import pytest import progressbar @pytest.mark.parametrize( 'total_seconds_elapsed,value,expected', [ (1, 0, ' 0.0 s/B'), (1, 0.01, '100.0 s/B'), (1, 0.1, ' 0.1 B/s'), (1, 1, ' 1.0 B/s'), (1, 2**10 - 1, '1023.0 B/s'), (1, 2**10 + 0, ' 1.0 KiB/s'), (1, 2**20, ' 1.0 MiB/s'), (1, 2**30, ' 1.0 GiB/s'), (1, 2**40, ' 1.0 TiB/s'), (1, 2**50, ' 1.0 PiB/s'), (1, 2**60, ' 1.0 EiB/s'), (1, 2**70, ' 1.0 ZiB/s'), (1, 2**80, ' 1.0 YiB/s'), (1, 2**90, '1024.0 YiB/s'), ], ) def test_file_transfer_speed(total_seconds_elapsed, value, expected) -> None: widget = progressbar.FileTransferSpeed() assert ( widget( None, dict( total_seconds_elapsed=total_seconds_elapsed, value=value, ), ) == expected ) python-progressbar-4.5.0/tests/test_stream.py000066400000000000000000000071121466372462000214510ustar00rootroot00000000000000import io import os import sys import pytest import progressbar from progressbar import terminal def test_nowrap() -> None: # Make sure we definitely unwrap for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) stdout = sys.stdout stderr = sys.stderr progressbar.streams.wrap() assert stdout == sys.stdout assert stderr == sys.stderr progressbar.streams.unwrap() assert stdout == sys.stdout assert stderr == sys.stderr # Make sure we definitely unwrap for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) def test_wrap() -> None: # Make sure we definitely unwrap for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) stdout = sys.stdout stderr = sys.stderr progressbar.streams.wrap(stderr=True, stdout=True) assert stdout != sys.stdout assert stderr != sys.stderr # Wrap again stdout = sys.stdout stderr = sys.stderr progressbar.streams.wrap(stderr=True, stdout=True) assert stdout == sys.stdout assert stderr == sys.stderr # Make sure we definitely unwrap for _i in range(5): progressbar.streams.unwrap(stderr=True, stdout=True) def test_excepthook() -> None: progressbar.streams.wrap(stderr=True, stdout=True) try: raise RuntimeError() # noqa: TRY301 except RuntimeError: progressbar.streams.excepthook(*sys.exc_info()) progressbar.streams.unwrap_excepthook() progressbar.streams.unwrap_excepthook() def test_fd_as_io_stream() -> None: stream = io.StringIO() with progressbar.ProgressBar(fd=stream) as pb: for i in range(101): pb.update(i) stream.close() def test_no_newlines() -> None: kwargs = dict( redirect_stderr=True, redirect_stdout=True, line_breaks=False, is_terminal=True, ) with progressbar.ProgressBar(**kwargs) as bar: for i in range(5): bar.update(i) for i in range(5, 10): try: print('\n\n', file=progressbar.streams.stdout) print('\n\n', file=progressbar.streams.stderr) except ValueError: pass bar.update(i) @pytest.mark.parametrize('stream', [sys.__stdout__, sys.__stderr__]) @pytest.mark.skipif(os.name == 'nt', reason='Windows does not support this') def test_fd_as_standard_streams(stream) -> None: with progressbar.ProgressBar(fd=stream) as pb: for i in range(101): pb.update(i) def test_line_offset_stream_wrapper() -> None: stream = terminal.LineOffsetStreamWrapper(5, io.StringIO()) stream.write('Hello World!') def test_last_line_stream_methods() -> None: stream = terminal.LastLineStream(io.StringIO()) # Test write method stream.write('Hello World!') assert stream.read() == 'Hello World!' assert stream.read(5) == 'Hello' # Test flush method stream.flush() assert stream.line == 'Hello World!' assert stream.readline() == 'Hello World!' assert stream.readline(5) == 'Hello' # Test truncate method stream.truncate(5) assert stream.line == 'Hello' stream.truncate() assert stream.line == '' # Test seekable/readable assert not stream.seekable() assert stream.readable() stream.writelines(['a', 'b', 'c']) assert stream.read() == 'c' assert list(stream) == ['c'] with stream: stream.write('Hello World!') assert stream.read() == 'Hello World!' assert stream.read(5) == 'Hello' # Test close method stream.close() python-progressbar-4.5.0/tests/test_terminal.py000066400000000000000000000106041466372462000217710ustar00rootroot00000000000000import signal import sys import time from datetime import timedelta import progressbar from progressbar import terminal def test_left_justify() -> None: """Left justify using the terminal width""" p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())], max_value=100, term_width=20, left_justify=True, ) assert p.term_width is not None for i in range(100): p.update(i) def test_right_justify() -> None: """Right justify using the terminal width""" p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(marker=progressbar.RotatingMarker())], max_value=100, term_width=20, left_justify=False, ) assert p.term_width is not None for i in range(100): p.update(i) def test_auto_width(monkeypatch) -> None: """Right justify using the terminal width""" def ioctl(*args): return '\xbf\x00\xeb\x00\x00\x00\x00\x00' def fake_signal(signal, func): pass try: import fcntl monkeypatch.setattr(fcntl, 'ioctl', ioctl) monkeypatch.setattr(signal, 'signal', fake_signal) p = progressbar.ProgressBar( widgets=[ progressbar.BouncingBar(marker=progressbar.RotatingMarker()), ], max_value=100, left_justify=True, term_width=None, ) assert p.term_width is not None for i in range(100): p.update(i) except ImportError: pass # Skip on Windows def test_fill_right() -> None: """Right justify using the terminal width""" p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(fill_left=False)], max_value=100, term_width=20, ) assert p.term_width is not None for i in range(100): p.update(i) def test_fill_left() -> None: """Right justify using the terminal width""" p = progressbar.ProgressBar( widgets=[progressbar.BouncingBar(fill_left=True)], max_value=100, term_width=20, ) assert p.term_width is not None for i in range(100): p.update(i) def test_no_fill(monkeypatch) -> None: """Simply bounce within the terminal width""" bar = progressbar.BouncingBar() bar.INTERVAL = timedelta(seconds=1) p = progressbar.ProgressBar( widgets=[bar], max_value=progressbar.UnknownLength, term_width=20, ) assert p.term_width is not None for i in range(30): p.update(i, force=True) # Fake the start time so we can actually emulate a moving progress bar p.start_time = p.start_time - timedelta(seconds=i) def test_stdout_redirection() -> None: p = progressbar.ProgressBar( fd=sys.stdout, max_value=10, redirect_stdout=True, ) for i in range(10): print('', file=sys.stdout) p.update(i) def test_double_stdout_redirection() -> None: p = progressbar.ProgressBar(max_value=10, redirect_stdout=True) p2 = progressbar.ProgressBar(max_value=10, redirect_stdout=True) for i in range(10): print('', file=sys.stdout) p.update(i) p2.update(i) def test_stderr_redirection() -> None: p = progressbar.ProgressBar(max_value=10, redirect_stderr=True) for i in range(10): print('', file=sys.stderr) p.update(i) def test_stdout_stderr_redirection() -> None: p = progressbar.ProgressBar( max_value=10, redirect_stdout=True, redirect_stderr=True, ) p.start() for i in range(10): time.sleep(0.01) print('', file=sys.stdout) print('', file=sys.stderr) p.update(i) p.finish() def test_resize(monkeypatch) -> None: def ioctl(*args): return '\xbf\x00\xeb\x00\x00\x00\x00\x00' def fake_signal(signal, func): pass try: import fcntl monkeypatch.setattr(fcntl, 'ioctl', ioctl) monkeypatch.setattr(signal, 'signal', fake_signal) p = progressbar.ProgressBar(max_value=10) p.start() for i in range(10): p.update(i) p._handle_resize() p.finish() except ImportError: pass # Skip on Windows def test_base() -> None: assert str(terminal.CUP) assert str(terminal.CLEAR_SCREEN_ALL_AND_HISTORY) terminal.clear_line(0) terminal.clear_line(1) python-progressbar-4.5.0/tests/test_timed.py000066400000000000000000000105111466372462000212550ustar00rootroot00000000000000import datetime import time import progressbar def test_timer() -> None: """Testing (Adaptive)ETA when the value doesn't actually change""" widgets = [ progressbar.Timer(), ] p = progressbar.ProgressBar( max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() p.update() p.update(1) p._needs_update() time.sleep(0.001) p.update(1) p.finish() def test_eta() -> None: """Testing (Adaptive)ETA when the value doesn't actually change""" widgets = [ progressbar.ETA(), ] p = progressbar.ProgressBar( min_value=0, max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() time.sleep(0.001) p.update(0) time.sleep(0.001) p.update(1) time.sleep(0.001) p.update(1) time.sleep(0.001) p.update(2) time.sleep(0.001) p.finish() time.sleep(0.001) p.update(2) def test_adaptive_eta() -> None: """Testing (Adaptive)ETA when the value doesn't actually change""" widgets = [ progressbar.AdaptiveETA(), ] widgets[0].INTERVAL = datetime.timedelta(microseconds=1) p = progressbar.ProgressBar( max_value=2, samples=2, widgets=widgets, poll_interval=0.0001, ) p.start() for _i in range(20): p.update(1) time.sleep(0.001) p.finish() def test_adaptive_transfer_speed() -> None: """Testing (Adaptive)ETA when the value doesn't actually change""" widgets = [ progressbar.AdaptiveTransferSpeed(), ] p = progressbar.ProgressBar( max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() p.update(1) time.sleep(0.001) p.update(1) p.finish() def test_etas(monkeypatch) -> None: """Compare file transfer speed to adaptive transfer speed""" n = 10 interval = datetime.timedelta(seconds=1) widgets = [ progressbar.FileTransferSpeed(), progressbar.AdaptiveTransferSpeed(samples=n / 2), ] datas = [] # Capture the output sent towards the `_speed` method def calculate_eta(self, value, elapsed): """Capture the widget output""" data = dict( value=value, elapsed=int(elapsed), ) datas.append(data) return 0, 0 monkeypatch.setattr(progressbar.FileTransferSpeed, '_speed', calculate_eta) monkeypatch.setattr( progressbar.AdaptiveTransferSpeed, '_speed', calculate_eta, ) for widget in widgets: widget.INTERVAL = interval p = progressbar.ProgressBar( max_value=n, widgets=widgets, poll_interval=interval, ) # Run the first few samples at a low speed and speed up later so we can # compare the results from both widgets for i in range(n): p.update(i) if i > n / 2: time.sleep(1) else: time.sleep(10) p.finish() # Due to weird travis issues, the actual testing is disabled for now # import pprint # pprint.pprint(datas[::2]) # pprint.pprint(datas[1::2]) # for i, (a, b) in enumerate(zip(datas[::2], datas[1::2])): # # Because the speed is identical initially, the results should be the # # same for adaptive and regular transfer speed. Only when the speed # # changes we should start see a lot of differences between the two # if i < (n / 2 - 1): # assert a['elapsed'] == b['elapsed'] # if i > (n / 2 + 1): # assert a['elapsed'] > b['elapsed'] def test_non_changing_eta() -> None: """Testing (Adaptive)ETA when the value doesn't actually change""" widgets = [ progressbar.AdaptiveETA(), progressbar.ETA(), progressbar.AdaptiveTransferSpeed(), ] p = progressbar.ProgressBar( max_value=2, widgets=widgets, poll_interval=0.0001, ) p.start() p.update(1) time.sleep(0.001) p.update(1) p.finish() def test_eta_not_available(): """ When ETA is not available (data coming from a generator), ETAs should not raise exceptions. """ def gen(): yield from range(200) widgets = [progressbar.AdaptiveETA(), progressbar.ETA()] bar = progressbar.ProgressBar(widgets=widgets) for _i in bar(gen()): pass python-progressbar-4.5.0/tests/test_timer.py000066400000000000000000000027021466372462000212760ustar00rootroot00000000000000from datetime import timedelta import pytest import progressbar @pytest.mark.parametrize( 'poll_interval,expected', [ (1, 1), (timedelta(seconds=1), 1), (0.001, 0.001), (timedelta(microseconds=1000), 0.001), ], ) @pytest.mark.parametrize( 'parameter', [ 'poll_interval', 'min_poll_interval', ], ) def test_poll_interval(parameter, poll_interval, expected) -> None: # Test int, float and timedelta intervals bar = progressbar.ProgressBar(**{parameter: poll_interval}) assert getattr(bar, parameter) == expected @pytest.mark.parametrize( 'interval', [ 1, timedelta(seconds=1), ], ) def test_intervals(monkeypatch, interval) -> None: monkeypatch.setattr( progressbar.ProgressBar, '_MINIMUM_UPDATE_INTERVAL', interval, ) bar = progressbar.ProgressBar(max_value=100) # Initially there should be no last_update_time assert bar.last_update_time is None # After updating there should be a last_update_time bar.update(1) assert bar.last_update_time # We should not need an update if the time is nearly the same as before last_update_time = bar.last_update_time bar.update(2) assert bar.last_update_time == last_update_time # We should need an update if we're beyond the poll_interval bar._last_update_time -= 2 bar.update(3) assert bar.last_update_time != last_update_time python-progressbar-4.5.0/tests/test_unicode.py000066400000000000000000000014641466372462000216100ustar00rootroot00000000000000from __future__ import annotations import time import pytest from python_utils import converters import progressbar @pytest.mark.parametrize( 'name,markers', [ ('line arrows', '←↖↑↗→↘↓↙'), ('block arrows', '◢◣◤◥'), ('wheels', '◐◓◑◒'), ], ) @pytest.mark.parametrize('as_unicode', [True, False]) def test_markers(name, markers: bytes | str, as_unicode) -> None: if as_unicode: markers = converters.to_unicode(markers) else: markers = converters.to_str(markers) widgets = [ f'{name.capitalize()}: ', progressbar.AnimatedMarker(markers=markers), ] bar = progressbar.ProgressBar(widgets=widgets) bar._MINIMUM_UPDATE_INTERVAL = 1e-12 for _i in bar(iter(range(24))): time.sleep(0.001) python-progressbar-4.5.0/tests/test_unknown_length.py000066400000000000000000000017021466372462000232150ustar00rootroot00000000000000import progressbar def test_unknown_length() -> None: pb = progressbar.ProgressBar( widgets=[progressbar.AnimatedMarker()], max_value=progressbar.UnknownLength, ) assert pb.max_value is progressbar.UnknownLength def test_unknown_length_default_widgets() -> None: # The default widgets picked should work without a known max_value pb = progressbar.ProgressBar(max_value=progressbar.UnknownLength).start() for i in range(60): pb.update(i) pb.finish() def test_unknown_length_at_start() -> None: # The default widgets should be picked after we call .start() pb = progressbar.ProgressBar().start(max_value=progressbar.UnknownLength) for i in range(60): pb.update(i) pb.finish() 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) python-progressbar-4.5.0/tests/test_utils.py000066400000000000000000000067261466372462000213300ustar00rootroot00000000000000import io import pytest import progressbar import progressbar.env @pytest.mark.parametrize( 'value,expected', [ (None, None), ('', None), ('1', True), ('y', True), ('t', True), ('yes', True), ('true', True), ('True', True), ('0', False), ('n', False), ('f', False), ('no', False), ('false', False), ('False', False), ], ) def test_env_flag(value, expected, monkeypatch) -> None: if value is not None: monkeypatch.setenv('TEST_ENV', value) assert progressbar.env.env_flag('TEST_ENV') == expected if value: monkeypatch.setenv('TEST_ENV', value.upper()) assert progressbar.env.env_flag('TEST_ENV') == expected monkeypatch.undo() def test_is_terminal(monkeypatch) -> None: fd = io.StringIO() monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) monkeypatch.setattr(progressbar.env, 'JUPYTER', False) assert progressbar.env.is_terminal(fd) is False assert progressbar.env.is_terminal(fd, True) is True assert progressbar.env.is_terminal(fd, False) is False monkeypatch.setattr(progressbar.env, 'JUPYTER', True) assert progressbar.env.is_terminal(fd) is True # Sanity check monkeypatch.setattr(progressbar.env, 'JUPYTER', False) assert progressbar.env.is_terminal(fd) is False monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') assert progressbar.env.is_terminal(fd) is True monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') assert progressbar.env.is_terminal(fd) is False monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') # Sanity check assert progressbar.env.is_terminal(fd) is False def test_is_ansi_terminal(monkeypatch) -> None: fd = io.StringIO() monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL', raising=False) monkeypatch.setattr(progressbar.env, 'JUPYTER', False) assert not progressbar.env.is_ansi_terminal(fd) assert progressbar.env.is_ansi_terminal(fd, True) is True assert progressbar.env.is_ansi_terminal(fd, False) is False monkeypatch.setattr(progressbar.env, 'JUPYTER', True) assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.setattr(progressbar.env, 'JUPYTER', False) # Sanity check assert not progressbar.env.is_ansi_terminal(fd) monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'true') assert not progressbar.env.is_ansi_terminal(fd) monkeypatch.setenv('PROGRESSBAR_IS_TERMINAL', 'false') assert not progressbar.env.is_ansi_terminal(fd) monkeypatch.delenv('PROGRESSBAR_IS_TERMINAL') # Sanity check assert not progressbar.env.is_ansi_terminal(fd) # Fake TTY mode for environment testing fd.isatty = lambda: True monkeypatch.setenv('TERM', 'xterm') assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.setenv('TERM', 'xterm-256') assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.setenv('TERM', 'xterm-256color') assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.setenv('TERM', 'xterm-24bit') assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.delenv('TERM') monkeypatch.setenv('ANSICON', 'true') assert progressbar.env.is_ansi_terminal(fd) is True monkeypatch.delenv('ANSICON') assert not progressbar.env.is_ansi_terminal(fd) def raise_error(): raise RuntimeError('test') fd.isatty = raise_error assert not progressbar.env.is_ansi_terminal(fd) python-progressbar-4.5.0/tests/test_widgets.py000066400000000000000000000152171466372462000216310ustar00rootroot00000000000000from __future__ import annotations import time import pytest import progressbar max_values: list[None | type[progressbar.base.UnknownLength] | int] = [ None, 10, progressbar.UnknownLength, ] def test_create_wrapper() -> None: with pytest.raises(AssertionError): progressbar.widgets.create_wrapper('ab') with pytest.raises(RuntimeError): progressbar.widgets.create_wrapper(123) def test_widgets_small_values() -> None: widgets = [ 'Test: ', progressbar.Percentage(), ' ', progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', progressbar.ETA(), ' ', progressbar.AbsoluteETA(), ' ', progressbar.FileTransferSpeed(), ] p = progressbar.ProgressBar(widgets=widgets, max_value=10).start() p.update(0) for i in range(10): time.sleep(1) p.update(i + 1) p.finish() @pytest.mark.parametrize('max_value', [10**6, 10**8]) def test_widgets_large_values(max_value) -> None: widgets = [ 'Test: ', progressbar.Percentage(), ' ', progressbar.Bar(marker=progressbar.RotatingMarker()), ' ', progressbar.ETA(), ' ', progressbar.AbsoluteETA(), ' ', progressbar.FileTransferSpeed(), ] p = progressbar.ProgressBar(widgets=widgets, max_value=max_value).start() for i in range(0, 10**6, 10**4): time.sleep(1) p.update(i + 1) p.finish() def test_format_widget() -> None: widgets = [ progressbar.FormatLabel(f'%({mapping})r') for mapping in progressbar.FormatLabel.mapping ] p = progressbar.ProgressBar(widgets=widgets) for _ in p(range(10)): time.sleep(1) @pytest.mark.parametrize('max_value', [None, 10]) def test_all_widgets_small_values(max_value) -> None: widgets = [ progressbar.Timer(), progressbar.ETA(), progressbar.AdaptiveETA(), progressbar.AbsoluteETA(), progressbar.DataSize(), progressbar.FileTransferSpeed(), progressbar.AdaptiveTransferSpeed(), progressbar.AnimatedMarker(), progressbar.Counter(), progressbar.Percentage(), progressbar.FormatLabel('%(value)d'), progressbar.SimpleProgress(), progressbar.Bar(), progressbar.ReverseBar(), progressbar.BouncingBar(), progressbar.CurrentTime(), progressbar.CurrentTime(microseconds=False), progressbar.CurrentTime(microseconds=True), ] p = progressbar.ProgressBar(widgets=widgets, max_value=max_value) for i in range(10): time.sleep(1) p.update(i + 1) p.finish() @pytest.mark.parametrize('max_value', [10**6, 10**7]) def test_all_widgets_large_values(max_value) -> None: widgets = [ progressbar.Timer(), progressbar.ETA(), progressbar.AdaptiveETA(), progressbar.AbsoluteETA(), progressbar.DataSize(), progressbar.FileTransferSpeed(), progressbar.AdaptiveTransferSpeed(), progressbar.AnimatedMarker(), progressbar.Counter(), progressbar.Percentage(), progressbar.FormatLabel('%(value)d/%(max_value)d'), progressbar.SimpleProgress(), progressbar.Bar(fill=lambda progress, data, width: '#'), progressbar.ReverseBar(), progressbar.BouncingBar(), progressbar.FormatCustomText('Custom %(text)s', dict(text='text')), ] p = progressbar.ProgressBar(widgets=widgets, max_value=max_value) p.update() time.sleep(1) p.update() for i in range(0, 10**6, 10**4): time.sleep(1) p.update(i) @pytest.mark.parametrize('min_width', [None, 1, 2, 80, 120]) @pytest.mark.parametrize('term_width', [1, 2, 80, 120]) def test_all_widgets_min_width(min_width, term_width) -> None: widgets = [ progressbar.Timer(min_width=min_width), progressbar.ETA(min_width=min_width), progressbar.AdaptiveETA(min_width=min_width), progressbar.AbsoluteETA(min_width=min_width), progressbar.DataSize(min_width=min_width), progressbar.FileTransferSpeed(min_width=min_width), progressbar.AdaptiveTransferSpeed(min_width=min_width), progressbar.AnimatedMarker(min_width=min_width), progressbar.Counter(min_width=min_width), progressbar.Percentage(min_width=min_width), progressbar.FormatLabel('%(value)d', min_width=min_width), progressbar.SimpleProgress(min_width=min_width), progressbar.Bar(min_width=min_width), progressbar.ReverseBar(min_width=min_width), progressbar.BouncingBar(min_width=min_width), progressbar.FormatCustomText( 'Custom %(text)s', dict(text='text'), min_width=min_width, ), progressbar.DynamicMessage('custom', min_width=min_width), progressbar.CurrentTime(min_width=min_width), ] p = progressbar.ProgressBar(widgets=widgets, term_width=term_width) p.update(0) p.update() for widget in p._format_widgets(): if min_width and min_width > term_width: assert widget == '' else: assert widget != '' @pytest.mark.parametrize('max_width', [None, 1, 2, 80, 120]) @pytest.mark.parametrize('term_width', [1, 2, 80, 120]) def test_all_widgets_max_width(max_width, term_width) -> None: widgets = [ progressbar.Timer(max_width=max_width), progressbar.ETA(max_width=max_width), progressbar.AdaptiveETA(max_width=max_width), progressbar.AbsoluteETA(max_width=max_width), progressbar.DataSize(max_width=max_width), progressbar.FileTransferSpeed(max_width=max_width), progressbar.AdaptiveTransferSpeed(max_width=max_width), progressbar.AnimatedMarker(max_width=max_width), progressbar.Counter(max_width=max_width), progressbar.Percentage(max_width=max_width), progressbar.FormatLabel('%(value)d', max_width=max_width), progressbar.SimpleProgress(max_width=max_width), progressbar.Bar(max_width=max_width), progressbar.ReverseBar(max_width=max_width), progressbar.BouncingBar(max_width=max_width), progressbar.FormatCustomText( 'Custom %(text)s', dict(text='text'), max_width=max_width, ), progressbar.DynamicMessage('custom', max_width=max_width), progressbar.CurrentTime(max_width=max_width), ] p = progressbar.ProgressBar(widgets=widgets, term_width=term_width) p.update(0) p.update() for widget in p._format_widgets(): if max_width and max_width < term_width: assert widget == '' else: assert widget != '' python-progressbar-4.5.0/tests/test_windows.py000066400000000000000000000043761466372462000216610ustar00rootroot00000000000000import os import sys import time import pytest if os.name == 'nt': import win32console # "pip install pypiwin32" to get this else: pytest.skip('skipping windows-only tests', allow_module_level=True) import progressbar pytest_plugins = 'pytester' _WIDGETS = [ progressbar.Percentage(), ' ', progressbar.Bar(), ' ', progressbar.FileTransferSpeed(), ' ', progressbar.ETA(), ] _MB: int = 1024 * 1024 # --------------------------------------------------------------------------- def scrape_console(line_count): pcsb = win32console.GetStdHandle(win32console.STD_OUTPUT_HANDLE) csbi = pcsb.GetConsoleScreenBufferInfo() col_max = csbi['Size'].X row_max = csbi['CursorPosition'].Y line_count = min(line_count, row_max) lines = [] for row in range(line_count): pct = win32console.PyCOORDType(0, row + row_max - line_count) line = pcsb.ReadConsoleOutputCharacter(col_max, pct) lines.append(line.rstrip()) return lines # --------------------------------------------------------------------------- def runprogress() -> int: print('***BEGIN***') b = progressbar.ProgressBar( widgets=['example.m4v: ', *_WIDGETS], max_value=10 * _MB, ) for i in range(10): b.update((i + 1) * _MB) time.sleep(0.25) b.finish() print('***END***') return 0 # --------------------------------------------------------------------------- def find(lines, x): try: return lines.index(x) except ValueError: return -sys.maxsize # --------------------------------------------------------------------------- def test_windows(testdir: pytest.Testdir) -> None: testdir.run( sys.executable, '-c', 'import progressbar; print(progressbar.__file__)' ) def main() -> int: runprogress() scraped_lines = scrape_console(100) # reverse lines so we find the LAST instances of output. scraped_lines.reverse() index_begin = find(scraped_lines, '***BEGIN***') index_end = find(scraped_lines, '***END***') if index_end + 2 != index_begin: print('ERROR: Unexpected multi-line output from progressbar') print(f'{index_begin=} {index_end=}') return 1 return 0 if __name__ == '__main__': main() python-progressbar-4.5.0/tests/test_with.py000066400000000000000000000007041466372462000211310ustar00rootroot00000000000000import progressbar def test_with() -> None: with progressbar.ProgressBar(max_value=10) as p: for i in range(10): p.update(i) def test_with_stdout_redirection() -> None: with progressbar.ProgressBar(max_value=10, redirect_stdout=True) as p: for i in range(10): p.update(i) def test_with_extra_start() -> None: with progressbar.ProgressBar(max_value=10) as p: p.start() p.start() python-progressbar-4.5.0/tests/test_wrappingio.py000066400000000000000000000026611466372462000223410ustar00rootroot00000000000000import io import sys import pytest from progressbar import utils def test_wrappingio() -> None: # Test the wrapping of our version of sys.stdout` ` q fd = utils.WrappingIO(sys.stdout) assert fd.fileno() assert not fd.isatty() assert not fd.read() assert not fd.readline() assert not fd.readlines() assert fd.readable() assert not fd.seek(0) assert fd.seekable() assert not fd.tell() assert not fd.truncate() assert fd.writable() assert fd.write('test') assert not fd.writelines(['test']) with pytest.raises(StopIteration): next(fd) with pytest.raises(StopIteration): next(iter(fd)) def test_wrapping_stringio() -> None: # Test the wrapping of our version of sys.stdout` ` q string_io = io.StringIO() fd = utils.WrappingIO(string_io) with fd: with pytest.raises(io.UnsupportedOperation): fd.fileno() assert not fd.isatty() assert not fd.read() assert not fd.readline() assert not fd.readlines() assert fd.readable() assert not fd.seek(0) assert fd.seekable() assert not fd.tell() assert not fd.truncate() assert fd.writable() assert fd.write('test') assert not fd.writelines(['test']) with pytest.raises(StopIteration): next(fd) with pytest.raises(StopIteration): next(iter(fd)) python-progressbar-4.5.0/tox.ini000066400000000000000000000022351466372462000167170ustar00rootroot00000000000000[tox] envlist = py38 py39 py310 py311 py312 docs black ruff ; mypy ; codespell skip_missing_interpreters = True [testenv] deps = -r{toxinidir}/tests/requirements.txt pyright commands = pyright py.test --basetemp="{envtmpdir}" --confcutdir=.. {posargs} skip_install = true [testenv:mypy] changedir = basepython = python3 deps = mypy commands = mypy {toxinidir}/progressbar [testenv:black] basepython = python3 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 mkdir commands = rm -f docs/modules.rst mkdir -p docs/_static sphinx-apidoc -e -o docs/ progressbar */os_specific/* rm -f docs/modules.rst sphinx-build -b html -d docs/_build/doctrees docs docs/_build/html {posargs} [testenv:ruff] commands = ruff check ruff format deps = ruff skip_install = true [testenv:codespell] changedir = {toxinidir} commands = codespell . deps = codespell skip_install = true command = codespell