Created
March 26, 2025 05:18
-
-
Save EvilSupahFly/d678316364c5716c7fe0b1026e5a9c40 to your computer and use it in GitHub Desktop.
rebuild.sh
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# Check if terminal supports colour output | |
supports_colour() { | |
if [ -t 1 ] && command -v tput &> /dev/null; then | |
local colours | |
colours=$(tput colors 2>/dev/null) | |
if [[ -n "$colours" && "$colours" -ge 8 ]]; then | |
return 0 # Colour support detected | |
fi | |
fi | |
return 1 # Colour not supported | |
} | |
# Display status messages with colours | |
report() { | |
local status | |
local message | |
local timestamp | |
local ERR_MSG | |
local PASSMSG | |
local NOTEMSG | |
local output_message | |
local log_message | |
status=$1 # F = failure, P = pass, N = notice (neutral), B = Blank | |
message="$2" | |
timestamp=$(date +"%Y-%m-%d %H:%M:%S") | |
ERR_MSG="[$timestamp] ERROR: " | |
PASSMSG="[$timestamp] SUCCESS: " | |
NOTEMSG="[$timestamp] NOTICE: " | |
# Ensure color variables are defined | |
if [[ -z "$RED" ]]; then | |
output_message="[$timestamp] $message" | |
log_message="[$timestamp] $message" # Plain message for log | |
else | |
case "$status" in | |
F) output_message="${RED}$ERR_MSG ${WHITE}$message${RESET}" | |
log_message="$ERR_MSG $message" ;; # Plain message for log | |
P) output_message="${GREEN}$PASSMSG ${WHITE}$message${RESET}" | |
log_message="$PASSMSG $message" ;; # Plain message for log | |
N) output_message="${YELLOW}$NOTEMSG ${WHITE}$message${RESET}" | |
log_message="$NOTEMSG $message" ;; # Plain message for log | |
B) output_message="${WHITE}$message${RESET}" | |
log_message="$message" ;; # Plain message for log | |
*) output_message="${RED}$ERR_MSG ${WHITE}$status is an unsupported status flag.${RESET}" | |
log_message="$ERR_MSG $status is an unsupported status flag." ;; # Plain message for log | |
esac | |
fi | |
# Output to terminal | |
echo -e "$output_message" | |
# Log to run.log | |
#doLog "rebuild" "$log_message" # Log the plain message | |
} | |
#doLog() { | |
# # Handle renaming of log files | |
# local base_name | |
# local log_message | |
# local log_file | |
# local backup_file | |
# local LOGCHECK | |
# | |
# local base_name="$1" | |
# local log_message="$2" | |
# local log_file="${base_name}.log" | |
# local backup_file="${base_name}.bak" | |
# local LOGCHECK=false | |
# | |
# if [ "$3" == "--check" ]; then | |
# AUTO=true | |
# LOGCHECK=true | |
# echo -e "Checking log file status...\n"; sleep 2 | |
# fi | |
# if [ "$WRITING_LOG" == "false" ]; then | |
# # Check if the log file already exists | |
# if [ -e "$log_file" ]; then | |
# # Check if the backup file already exists | |
# if [ -e "$backup_file" ]; then | |
# if [ "$AUTO" = "false" ]; then | |
# echo "${backup_file} already exists. Do you want to rename it? (y/n) "; read -r response | |
# else | |
# response="n" | |
# fi | |
# if [[ "$response" == "y" ]]; then | |
# echo "Please enter a new name for the backup file: "; read -r new_name | |
# mv -v "$backup_file" "$new_name" | |
# echo -e "Renamed existing backup file to $new_name\n" | |
# else | |
# rm "$backup_file" | |
# echo -e "Deleted existing backup file ${backup_file}\n" | |
# fi | |
# fi | |
# # Rename existing log file to .bak | |
# mv -v "$log_file" "$backup_file" | |
# echo -e "Renamed existing log file to ${backup_file}\n" | |
# fi | |
# if [ "$LOGCHECK" == "false" ]; then | |
# # Write the log message to the new log file | |
# echo "$log_message" > "$log_file" || echo "Failed to write to ${log_file}." | |
# fi | |
# WRITING_LOG=true # Set to true after the first write | |
# else | |
# if [ "$LOGCHECK" == "false" ]; then | |
# # Write the log message to the existing log file | |
# echo "$log_message" >> "$log_file" || echo "Failed to write to ${log_file}." | |
# fi | |
# fi | |
# if [ "$LOGCHECK" == "true" ]; then | |
# AUTO=false | |
# LOGCHECK=false | |
# fi | |
#} | |
#doLog "rebuild" "Clean-up log." "--check" | |
# Verify checksum of copied file | |
verify_checksum() { | |
local file | |
local expected_checksum | |
file="$1" | |
expected_checksum="$2" | |
# Calculate checksum of the file | |
actual_checksum=$(sha256sum "$file" | awk '{print $1}') | |
if [[ "$actual_checksum" == "$expected_checksum" ]]; then | |
report P "Checksum verification passed for '$file'." | |
else | |
report F "Checksum mismatch for '$file'. Expected: $expected_checksum, Found: $actual_checksum" | |
fi | |
} | |
# Update SHA256 checksum in YAML file | |
#update_yaml_sha256() { | |
# local yaml_file="$1" | |
# local file_name="$2" | |
# local new_sha256="$3" | |
# | |
# # Check if the YAML file exists | |
# if [[ ! -f "$yaml_file" ]]; then | |
# report F "YAML file '$yaml_file' does not exist." | |
# exit 1 | |
# fi | |
# | |
# echo "Checking for '$file_name' in '$yaml_file'" | |
# | |
# # Look for references to the file with 'url:' or 'path:' | |
# file_reference=$(grep -E "url:|path:" "$yaml_file" | grep "$file_name") | |
# | |
# if [[ -n "$file_reference" ]]; then | |
# echo "Found reference to '$file_name' in YAML file." | |
# | |
# # Show the original line (before 'url:' change) | |
# echo "Original line: '$file_reference'" | |
# | |
# # Replace 'url:' with 'path:' if found | |
# if [[ "$file_reference" =~ ^[[:space:]]*url: ]]; then | |
# indent=$(echo "$file_reference" | sed 's/\S.*//') # Extract leading spaces | |
# indent="${indent% }" # Remove trailing spaces from the indent | |
# new_line="$indent path: $file_name" # Ensure no extra space is added | |
# | |
# # Show the proposed new line | |
# echo "Proposed change: '$new_line'" | |
# | |
# # Confirm change before applying | |
# if [ "$AUTO" = false ]; then | |
# read -p "Do you want to apply this change to 'url:' -> 'path:'? (y/n): " confirm | |
# else | |
# confirm="y" | |
# fi | |
# | |
# if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then | |
# sed -i "s|$file_reference|$new_line|" "$yaml_file" | |
# report N "Replaced 'url:' with 'path:' for $file_name." | |
# else | |
# report N "Skipped modifying the line for '$file_name'." | |
# fi | |
# fi | |
# | |
# # Find the old checksum if it exists | |
# old_sha256=$(grep -A 1 "$file_name" "$yaml_file" | grep "sha256:" | awk '{print $2}') | |
# | |
# if [[ -n "$old_sha256" ]]; then | |
# # Show the old checksum | |
# echo "Old SHA256: $old_sha256" | |
# | |
# # Show the proposed new checksum | |
# echo "Proposed SHA256 change: $new_sha256" | |
# | |
# # Confirm checksum change before applying | |
# if [ "$AUTO" = false ]; then | |
# read -p "Do you want to update the SHA256 for '$file_name'? (y/n): " confirm_checksum | |
# else | |
# confirm_checksum="y" | |
# fi | |
# | |
# if [[ "$confirm_checksum" == "y" || "$confirm_checksum" == "Y" ]]; then | |
# sed -i "s|sha256: $old_sha256|sha256: $new_sha256|" "$yaml_file" | |
# report N "SHA256 for '$file_name' updated to $new_sha256." | |
# else | |
# report N "Skipped updating the SHA256 for '$file_name'." | |
# fi | |
# else | |
# # If no checksum exists, show the proposed new checksum | |
# echo "No existing SHA256 found for '$file_name'. Proposed SHA256: $new_sha256" | |
# read -p "Do you want to add the SHA256 for '$file_name'? (y/n): " confirm_add_sha256 | |
# if [[ "$confirm_add_sha256" == "y" || "$confirm_add_sha256" == "Y" ]]; then | |
# # Add the new SHA256 if not found | |
# sed -i "/$file_name/a\ \ sha256: $new_sha256" "$yaml_file" | |
# report N "SHA256 added for '$file_name': $new_sha256." | |
# else | |
# report N "Skipped adding the SHA256 for '$file_name'." | |
# fi | |
# fi | |
# | |
# # Verify checksum | |
# verify_checksum "$DEST/$(basename "$file_name")" "$new_sha256" | |
# | |
# else | |
# echo "No reference found for '$file_name' in YAML file." | |
# fi | |
#} | |
# Update SHA256 checksum in YAML file | |
#update_yaml_sha256() { | |
# local yaml_file="$1" | |
# local new_file_name="$2" | |
# local new_sha256="$3" | |
# | |
# # Extract base name (without version) from the new file name | |
# local base_name=$(echo "$new_file_name" | sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+.*//') | |
# | |
# # Check if the YAML file exists | |
# if [[ ! -f "$yaml_file" ]]; then | |
# report F "YAML file '$yaml_file' does not exist." | |
# exit 1 | |
# fi | |
# | |
# echo "Checking for entries matching '$base_name' in '$yaml_file'" | |
# | |
# # Find the existing file entry ignoring version numbers | |
# old_file_entry=$(grep -E "url:|path:" "$yaml_file" | grep "$base_name") | |
# | |
# if [[ -n "$old_file_entry" ]]; then | |
# echo "Found reference to '$base_name' in YAML file." | |
# | |
# # Extract the full old file name (including version and extension) | |
# old_file_name=$(echo "$old_file_entry" | awk '{print $2}') | |
# | |
# echo "Old file reference: '$old_file_name'" | |
# echo "New file reference: '$new_file_name'" | |
# | |
# # Replace 'url:' with 'path:' if found | |
# if [[ "$old_file_entry" =~ ^[[:space:]]*url: ]]; then | |
# # Extract leading spaces (indentation) | |
# indent="${old_file_entry%%[^[:space:]]*}" | |
# | |
# # Construct the new line correctly | |
# if [[ "$old_file_entry" =~ ^[[:space:]]*url: ]]; then | |
# new_line="${indent}path: $file_name" # Replace 'url:' with 'path:' | |
# else | |
# new_line="${indent}${old_file_entry#* } $file_name" # Preserve original key (path:) | |
# fi | |
# | |
# # Use an exact match replacement | |
# sed -i "s|^$old_file_entry\$|$new_line|" "$yaml_file" | |
# | |
# report N "Proposed change: '$new_line'" | |
# | |
# if [ "$AUTO" = false ]; then | |
# read -p "Apply change to 'url:' -> 'path:'? (y/n): " confirm | |
# else | |
# confirm="y" | |
# fi | |
# | |
# if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then | |
# sed -i "s|$old_file_name|$new_file_name|" "$yaml_file" | |
# report N "Replaced '$old_file_name' with '$new_file_name'." | |
# else | |
# report N "Skipped modifying the line for '$base_name'." | |
# fi | |
# fi | |
# | |
# # Find and update the old checksum | |
# old_sha256=$(grep -A 1 "$old_file_name" "$yaml_file" | grep "sha256:" | awk '{print $2}') | |
# | |
# if [[ -n "$old_sha256" ]]; then | |
# echo "Old SHA256: $old_sha256" | |
# echo "New SHA256: $new_sha256" | |
# | |
# if [ "$AUTO" = false ]; then | |
# read -p "Update SHA256 for '$new_file_name'? (y/n): " confirm_checksum | |
# else | |
# confirm_checksum="y" | |
# fi | |
# | |
# if [[ "$confirm_checksum" == "y" || "$confirm_checksum" == "Y" ]]; then | |
# sed -i "s|sha256: $old_sha256|sha256: $new_sha256|" "$yaml_file" | |
# report N "SHA256 for '$new_file_name' updated to $new_sha256." | |
# else | |
# report N "Skipped updating the SHA256 for '$new_file_name'." | |
# fi | |
# else | |
# echo "No existing SHA256 found for '$old_file_name'. Proposed SHA256: $new_sha256" | |
# read -p "Add SHA256 for '$new_file_name'? (y/n): " confirm_add_sha256 | |
# if [[ "$confirm_add_sha256" == "y" || "$confirm_add_sha256" == "Y" ]]; then | |
# sed -i "/$old_file_name/a\ \ sha256: $new_sha256" "$yaml_file" | |
# report N "SHA256 added for '$new_file_name': $new_sha256." | |
# else | |
# report N "Skipped adding the SHA256 for '$new_file_name'." | |
# fi | |
# fi | |
# | |
# # Verify checksum | |
# verify_checksum "$DEST/$(basename "$new_file_name")" "$new_sha256" | |
# else | |
# report F "No reference found for '$base_name' in YAML file." | |
# fi | |
#} | |
update_yaml_sha256() { | |
local yaml_file="$1" | |
local new_file_name="$2" | |
local new_sha256="$3" | |
# Extract base name by removing version numbers and extensions | |
local base_name=$(echo "$new_file_name" | sed -E 's/-[0-9]+\.[0-9]+\.[0-9]+.*//') | |
if [[ ! -f "$yaml_file" ]]; then | |
report F "Line $LINENO: YAML file '$yaml_file' does not exist." | |
exit 1 | |
fi | |
report N "Checking for entries matching '$base_name' in '$yaml_file'" | |
# Escape special characters for sed | |
escaped_old_file_name=$(printf '%s\n' "$old_file_name" | sed 's/[.[\*^$]/\\&/g') | |
escaped_new_file_name=$(printf '%s\n' "$new_file_name" | sed 's/[.[\*^$]/\\&/g') | |
# Find the existing entry (either `url:` or `path:`) and preserve indentation | |
old_file_entry=$(grep -E "^[[:space:]]*(url|path): .*$escaped_old_file_name" "$yaml_file") | |
if [[ -n "$old_file_entry" ]]; then | |
indent="${old_file_entry%%[^[:space:]]*}" # Extract leading whitespace | |
# If it's a `url:`, replace with `path:`, otherwise just update filename | |
if [[ "$old_file_entry" =~ ^[[:space:]]*url: ]]; then | |
new_line="${indent}path: $new_file_name" | |
else | |
new_line="${indent}path: $new_file_name" | |
fi | |
# Replace in YAML file | |
sed -i "s|$escaped_old_file_name|$escaped_new_file_name|" "$yaml_file" | |
report N "Updated entry: $old_file_entry -> $new_line" | |
fi | |
# Update the SHA256 checksum | |
old_sha256_entry=$(grep -A 1 "$escaped_old_file_name" "$yaml_file" | grep "sha256:") | |
if [[ -n "$old_sha256_entry" ]]; then | |
indent="${old_sha256_entry%%[^[:space:]]*}" # Preserve indentation | |
old_sha256_value=$(echo "$old_sha256_entry" | awk '{print $2}') | |
# Use sed to replace the old sha256 value with the new one | |
sed -i "s|sha256: $old_sha256_value|sha256: $new_sha256|" "$yaml_file" | |
report N "Updated SHA256: $new_sha256" | |
else | |
report F "No reference found for '$base_name' in YAML file." | |
fi | |
} | |
# Remove old file or directory | |
remove_obj() { | |
if [ -e "$1" ]; then | |
report N "Removing old version of $1..." | |
rm -rf "$1" # Handles both files and directories | |
report N "Done." | |
fi | |
} | |
# Ensure script is run in a Python virtual environment | |
check_virtual_env() { | |
[[ -z "$VIRTUAL_ENV" ]] && { echo -e "\nThis script must be run in a Python virtual environment.\n"; exit 1; } | |
} | |
# Ensure the specified directory exists | |
check_directory() { | |
[[ ! -d "$1" ]] && { report F "Directory '$1' does not exist."; exit 1; } | |
} | |
# Ensure the 'build' module is installed | |
ensure_build_installed() { | |
report N "Inspecting Python environment..."; sleep 2 | |
if ! pip show build > /dev/null; then | |
report N "'build' module not found. Installing..." | |
pip install build | |
fi | |
report P "'build' module confirmed." | |
} | |
# Build the project | |
build_project() { | |
report N "Attempting to run 'python -m build $PROJ_DIR'..." | |
if ! python -m build -s -w $1 > /dev/null; then | |
report F "Project build for $1 failed." | |
exit 1 | |
else | |
report P "Project build for $1 succeeded." | |
fi | |
} | |
# List SHA256 values of files in 'dist' | |
list_sha256() { | |
report B "SHA256 values of files in 'dist':" | |
for file in "${files[@]}"; do | |
sha256=$(sha256sum "$file" | awk '{print $1}') | |
report B "$sha256 $file" | |
sha256_values["$file"]="$sha256" # Store SHA256 in the associative array | |
done | |
report B "SHA256 Values in \$sha256_values:" | |
for file in "${!sha256_values[@]}"; do | |
report B "$file: ${sha256_values[$file]}" | |
done | |
} | |
# Copy .tar.gz (or other) files to a destination directory | |
copy_files() { | |
local dest="$1" | |
mkdir -p "$dest" | |
# Prompt for file type if no .tar.gz files are found | |
if [ "$AUTO" = false ]; then | |
read -p "Enter file extension to copy (e.g., .tar.gz, .whl, .zip): " file_ext | |
else | |
file_ext=$DEFAULT_COPY | |
fi | |
if [[ -z "$file_ext" ]]; then | |
report F "No file extension provided. Exiting." | |
exit 1 | |
fi | |
# Filter files based on extension | |
files_to_copy=() | |
for file in "${files[@]}"; do | |
if [[ "$file" == *"$file_ext" ]]; then | |
files_to_copy+=("$file") | |
fi | |
done | |
# If no files found, ask user if they want to try a different extension | |
if [ ${#files_to_copy[@]} -eq 0 ]; then | |
report F "No $file_ext files found in 'dist'." | |
exit 1 | |
fi | |
# Confirm each file for copying | |
for file in "${files_to_copy[@]}"; do | |
if [ "$AUTO" = false ]; then | |
read -p "Are you sure you want to copy '$file' to '$dest'? (y/n): " confirm | |
else | |
confirm="y" | |
fi | |
if [[ "$confirm" == "y" || "$confirm" == "Y" ]]; then | |
cp -v "$file" "$dest" | |
report P "File '$file' copied to '$dest'."; sleep 2 | |
else | |
report N "Skipped copying '$file'."; sleep 2 | |
fi | |
done | |
} | |
# Display help | |
show_help() { | |
report B "" | |
report B "Usage: $0 [folder] [--auto] [--help] [--debug]" | |
report B "Options:" | |
report B " folder Required folder name." | |
report B " --auto Optional flag to enable automatic mode." | |
report B " --help Display this message and quit (overrides all other options)." | |
exit 1 | |
} | |
# Define colours if supported by the terminal | |
if supports_colour; then | |
RED=$(tput bold; tput setaf 9) # Bright Red | |
GREEN=$(tput bold; tput setaf 10) # Bright Green | |
YELLOW=$(tput bold; tput setaf 11) # Bright Yellow | |
BLUE=$(tput bold; tput setaf 12) # Bright Blue | |
CYAN=$(tput bold; tput setaf 14) # Bright Cyan | |
WHITE=$(tput bold; tput setaf 15) # Bright White | |
RESET=$(tput sgr0) # Reset colours | |
else | |
RED=""; GREEN=""; YELLOW=""; BLUE=""; CYAN=""; WHITE=""; RESET="" | |
fi | |
# Check for command-line arguments | |
if [[ $# -eq 0 ]]; then | |
report F "Folder name cannot be blank." | |
show_help | |
fi | |
# Declare the associative array for SHA256 values | |
declare -A sha256_values | |
LAUNCH_DIR=$(dirname "$(readlink -f "$0")") | |
PROJ_DIR="" | |
AUTO=false | |
DEBUG=false | |
SKIP_DO_THIS=false | |
DEFAULT_DEST="/home/seann/Amulet-Flatpak-Testing/testing" | |
DEFAULT_YAML="${DEFAULT_DEST}/pip-gen.yaml" | |
DEFAULT_COPY=".tar.gz" | |
WRITING_LOG=false | |
while [[ "$1" != "" ]]; do | |
case "$1" in | |
--help) | |
show_help | |
shift | |
;; | |
--auto) | |
AUTO=true | |
report N "AUTO mode active" | |
shift | |
;; | |
--yaml=*) | |
DO_THIS_YAML="${1#*=}" | |
DEFAULT_YAML="${DEFAULT_DEST}/${DO_THIS_YAML}" | |
report N "YAML manifest ${DEFAULT_YAML} will be used." | |
shift | |
;; | |
--skip-do-this) | |
SKIP_DO_THIS=true | |
report N "Skipping 'do_this.sh'" | |
shift | |
;; | |
*) | |
if [[ -z $PROJ_DIR ]]; then | |
PROJ_DIR=$1 | |
else | |
report F "Error: Only one folder name is allowed." | |
show_help | |
fi | |
shift | |
;; | |
esac | |
done | |
# Main script execution | |
report N "Verifying VENV..."; sleep 2 | |
check_virtual_env #; report N "\n check_virtual_env: LINE: $LINENO\n" | |
report P "VENV confirmed."; sleep 2 #; report N "\n LINE: $LINENO\n" | |
report N "Verifying $PROJ_DIR..."; sleep 2 | |
check_directory "$PROJ_DIR" #; report N "\n check_directory \"$PROJ_DIR\" LINE: $LINENO\n" | |
report P "$PROJ_DIR confirmed."; sleep 2 | |
remove_obj "$PROJ_DIR/dist" #; report N "\n remove_obj \"$PROJ_DIR/dist\" LINE: $LINENO\n" | |
ensure_build_installed #; report N "\n ensure_build_installed LINE: $LINENO\n" | |
build_project "$PROJ_DIR" #; report N "\n build_project \"$PROJ_DIR\" LINE: $LINENO\n" | |
# Collect files in 'dist' | |
files=($PROJ_DIR/dist/*) | |
report B "Files found in '$PROJ_DIR/dist':" | |
for dfile in "${files[@]}"; do | |
report B "$dfile"; sleep 1 | |
done | |
# List SHA256 values | |
list_sha256 #; report N "\n list_sha256 LINE: $LINENO\n" | |
# Prompt for destination directory | |
if [ "$AUTO" = false ]; then | |
read -p "Enter destination directory to copy files ('q' to quit, default: '$DEFAULT_DEST'): " DEST | |
fi | |
if [[ "$DEST" == "q" ]]; then | |
report F "Exiting without copying files." | |
exit 0 | |
fi | |
DEST="${DEST:-$DEFAULT_DEST}" | |
report N "\n\n copy_files \"$DEST\" LINE: $LINENO\n\n" | |
copy_files "$DEST" | |
# Prompt for YAML file | |
if [ "$AUTO" = false ]; then | |
read -p "Enter path to YAML file (default: '$DEFAULT_YAML'): " yaml_dest | |
fi | |
yaml_dest="${yaml_dest:-$DEFAULT_YAML}" | |
# Ensure YAML file exists | |
if [[ ! -f "$yaml_dest" ]]; then | |
report F "YAML file '$yaml_dest' does not exist." | |
exit 1 | |
fi | |
# Update SHA256 in YAML for each file | |
for nfile in "${files[@]}"; do | |
bname=$(basename "$file") | |
new_sha256="${sha256_values[$nfile]}" | |
report N "\n\n update_yaml_sha256 \"$yaml_dest\" \"$bname\" \"$new_sha256\" LINE: $LINENO\n\n" | |
update_yaml_sha256 "$yaml_dest" "$bname" "$new_sha256"; sleep 1 | |
report B "" | |
done | |
if [ "$SKIP_DO_THIS" = true ]; then | |
exit 0 | |
else | |
echo -e "\a"; echo -e "\a" | |
if [ -f "${DEST}/do_this.sh" ]; then | |
cd $DEST | |
if [ "$AUTO" = false ]; then | |
read -p "do_this.sh located at '$DEST' - run it now?: " do_this | |
else | |
do_this="y" | |
fi | |
if [ $do_this = "y" ]; then | |
./do_this.sh --auto --debug --yaml="$DEFAULT_YAML" | |
fi | |
else | |
echo "Script do_this.sh does not exist in $DEST - skipping launch." | |
fi | |
cd $LAUNCH_DIR | |
fi |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment