This commit is contained in:
hhftechnologies 2024-11-24 21:42:21 +05:30
parent 014a98ee43
commit 15d7215af1
18 changed files with 701 additions and 0 deletions

View file

@ -0,0 +1,71 @@
# BASH CLI Formatter with Spinner
This repository contains two scripts, `task_formatter.sh` and `example_usage.sh`, that work together to execute tasks with a spinner and formatted output. The `task_formatter.sh` script provides functions for running tasks with a spinner, while the `example_usage.sh` script demonstrates how to use these functions by defining and executing a set of tasks.
## Example Output
![](https://github.com/seanssmith/CLI-Formatter/blob/main/bash_task_importer/bashformatterexample.gif)
## Files
### task_formatter.sh
This script contains functions for running tasks with a spinner and formatted output.
#### Functions
- `spinner(pid)`: Displays a spinner while a background task is running.
- `run_task(task_description, task_command)`: Executes a task with a spinner and captures its output.
- `run_all_tasks(tasks)`: Runs all tasks passed as arguments, using the `run_task` function.
#### Usage
The `task_formatter.sh` script is designed to be sourced by another script, which can then call its functions to run tasks with formatted output.
### example_usage.sh
This script demonstrates how to use the functions provided by `task_formatter.sh` to run a set of tasks.
#### Usage
1. Ensure both `task_formatter.sh` and `example_usage.sh` are executable:
```bash
chmod +x task_formatter.sh
chmod +x example_usage.sh
```
2. Run the `example_usage.sh` script:
```bash
./example_usage.sh
```
#### Example Tasks
The `example_usage.sh` script defines three example tasks:
- `Task 1`: Runs the `sample_task` function, which simulates a task running for 3 seconds.
- `Task 2`: Runs the `another_sample_task` function, which simulates a task running for 5 seconds and then returns an error.
- `Task 3`: Runs the `sleep 2` command, which simulates a task running for 2 seconds.
## How It Works
1. The `example_usage.sh` script sources the `task_formatter.sh` script to gain access to its functions.
2. The `example_usage.sh` script defines a set of tasks in an array, with each task specified as a description and a command separated by a colon (`:`).
3. The `example_usage.sh` script calls the `run_all_tasks` function from `task_formatter.sh`, passing the tasks as arguments.
4. The `run_all_tasks` function iterates over the tasks, splits each task into a description and a command, and calls the `run_task` function for each task.
5. The `run_task` function executes the task with a spinner and captures its output, displaying the task's status when it completes.
## Spinner
The Spinner was adjusted from https://github.com/sindresorhus/cli-spinners
## Example Output
```plaintext
Task 1 Running [⠼] [✔] Task 1 Completed 3s
Task 2 Running [⠧] [✘] Task 2 Error 5s
Error output:
Another sample task running
Task 3 Running [⠹] [✔] Task 3 Completed 2s

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

View file

@ -0,0 +1,49 @@
#!/bin/bash
# Source the formatter script
source ./task_formatter.sh
# Example function 1
example_function_1() {
echo -e "This is example function 1. $CHECK_MARK"
sleep 2
}
# Example function 2
example_function_2() {
echo "This is example function 2."
sleep 2
echo -e "Done $CHECK_MARK"
sleep 2
}
# Example function 3 with an error
example_function_3() {
echo -e "This is example function 3 and it will fail. $CROSS_MARK"
sleep 3
return 1
}
ask_reconfigure() {
read -p "Question? (y/n): " choice
case "$choice" in
y|Y ) return 0;;
n|N ) return 1;;
* ) echo "Invalid choice."; ask_reconfigure;;
esac
}
# Using the formatter to format the output of the example functions
print_header "Example Formatter" "https://github.com/pitterpatter22/TaskFormatter/blob/main/bash_task_formatter/example_new.sh"
format_output example_function_1 "Example Function 1"
format_output example_function_2 "Example Function 2"
format_output example_function_3 "Example Function 3"
format_output_with_input ask_reconfigure "Test Reconfiguring"
# Print final message
final_message "Example Formatter (Success Example) $CHECK_MARK" 0
final_message "Example Formatter (Failure Example) $CROSS_MARK" 1
# Exit with appropriate status
exit 0

View file

@ -0,0 +1,122 @@
#!/bin/bash
# Color variables
COLOR_RESET="\033[0m"
COLOR_BLUE="\033[1;34m"
COLOR_YELLOW="\033[1;33m"
COLOR_GREEN="\033[1;32m"
COLOR_RED="\033[1;31m"
# Symbols
CHECK_MARK="\033[1;32m✔\033[0m"
CROSS_MARK="\033[1;31m✘\033[0m"
# Function to print header with script name
print_header() {
local script_name=$1
echo -e "${COLOR_GREEN}"
echo " _____ _ _ _ "
echo " / ____| (_) | | | "
echo " | (___ _ __ ___ _| |_| |__ ___ ___ _ ____ _____ _ __ "
echo " \\___ \\| '_ \` _ \\| | __| '_ \\/ __|/ _ \\ '__\\ \\ / / _ \\ '__|"
echo " ____) | | | | | | | |_| | | \\__ \\ __/ | \\ V / __/ | "
echo " |_____/|_| |_| |_|_|\\__|_| |_|___/\\___|_| \\_/ \\___|_| "
echo -e "${COLOR_RESET}\n"
echo -e "${COLOR_GREEN}${script_name}${COLOR_RESET}\n\n\n"
}
# Function to display a spinner
spinner() {
local pid=$1
local func_name=$2
local delay=0.1
local spinstr=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
local temp
# Hide cursor
tput civis
while kill -0 "$pid" 2>/dev/null; do
for temp in "${spinstr[@]}"; do
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_YELLOW}Status: Running... %s${COLOR_RESET}" "$func_name" "$temp"
sleep $delay
done
done
# Show cursor
tput cnorm
}
# Function to handle cleanup on exit
cleanup() {
local exit_status=$?
tput cnorm
if [[ -f "$temp_file" ]]; then
rm -f "$temp_file"
fi
if [[ $exit_status -ne 0 ]]; then
printf "\n${COLOR_RED}Script interrupted or an error occurred${COLOR_RESET}\n"
fi
exit $exit_status
}
# Set trap for cleanup on exit or interrupt
trap cleanup EXIT
trap 'exit 130' INT
# Function to format the output of another function
format_output() {
local func_name=$1
local display_name=${2:-$func_name}
temp_file=$(mktemp)
# Run the function in the background and capture its output
( $func_name >"$temp_file" 2>&1 ) &
local pid=$!
spinner $pid "$display_name"
wait $pid
local exit_status=$?
if [ $exit_status -eq 0 ]; then
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_GREEN}Status: Finished ${CHECK_MARK}${COLOR_RESET} \n" "$display_name"
else
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_RED}Status: Error ${CROSS_MARK}${COLOR_RESET} \n" "$display_name"
fi
echo -e "${COLOR_BLUE}Output:${COLOR_RESET}"
cat "$temp_file"
echo "" # Ensure a new line after the function output
rm -f "$temp_file"
return $exit_status
}
# Function to format the output of another function that requires user input
format_output_with_input() {
local func_name=$1
local display_name=${2:-$func_name}
temp_file=$(mktemp)
echo -e "${COLOR_BLUE}Function: $display_name${COLOR_RESET} - ${COLOR_YELLOW}Status: Running...${COLOR_RESET}"
# Run the function in the foreground to handle interactive input
$func_name 2>&1 | tee "$temp_file"
local exit_status=${PIPESTATUS[0]}
if [ $exit_status -eq 0 ]; then
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_GREEN}Status: Finished ${CHECK_MARK}${COLOR_RESET} \n" "$display_name"
else
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_RED}Status: Error ${CROSS_MARK}${COLOR_RESET} \n" "$display_name"
fi
echo -e "${COLOR_BLUE}Output:${COLOR_RESET}"
cat "$temp_file"
echo "" # Ensure a new line after the function output
rm -f "$temp_file"
return $exit_status
}
# Export the functions for use in other scripts
export -f print_header
export -f format_output
export -f format_output_with_input
export COLOR_RESET COLOR_BLUE COLOR_YELLOW COLOR_GREEN COLOR_RED CHECK_MARK CROSS_MARK

View file

@ -0,0 +1,155 @@
#!/bin/bash
# Color variables
COLOR_RESET="\033[0m"
COLOR_BLUE="\033[1;34m"
COLOR_YELLOW="\033[1;33m"
COLOR_GREEN="\033[1;32m"
COLOR_RED="\033[1;31m"
# Symbols
CHECK_MARK="\033[1;32m✔\033[0m"
CROSS_MARK="\033[1;31m✘\033[0m"
# Function to center text
center_text() {
local text="$1"
local colorless_text=$(echo -e "$text" | sed 's/\x1b\[[0-9;]*m//g')
local width=$(tput cols)
local text_length=${#colorless_text}
local padding=$(( (width - text_length) / 2 ))
printf "%${padding}s%s\n" "" "$text"
}
# Function to print header with script name
print_header() {
local script_name=$1
local script_link=$2
clear
echo -e "${COLOR_GREEN}"
center_text " ______ _ ______ _ _ "
center_text "| ___ \ | | | ___| | | | | "
center_text "| |_/ / __ _ ___| |__ | |_ ___ _ __ _ __ ___ __ _| |_| |_ ___ _ __ "
center_text "| ___ \/ _\` / __| '_ \ | _/ _ \| '__| '_ \` _ \ / _\` | __| __/ _ \ '__|"
center_text "| |_/ / (_| \__ \ | | | | || (_) | | | | | | | | (_| | |_| || __/ | "
center_text "\____/ \__,_|___/_| |_| \_| \___/|_| |_| |_| |_|\__,_|\__|\__\___|_| "
center_text " "
echo -e "${COLOR_YELLOW}\n"
center_text "${script_name}"
echo -e "\n"
center_text "${script_link}"
echo -e "${COLOR_RESET}\n"
echo -e "\n\n"
}
# Function to display a spinner
spinner() {
local pid=$1
local func_name=$2
local delay=0.1
local spinstr=("⠋" "⠙" "⠹" "⠸" "⠼" "⠴" "⠦" "⠧" "⠇" "⠏")
local temp
# Hide cursor
tput civis
while kill -0 "$pid" 2>/dev/null; do
for temp in "${spinstr[@]}"; do
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_YELLOW}Status: Running... %s${COLOR_RESET}" "$func_name" "$temp"
sleep $delay
done
done
# Show cursor
tput cnorm
}
# Function to handle cleanup on exit
cleanup() {
local exit_status=$?
tput cnorm
if [[ -f "$temp_file" ]]; then
rm -f "$temp_file"
fi
if [[ $exit_status -ne 0 ]]; then
printf "\n${COLOR_RED}Script interrupted or an error occurred${COLOR_RESET}\n"
fi
exit $exit_status
}
# Set trap for cleanup on exit or interrupt
trap cleanup EXIT
trap 'exit 130' INT
# Function to format the output of another function
format_output() {
local func_name=$1
local display_name=${2:-$func_name}
temp_file=$(mktemp)
# Run the function in the background and capture its output
( $func_name >"$temp_file" 2>&1 ) &
local pid=$!
spinner $pid "$display_name"
wait $pid
local exit_status=$?
if [ $exit_status -eq 0 ]; then
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_GREEN}Status: Finished ${CHECK_MARK}${COLOR_RESET} \n" "$display_name"
else
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_RED}Status: Error ${CROSS_MARK}${COLOR_RESET} \n" "$display_name"
fi
echo -e "${COLOR_BLUE}Output:${COLOR_RESET}"
cat "$temp_file"
echo "" # Ensure a new line after the function output
rm -f "$temp_file"
return $exit_status
}
# Function to format the output of another function that requires user input
format_output_with_input() {
local func_name=$1
local display_name=${2:-$func_name}
temp_file=$(mktemp)
echo -e "${COLOR_BLUE}Function: $display_name${COLOR_RESET} - ${COLOR_YELLOW}Status: Running...${COLOR_RESET}"
# Run the function in the foreground to handle interactive input
$func_name 2>&1 | tee "$temp_file"
local exit_status=${PIPESTATUS[0]}
if [ $exit_status -eq 0 ]; then
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_GREEN}Status: Finished ${CHECK_MARK}${COLOR_RESET} \n" "$display_name"
else
printf "\r${COLOR_BLUE}Function: %s${COLOR_RESET} - ${COLOR_RED}Status: Error ${CROSS_MARK}${COLOR_RESET} \n" "$display_name"
fi
echo "" # Ensure a new line after the function output
rm -f "$temp_file"
return $exit_status
}
# Function to print final message
final_message() {
local script_name=$1
local success=$2
if [ -z "$success" ]; then
success=1
fi
if [ "$success" -eq 0 ]; then
echo -e "\n${COLOR_GREEN}${script_name} completed successfully!${COLOR_RESET}\n"
else
echo -e "\n${COLOR_RED}${script_name} encountered errors!${COLOR_RESET}\n"
fi
}
# Export the functions for use in other scripts
export -f print_header
export -f format_output
export -f format_output_with_input
export -f final_message
export COLOR_RESET COLOR_BLUE COLOR_YELLOW COLOR_GREEN COLOR_RED CHECK_MARK CROSS_MARK

View file

@ -0,0 +1,73 @@
# Formatter and Example
This project demonstrates the use of a Python decorator to format the output of functions, showing a running spinner, and indicating success or failure with colored and formatted text. It includes two files: `formatter.py` and `example.py`.
## Install
`pip install TaskFormatter`
## Example Output
![](https://github.com/seanssmith/CLI-Formatter/blob/main/python_task_importer/pythonformatterexample.gif)
## Files
1. **formatter.py**: This file contains the `format_output` decorator that provides the desired formatting for function output.
2. **example.py**: This file demonstrates how to use the `format_output` decorator by applying it to two example functions, one that succeeds and one that fails.
## Features
- **Spinner Animation**: Displays a spinner animation while the function is running.
- **Success/Failure Indication**: Shows a green checkmark for successful completion and a red cross for failures.
- **Exception Handling**: Exceptions are displayed in red and underlined.
- **Function Numbering**: Each function is prefixed with a unique number.
- **Text Formatting**: Function names are displayed in bold and underlined.
- **Separation of Outputs**: Outputs from different functions are separated by a line for clarity.
## Usage
### 1. formatter.py
The `format_output` decorator is defined in this file. It wraps a function to display the running spinner, success/failure message, and handle exceptions.
### 2. example.py
The `example.py` file demonstrates the use of the `format_output` decorator. It includes two functions, one that succeeds and one that fails, to showcase the decorator's functionality.
```
from TaskFormatter import format_output
from time import sleep
@format_output
def successful_function():
print("Starting")
sleep(2)
print("Function logic output")
@format_output
def failing_function():
print("Just Started")
sleep(2)
print("Running")
sleep(2)
print("Done")
raise ValueError("Custom Error")
if __name__ == "__main__":
successful_function()
try:
failing_function()
except Exception as e:
print("Caught an exception: ", e)
```
## Running the Example
1. Save formatter.py and example.py in the same directory.
2. Run example.py using the command:
```
python example.py
```
You should see the spinner, success, and failure messages formatted as described, with appropriate separation and formatting for each function's output.

View file

@ -0,0 +1,91 @@
import time
import sys
import multiprocessing
from functools import wraps
import signal
import io
# Global counter for function numbering
function_counter = multiprocessing.Value('i', 0)
def spinner(function_number, func_name, spinner_done, print_lock):
spinner_gen = spinner_gen_func()
while not spinner_done.is_set():
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[94m{function_number}. {func_name} Running [{next(spinner_gen)}] \033[0m")
sys.stdout.flush()
time.sleep(0.1)
def spinner_gen_func():
while True:
for cursor in "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏":
yield cursor
def format_output(func):
@wraps(func)
def wrapper(*args, **kwargs):
with function_counter.get_lock():
function_counter.value += 1
function_number = function_counter.value
start_time = time.time()
func_name = func.__name__
spinner_done = multiprocessing.Event()
print_lock = multiprocessing.Lock()
spin_process = multiprocessing.Process(target=spinner, args=(function_number, func_name, spinner_done, print_lock))
spin_process.start()
def handle_exit(signum, frame):
raise SystemExit("Process exited")
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)
buffer = io.StringIO()
original_stdout = sys.stdout
sys.stdout = buffer
try:
func(*args, **kwargs)
duration = time.time() - start_time
spinner_done.set()
spin_process.join()
sys.stdout = original_stdout
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[92m{function_number}. {func_name} Completed [✓] in {duration:.2f}s\033[0m\n")
sys.stdout.flush()
output = buffer.getvalue().strip()
if output:
sys.stdout.write(output + "\n")
sys.stdout.write('\n---\n') # Add separator before the next function
except SystemExit as e:
duration = time.time() - start_time
spinner_done.set()
spin_process.join()
sys.stdout = original_stdout
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[91m{function_number}. {func_name} Failed [✗] in {duration:.2f}s\033[0m\n")
sys.stdout.flush()
output = buffer.getvalue().strip()
if output:
sys.stdout.write(output + "\n")
print(f"\033[91m\033[4m{e}\033[0m")
sys.stdout.write('\n---\n') # Add separator before the next function
raise e
except Exception as e:
duration = time.time() - start_time
spinner_done.set()
spin_process.join()
sys.stdout = original_stdout
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[91m{function_number}. {func_name} Failed [✗] in {duration:.2f}s\033[0m\n")
sys.stdout.flush()
output = buffer.getvalue().strip()
if output:
sys.stdout.write(output + "\n")
print(f"\033[91m\033[4m{e}\033[0m")
sys.stdout.write('\n---\n') # Add separator before the next function
raise e
return wrapper

View file

@ -0,0 +1 @@
from .TaskFormatter import format_output

View file

@ -0,0 +1,91 @@
import time
import sys
import multiprocessing
from functools import wraps
import signal
import io
# Global counter for function numbering
function_counter = multiprocessing.Value('i', 0)
def spinner(function_number, func_name, spinner_done, print_lock):
spinner_gen = spinner_gen_func()
while not spinner_done.is_set():
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[94m{function_number}. {func_name} Running [{next(spinner_gen)}] \033[0m")
sys.stdout.flush()
time.sleep(0.1)
def spinner_gen_func():
while True:
for cursor in "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏":
yield cursor
def format_output(func):
@wraps(func)
def wrapper(*args, **kwargs):
with function_counter.get_lock():
function_counter.value += 1
function_number = function_counter.value
start_time = time.time()
func_name = func.__name__
spinner_done = multiprocessing.Event()
print_lock = multiprocessing.Lock()
spin_process = multiprocessing.Process(target=spinner, args=(function_number, func_name, spinner_done, print_lock))
spin_process.start()
def handle_exit(signum, frame):
raise SystemExit("Process exited")
signal.signal(signal.SIGTERM, handle_exit)
signal.signal(signal.SIGINT, handle_exit)
buffer = io.StringIO()
original_stdout = sys.stdout
sys.stdout = buffer
try:
func(*args, **kwargs)
duration = time.time() - start_time
spinner_done.set()
spin_process.join()
sys.stdout = original_stdout
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[92m{function_number}. {func_name} Completed [✓] in {duration:.2f}s\033[0m\n")
sys.stdout.flush()
output = buffer.getvalue().strip()
if output:
sys.stdout.write(output + "\n")
sys.stdout.write('\n---\n') # Add separator before the next function
except SystemExit as e:
duration = time.time() - start_time
spinner_done.set()
spin_process.join()
sys.stdout = original_stdout
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[91m{function_number}. {func_name} Failed [✗] in {duration:.2f}s\033[0m\n")
sys.stdout.flush()
output = buffer.getvalue().strip()
if output:
sys.stdout.write(output + "\n")
print(f"\033[91m\033[4m{e}\033[0m")
sys.stdout.write('\n---\n') # Add separator before the next function
raise e
except Exception as e:
duration = time.time() - start_time
spinner_done.set()
spin_process.join()
sys.stdout = original_stdout
with print_lock:
sys.stdout.write(f"\r\033[1m\033[4m\033[91m{function_number}. {func_name} Failed [✗] in {duration:.2f}s\033[0m\n")
sys.stdout.flush()
output = buffer.getvalue().strip()
if output:
sys.stdout.write(output + "\n")
print(f"\033[91m\033[4m{e}\033[0m")
sys.stdout.write('\n---\n') # Add separator before the next function
raise e
return wrapper

View file

@ -0,0 +1 @@
from .TaskFormatter import format_output

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,24 @@
from TaskFormatter import format_output
from time import sleep
@format_output
def successful_function():
print("Starting")
sleep(2)
print("Function logic output")
@format_output
def failing_function():
print("Just Started")
sleep(2)
print("Running")
sleep(2)
print("Done")
raise ValueError("Custom Error")
if __name__ == "__main__":
successful_function()
try:
failing_function()
except Exception as e:
print("Caught an exception: ", e)

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 KiB

View file

@ -0,0 +1,3 @@
# setup.cfg
[metadata]
description-file = README.md

View file

@ -0,0 +1,20 @@
# setup.py
from setuptools import setup, find_packages
setup(
name="TaskFormatter",
version="1.0.2",
description="A Python package for function execution status and spinner output",
long_description=open('README.md').read(),
long_description_content_type='text/markdown',
author="Sean Smith",
author_email="sean@ssmith.app",
url="https://github.com/pitterpatter22/TaskFormatter",
packages=find_packages(),
classifiers=[
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
],
python_requires='>=3.6',
)