From a61536cb7d898541abbed57882b3f93a7f7e16a9 Mon Sep 17 00:00:00 2001 From: Yohan Boujon Date: Thu, 10 Apr 2025 14:00:42 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 5 +++ README.md | 3 ++ dummy.sh | 15 +++++++ main.py | 64 +++++++++++++++++++++++++++ tui.py | 123 +++++++++++++++++++++++++++++++++++++++++++++++++++ tuicolors.py | 17 +++++++ 6 files changed, 227 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 dummy.sh create mode 100755 main.py create mode 100644 tui.py create mode 100644 tuicolors.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9d0ffcd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# Python +**/__pycache__ + +# Temporary +**/temp \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b2b64f --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Python Loading Bar + +Just a simple loading bar to use in any of your projects. A fully working example can be found in main.py. \ No newline at end of file diff --git a/dummy.sh b/dummy.sh new file mode 100755 index 0000000..a298940 --- /dev/null +++ b/dummy.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# Simulate a "download" process with fake delays + +echo "Starting download (3 files)..." +sleep 2 +echo "Downloading file 1 of 3..." +sleep 2 +echo "Downloading file 2 of 3..." +sleep 2 +echo "Downloading file 3 of 3..." +sleep 2 +echo "Extracting files..." +sleep 1 +echo "Download complete!" diff --git a/main.py b/main.py new file mode 100755 index 0000000..1611246 --- /dev/null +++ b/main.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import tui +from tuicolors import * +import os +import subprocess +import threading +import time + +ROOT_FOLDER = os.path.dirname(os.path.abspath(__file__)) + +def updateprogress(text: str, lb: tui.LoadingBar) -> int: + if text == None: + return -1 + + if lb.getMaximum() == -1 and text.startswith("Starting download"): + filetotal=int(text[19]) + lb.updateLine(1,f"Downloading files ({filetotal})...") + lb.setMaximum(filetotal+2) + elif lb.getMaximum() > 0: + if text.startswith("Downloading file"): + filenum = int(text[17]) + lb.setValue(filenum) + if text.startswith("Extracting files..."): + lb.updateLine(1,"Extracting files...") + lb.incrementValue() + if text.startswith("Download complete!"): + lb.finish() + +def showprogress(): + # File info + lastentry = "" + # Loading bar + lb = tui.LoadingBar() + lb.addLine(3) + lb.updateLine(1,"Preparing download...") + + while not(lb.hasFinished()): + newentry = tui.getLastEntry(f"{ROOT_FOLDER}/temp/log.txt") + + # If we get a new string -> update percentage depending on the entry + if newentry != lastentry: + lastentry = newentry + updateprogress(lastentry,lb) + + # Printing and waiting + print(f"\r{lb}", end='', flush=True) + time.sleep(0.1) + +# Testing printing capabilities +colouredText = tui.colourText("Hello", colour=BRIGHT_RED, backgroundcolour=BLUE) +print(colouredText) +wow = tui.limitText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dapibus, tellus a consectetur elementum, ex ex laoreet ligula, sit amet dignissim metus eros vel ligula. Nulla malesuada risus nec libero gravida mattis. Fusce sit amet pharetra felis, congue cursus tellus. Morbi faucibus nisi at gravida viverra. Praesent a posuere ex. In hac habitasse platea dictumst. Sed sed feugiat leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Donec tincidunt porttitor arcu vel cursus. Vestibulum luctus quam vitae gravida viverra. ") +print(wow) +wow = tui.limitText("Tu ne parleras pas", 5) +print(wow) + +os.makedirs(ROOT_FOLDER + "/temp", exist_ok=True) +with open(ROOT_FOLDER + "/temp/log.txt", "a") as log_file: + thread = threading.Thread(target=showprogress) + thread.start() + subprocess.run(["bash", "dummy.sh"], stdout=log_file, stderr=log_file) + thread.join() + print(tui.colourText("\nSuccessfully downloaded project!", GREEN)) diff --git a/tui.py b/tui.py new file mode 100644 index 0000000..1f29c76 --- /dev/null +++ b/tui.py @@ -0,0 +1,123 @@ +from tuicolors import WHITE, BRIGHT_BLACK, GREEN, RED +import os +import math + +SPIN_STATUS = ['|','/','-','\\'] +CORRECT = "\033[92mv\033[00m" +INCORRECT = "\033[91mX\033[00m" +UP_LINE = "\033[A" + +def colourText(string: str, colour: int = WHITE, backgroundcolour: int = None) -> str: + if backgroundcolour is not None: + bg_code = backgroundcolour + 10 + return f"\033[{colour};{bg_code}m{string}\033[0m" + else: + return f"\033[{colour}m{string}\033[0m" + +def limitText(string: str, limit: int=-1) -> str: + if limit == -1: + limit = os.get_terminal_size().columns-3 + if len(string) > limit: + return string[:limit]+"..." + else: + return string + +class LoadingBar: + def __init__(self): + if os.get_terminal_size().columns < 57: + self.size = os.get_terminal_size().columns - 5 + else: + self.size = 55 + self.percentage = 0.0 + self.spin = 0 + self.line = [] + self.first = True + self.max = -1 + + def addLine(self, line: int): + self.line = [""] * line + + def updateLine(self, index: int, text: str): + self.line[index] = f"{text}{' '*(os.get_terminal_size().columns-len(text))}" + + def setPercentage(self, percentage: float): + if percentage >= 0.0 and percentage <= 100.0: + self.percentage = percentage + + def getPercentage(self) -> float: + return self.percentage + + def setMaximum(self, max: int): + self.max = max + + def getMaximum(self) -> int: + return self.max + + def setValue(self, value: int): + if self.max == -1: + raise ValueError("Please set the maximum before setValue by calling LoadingBar.setMaximum()") + elif value > self.max: + raise ValueError(f"Cannot set a value superior to LoadingBar maximum: {self.max}") + else: + self.percentage = (value/self.max)*100.0 + + def incrementValue(self): + if self.max == -1: + raise ValueError("Please set the maximum before incrementValue by calling LoadingBar.setMaximum()") + value = math.floor((self.percentage/100.0)*self.max)+1 + self.setValue(value) + + def finish(self): + self.percentage = 100.0 + + def hasFinished(self) -> bool: + return self.percentage == 100.0 + + def updateSpin(self): + self.spin += 1 + if self.spin >= len(SPIN_STATUS): + self.spin = 0 + + def getSpin(self) -> str: + if self.percentage >= 100.0: + return CORRECT + else: + return SPIN_STATUS[self.spin] + + def getBackgroundColour(self) -> str: + if self.percentage >= 100.0: + return GREEN + else: + return WHITE + + def getUpLine(self) -> str: + if not(self.first): + return UP_LINE*len(self.line) + else: + return "" + + def start(self): + print("\n"*(len(self.line)+1), end='', flush=True) + + def __str__(self) -> str: + filled = math.floor((self.percentage/100)*(self.size-5)) + empty = (self.size-5)-filled + filled_str = colourText(" " * filled, backgroundcolour=self.getBackgroundColour()) + empty_str = colourText(" " * empty, backgroundcolour=BRIGHT_BLACK) + formatted_percent = colourText(str(int(self.percentage)), self.getBackgroundColour()) + + ret = self.getUpLine() + for l in self.line: + ret += f"{l}\n" + ret += f"{self.getSpin()} {filled_str}{empty_str} {formatted_percent}%" + self.updateSpin() + self.first = False + return ret + +def getLastEntry(filepath: str) -> str: + with open(filepath, "r") as log_file: + log_file.seek(0) + new_data = log_file.readlines() + if new_data: + last_line = new_data[-1].strip() + return last_line \ No newline at end of file diff --git a/tuicolors.py b/tuicolors.py new file mode 100644 index 0000000..8788be4 --- /dev/null +++ b/tuicolors.py @@ -0,0 +1,17 @@ +BLACK = 30 +RED = 31 +GREEN = 32 +YELLOW = 33 +BLUE = 34 +MAGENTA = 35 +CYAN = 36 +WHITE = 37 + +BRIGHT_BLACK = 90 +BRIGHT_RED = 91 +BRIGHT_GREEN = 92 +BRIGHT_YELLOW = 93 +BRIGHT_BLUE = 94 +BRIGHT_MAGENTA = 95 +BRIGHT_CYAN = 96 +BRIGHT_WHITE = 97