#!/usr/bin/zsh setopt extendedglob VERSION="0.0.2" # Sat-2024-03-13 ZREP="Zsh Repository Tool" # Define the default path to .zreprc ZREP_CONFIG="${HOME}/.zreprc" function zrep_fpath() { local base_dir="${1}" # Check if the base directory exists if [[ ! -d "${base_dir}" ]]; then echo "Error: Base directory '${base_dir}' does not exist." return 1 fi # Add directories containing at least one file to fpath for dir in ${base_dir}/**/*(/N); do if [[ -n $(ls -A "${dir}/") ]]; then fpath=(${dir} $fpath) fi done } if [[ ${1} != "init" ]]; then zrep_fpath ${HOME}/.zrep/functions autoload -Uz zini fi # Define a list of colors available to use in themes typeset -A base_colors=( [green]="\033[0;32m" [yellow]="\033[1;33m" [red]="\033[0;31m" [bold_yellow]="\033[1;33m" [magenta]="\033[0;35m" [white]="\033[1;37m" [green_bg]="\033[42m" [white_on_green]="\033[1;37m\033[42m" # Combined color [end]="\033[0m" [black]="\033[0;30m" [blue]="\033[0;34m" [cyan]="\033[0;36m" [bold_black]="\033[1;30m" [bold_red]="\033[1;31m" [bold_green]="\033[1;32m" [bold_blue]="\033[1;34m" [bold_magenta]="\033[1;35m" [bold_cyan]="\033[1;36m" [bold_white]="\033[1;37m" [black_bg]="\033[40m" [red_bg]="\033[41m" [yellow_bg]="\033[43m" [blue_bg]="\033[44m" [magenta_bg]="\033[45m" [cyan_bg]="\033[46m" [white_bg]="\033[47m" [underline]="\033[4m" [italic]="\033[3m" ) # Define the global associative array to hold the current theme declare -A current_theme zrep_load_theme() { local theme_name="$1" local theme_file="${config[main_zrep_install_dir]}/themes/${theme_name}" if [[ ! -f "$theme_file" ]]; then echo "Error: Theme file for '${theme_name}' not found. Falling back to the 'classic' theme." theme_file="${config[main_zrep_install_dir]}/themes/classic" fi # Source the theme file, which should define 'theme_colors' source "$theme_file" # Copy 'theme_colors' to 'current_theme' for key value in ${(kv)theme_colors}; do current_theme[$key]="$value" done } function zrep_main_version_string() { echo "${base_colors[bold_black]}${base_colors[white_bg]} ${ZREP} ${base_colors[end]}${base_colors[bold_white]}${base_colors[black_bg]} ${VERSION} ${base_colors[end]}" } function zrep_version() { zrep_msg info "\nCreated by kekePower - 2024" zrep_msg info "License: MIT" zrep_msg info "https://git.kekepower.com/kekePower/zrep/" zrep_msg info "Please see '${base_colors[${current_theme[help]}]}${ZSH_SCRIPT:t} help${base_colors[end]}${base_colors[${current_theme[info]}]}' for more info${base_colors[end]}" exit } # This function is used to display messages and use colors # from the loaded theme. function zrep_msg() { local msg_type="$1" local message="$2" local color="${base_colors[end]}" # Default color is NONE # Retrieve the color key from the current theme local theme_color_key="${current_theme[$msg_type]}" # Check if a valid color was found based on the key if [[ -n "${base_colors[$theme_color_key]}" ]]; then color="${base_colors[$theme_color_key]}" else # Handle invalid theme color key if needed echo "Warning: Theme color key '$theme_color_key' not found. Using default." >&2 fi printf "%b\n" "${color}${message}${base_colors[end]}" } # Function to check if a given string exists in ~/.zshrc function zrep_find_string() { local searchString="$1" local found=0 while IFS= read -r line || [[ -n $line ]]; do if [[ "$line" == *"$searchString"* ]]; then found=1 break fi done < "${HOME}/.zshrc" echo $found } function zrep_check_for_deps() { # Array of required external programs local required_programs=('jq' "${config[global_downloader]}") # Iterate over the array for program in "${required_programs[@]}"; do # Check if the program is available in the system's PATH if ! command -v "$program" &> /dev/null; then # Program not found, display a message echo "Required program not found: $program" fi done } # The first step after downloading zrep. # Here we create the default file and directory structure function zrep_init() { local zshrc_file="${HOME}/.zshrc" local zrep_addons="${HOME}/.zrep_addons" local install_dir # Check if .zreprc exists if [[ ! -f ${ZREP_CONFIG} ]]; then echo "${ZREP_CONFIG} not found. Creating it..." # Prompt user for install directory read "install_dir?Enter zrep installation directory [${HOME}/.zrep]: " install_dir=${install_dir:-"${HOME}/.zrep"} mkdir -p ${install_dir} read "downloader?Choose command to download packages [curl, wget, wget2]: " if [[ ${downloader} != curl && ${downloader} != wget && ${downloader} != wget2 ]]; then echo "Invalid choice: '$downloader'. Try again." read "downloader?Choose command to download packages [curl, wget, wget2] " if [[ ${downloader} != curl && ${downloader} != wget && ${downloader} != wget2 ]]; then echo "Invalid choice: '$downloader'. Exiting." exit fi fi # Write to .zreprc cat > "${ZREP_CONFIG}" <> ${zshrc_file} autoload -Uz zini fi # Check if .zshrc already sources .zrep_addons, if not, add it if [[ $(zrep_find_string "source ${zrep_addons}") -eq 0 ]]; then echo "Adding source command for .zrep_addons to .zshrc..." echo "source ${zrep_addons}" >> "${zshrc_file}" fi # Create or update the .zrep_addons file if [[ ! -f ${zrep_addons} ]]; then echo "Creating file ${zrep_addons}..." touch ${install_dir}/.addons cat > "${zrep_addons}" < "${installed_json}" fi # Update or add the script entry jq --arg author "$author" --arg script "$script" --arg version "$version" \ 'if has($author) then .[$author] |= map(if .script == $script then .version = $version else . end) | if .[$author] | all(.script != $script) then .[$author] += [{"script": $script, "version": $version}] else . end else .[$author] = [{"script": $script, "version": $version}] end' "$installed_json" > "$installed_json.tmp" && mv "$installed_json.tmp" "$installed_json" zrep_msg info " - Package '$script' by '$author' version $version installed/updated successfully." } # Function to list installed packages from installed.json function zrep_list_installed_packages() { zrep_installed_json # Check if installed.json exists and is not empty if [[ ! -s "${installed_json}" ]]; then zrep_msg info "\nNo packages installed." return fi # Parse installed.json and list packages zrep_msg sub "\nInstalled packages:" # Check if the JSON file is effectively empty ({} or []) if jq -e 'if type == "object" then . == {} elif type == "array" then . == [] else false end' "${installed_json}" >/dev/null; then zrep_msg info "\nNo packages installed." return fi # Iterate through each author and their packages jq -r 'to_entries | .[] | .key as $author | .value[] | "\($author)/\(.script) (\(.version))"' "${installed_json}" | while IFS= read -r package_info; do local package_name=$(echo "${package_info}" | cut -d ' ' -f1) # Extract package name before the version local is_active="${base_colors[white]}(${base_colors[end]}${base_colors[bold_red]}Inactive${base_colors[end]}${base_colors[white]})${base_colors[end]}" # Set default to Inactive zmodload zsh/regex # Check if the package is active (only modify if active) if [[ $(<${config[main_zrep_install_dir]}/.addons) =~ "\b${package_name}\b" ]]; then is_active="${base_colors[white]}(${base_colors[end]}${base_colors[bold_green]}Active${base_colors[end]}${base_colors[white]})${base_colors[end]}" fi zrep_msg info " |> ${package_info} ${is_active}" done } function zrep_list_package() { zrep_installed_json local package_names="" # Parse installed.json and concatenate package names jq -r 'to_entries[] | .key as $author | .value[] | "\($author)/\(.script) (\(.version))"' "${installed_json}" | while IFS= read -r package_info; do package_names+="${package_info} " done # Assuming you want to print out the concatenated package names if [[ -n "${package_names}" ]]; then zrep_msg info "\nInstalled packages: ${package_names}" else zrep_msg debug "\nNo packages found." fi } # Function to load configuration function zrep_load_config() { # Check if jq is available if ! command -v jq &> /dev/null; then echo "Error: 'jq' is not installed. Please install jq to continue." exit 1 fi if [[ -f "${ZREP_CONFIG}" ]]; then zini "${ZREP_CONFIG}" zrep_fpath ${config[main_zrep_install_dir]} else if [[ "${1}" == "init" ]]; then echo "${ZREP_CONFIG} not found. Proceeding with 'zrep init'..." zrep_init else # echo "${ZREP_CONFIG} not found." # Ask the user if they want to run 'zrep init' echo "\nWelcome to zrep. Looks like this is the first time you are" echo "running me." read "response?Would you like to run 'zrep init' to set up? (y/n): " if [[ "${response}" =~ ^[Yy]$ ]]; then zrep_init else echo "Initialization canceled. Please run 'zrep init' manually to set up." exit 1 fi fi fi } function zrep_remove_package() { local package_name="${1}" zrep_installed_json local author="${package_name%%/*}" local script="${package_name#*/}" # Verify if the package is installed and get its version (if any) local installed_version=$(jq -r --arg author "$author" --arg script "$script" \ '.[$author][] | select(.script == $script) | .version' "$installed_json") if [[ -z "$installed_version" || "$installed_version" == "null" ]]; then zrep_msg debug "\nError: Package '${package_name}' is not installed." zrep_msg info "Please see 'zrep list' for installed packages." return 1 fi local first_letter="${author:0:1:l}" local package_dir="${config[main_zrep_install_dir]}/${first_letter}/${author}/${script}" zrep_msg info "\nFound installed package: $package_name, version: $installed_version" # Ask user for confirmation with default response "Y" read "REPLY?Are you sure you want to remove this package? (y/n) [Y]: " REPLY=${REPLY:-Y} echo if [[ "${REPLY}" =~ ^[Yy]$ ]]; then # Remove the package directory from disk if [[ -d "${package_dir}" ]]; then rm -rf "${package_dir}" zrep_msg sub "Package directory '${package_dir}' removed successfully." else zrep_msg debug "Warning: Package directory '${package_dir}' not found." fi # Remove the package from installed.json jq --arg author "$author" --arg script "$script" \ '(.[$author] |= map(select(.script != $script))) | if .[$author] == [] then del(.[$author]) else . end' \ "$installed_json" > "$installed_json.tmp" && mv "$installed_json.tmp" "$installed_json" zrep_msg sub "Package '${package_name}' removed successfully from installed.json." else zrep_msg info "Removal canceled." fi } function zrep_check_if_installed() { local package="${1}" zrep_installed_json local author_name="${package%%/*}" local script_name="${package#*/}" # Initialize version to an empty string typeset -g installed_version="" # Check if the package is already installed and retrieve its version installed_version=$(jq -r --arg author "$author_name" --arg script "$script_name" \ '.[$author][] | select(.script == $script) | .version' "$installed_json") if [[ -n "${installed_version}" && "${installed_version}" != "null" ]]; then # Package is installed, and version is stored in installed_version # echo "Package $package is installed with version $installed_version." return 0 # Package is installed else # echo "Package $package is not installed." return 1 # Package is not installed fi } typeset -A updatesAvailable function zrep_check_for_updates() { local remoteFile="${config[global_repo_url]}/packages.json" # local localFile="${config[main_zrep_install_dir]}/installed.json" zrep_installed_json local remotePackages=$(curl -s "${remoteFile}") # Reset global variables updatesAvailable=() typeset -g updates=false # Global declaration, initializes to false # Process updates local authorsScripts=$(jq -r '. | to_entries[] | .key as $author | .value[] | "\($author)/\(.script):\(.version)"' "$installed_json") for entry in ${(f)authorsScripts}; do local author="${entry%%/*}" local rest="${entry#*/}" local script="${rest%%:*}" local installed_version="${rest##*:}" local remote_version=$(jq -r --arg author "$author" --arg script "$script" \ '.authors[] | select(.name==$author) | .scripts[] | select(.name==$script) | .version' <<<"$remotePackages") if [[ "${remote_version}" > "${installed_version}" ]]; then updatesAvailable[${author}/${script}]="${remote_version}" zrep_msg info "\n${author}/${script} can be updated from ${installed_version} to ${remote_version}" updates=true # Mark that updates are available fi done if [[ ${updates} == "false" ]]; then zrep_msg info "\nNo updates found." fi } function zrep_update_package() { local specificPackage=${1} zrep_check_for_updates # Check if in update mode or specific package update if [[ -n "${specificPackage}" ]]; then # Logic for updating a specific package # Assuming specificPackage format is "author/script" local version=${updatesAvailable[${specificPackage}]} if [[ -n "$version" ]]; then local author="${specificPackage%%/*}" local script="${specificPackage#*/}" # local dlurl="${config[global_repo_url]}/download.php?a=${author}&s=${script}&v=${version}" # echo "Updating $specificPackage to version $version..." local install_pkg="${author}/${script}" zrep_install_package u ${install_pkg} else zrep_msg info "\nNo update available for ${specificPackage}." fi else if [[ ${updates} == "true" ]]; then # General update mode: update all packages listed in updatesAvailable for package in ${(k)updatesAvailable}; do local author=${package%%/*} local script=${package#*/} local version=${updatesAvailable[${package}]} # local dlurl="${config[global_repo_url]}/download.php?a=${author}&s=${script}&v=${version}" # echo "Preparing to update $package to version $version..." local install_pkg="${author}/${script}" zrep_install_package u ${install_pkg} done fi fi } # Enhanced zrep_downloader function with error handling and retry logic. # It attempts to download a file from a given URL to a specified zip file using curl, wget, or wget2 based on the global configuration. # This function supports retries and delays between attempts for robust error handling. # # Usage: zrep_download_package ZipFile DownloadURL # # Parameters: # DownloadURL: The URL from which to download the file. # ZipFile: The name of the file to save the downloaded content to. function zrep_download_package() { local ZipFile="${1}" local DownloadURL="${2}" local retries=5 local delay=5 local attempt=1 local downloader="" local http_status local cmd case "${config[global_downloader]}" in curl) downloader="curl" cmd="curl -s -o \"$ZipFile\" \"$DownloadURL\" -w \"%{http_code}\"" ;; wget) downloader="wget" cmd="wget -q -O \"$ZipFile\" \"$DownloadURL\"; echo $?" ;; wget2) downloader="wget2" cmd="wget2 -q -O \"$ZipFile\" \"$DownloadURL\"; echo $?" ;; *) zrep_msg debug "Unsupported or unspecified downloader: '${config[global_downloader]}'." return 1 ;; esac while (( attempt <= retries )); do zrep_msg sub "Attempt $attempt of $retries: Downloading using $downloader..." if [[ $downloader == "curl" ]]; then eval $cmd http_status="${PIPESTATUS[0]}" else # wget or wget2 http_status=$(eval $cmd) fi # Check if download command was successful (0 exit status) and, for curl, HTTP status is 200 (OK) if [[ $down_status -eq 0 && ($downloader != "curl" || $http_status -eq 200) ]]; then zrep_msg sub "Download successful." return 0 else zrep_msg debug "Error: Failed to download the package. HTTP status (curl only): ${http_status}." ((attempt++)) zrep_msg info "Waiting ${delay} seconds before retrying..." sleep $delay fi done zrep_msg debug "Error: The download failed after $retries attempts." return 1 } # Function to install a package by unzipping it to ${config[main_zrep_install_dir]} function zrep_install_package() { if [[ ${1} == "u" ]]; then updates=true local package=${2} echo "zrep_install_package: package=$package" else updates=false local package="${1}" # Call zrep_check_if_installed to check if the package is already installed if zrep_check_if_installed "${package}"; then zrep_msg info "Package ${package} is already installed." zrep_msg info "Use 'zrep list' to see installed packages." return 0 fi fi # If not installed, proceed with fetching the package information zrep_parse_remote "${config[global_repo_url]}/packages.json" "${package}" local tmpDir="${config[main_zrep_install_dir]}/tmp" mkdir -p "${tmpDir}" local zipFile="${tmpDir}/${author}-${script}-${version}.zip" zrep_download_package "${zipFile}" "${dlurl}" unzip -q -aa -o "${zipFile}" -d "${config[main_zrep_install_dir]}" if [[ $? -ne 0 ]]; then zrep_msg debug "\nError: Failed to unzip the package." return 1 else zrep_update_installed_json "${author}" "${script}" "${version}" fi rm "${zipFile}" } # Function to parse installed.json function zrep_parse_installed_json() { zrep_installed_json jq -c '.' "${installed_json}" } function zrep_parse_package_name() { package_name="${1}" zrep_installed_json author="${package_name%/*}" script="${package_name#*/}" local first_letter="${author:0:1:l}" addon_path="${config[main_zrep_install_dir]}/${first_letter}/${author}/${script}" # Check if the package is installed if ! jq -e --arg author "$author" --arg script "$script" '.[$author] | any(.script == $script)' "${installed_json}" &>/dev/null; then zrep_msg debug "\nError: Package '${package_name}' is not installed." return 1 fi } function zrep_enable() { local package_name="${1}" zrep_parse_package_name "${package_name}" # Initialize addons array if .zrep_addons does not exist if [ ! -f "${config[main_zrep_install_dir]}/.addons" ]; then addons=() else # Load existing addons from ${config[main_zrep_install_dir]}/.addons source "${config[main_zrep_install_dir]}/.addons" fi # Check if the addon is already enabled local addon_exists=0 for addon in "${addons[@]}"; do if [[ "${addon}" == "${addon_path}" ]]; then addon_exists=1 break fi done if ((addon_exists)); then echo "Package '${package_name}' is already enabled." return 0 fi # Add addon path to the array addons+=("${addon_path}") # Reconstruct .zrep_addons file with the updated addons array { echo "addons=(" for addon in "${addons[@]}"; do echo " '${addon}'" done echo ")" } > "${config[main_zrep_install_dir]}/.addons" zrep_msg info "\nPackage '${package_name}' has been enabled and added to fpath." zrep_msg info "You may have to run 'source ~/.zrep_addons' to get access to it." } function zrep_disable() { local package_name="${1}" zrep_parse_package_name "${package_name}" # Initialize addons array if .zrep_addons does not exist if [ ! -f "${config[main_zrep_install_dir]}/.addons" ]; then addons=() else # Load existing addons from ${config[main_zrep_install_dir]}/.addons source "${config[main_zrep_install_dir]}/.addons" fi # Initialize a new array for addons local new_addons=() # Flag to check if addon was found and removed local found=0 # Iterate through existing addons for addon in "${addons[@]}"; do if [[ "${addon}" == "${addon_path}" ]]; then found=1 else new_addons+=("${addon}") fi done if ((found == 0)); then zrep_msg debug "\nPackage '${package_name}' is not currently enabled." return 0 fi # Reconstruct .zrep_addons file with the new addons array { echo "addons=(" for addon in "${new_addons[@]}"; do echo " '${addon}'" done echo ")" } > ${config[main_zrep_install_dir]}/.addons zrep_msg info "\nPackage '${package_name} (${script})' has been disabled and removed from fpath." zrep_msg info "You may have to run 'source ~/.zrep_addons' to remove it from your shell." } # Help function to display available options function zrep_help() { zrep_msg sub "\nUsage: zrep [arguments]" zrep_msg info "Available commands:" if [[ ! -f ${ZREP_CONFIG} ]]; then zrep_msg info " init: Initialize zrep" fi zrep_msg info " install (i) :\t\t\tInstall a package" zrep_msg info " remove (rm, delete, del) :\tRemove a package" zrep_msg info " update (u) :\t\t\tUpdate zrep package" zrep_msg info " enable :\t\t\tEnable zrep package" zrep_msg info " disable :\t\t\tDisable zrep package" zrep_msg info " version:\t\t\t\t\tDisplay zrep version" zrep_msg info " list:\t\t\t\t\t\tList installed packages" zrep_msg info " help:\t\t\tDisplay help for pacakage (if available)" } function zrep_package_info() { local package_name="${1}" zrep_installed_json # Ensure installed.json is loaded and available # Parse the package name to extract author and script local author="${package_name%/*}" local script="${package_name#*/}" local first_letter="${author:0:1:l}" # Get the first letter of the author's name to construct the URL # Check if the package is installed and get its version if zrep_check_if_installed "${package_name}"; then local installed_version="${installed_version}" # This variable is set by zrep_check_if_installed local package_dir="${config[main_zrep_install_dir]}/${first_letter}/${author}/${script}" local zrep_url="${config[global_repo_url]}/${first_letter}/${author}/${script}" # Display package information zrep_msg info "\nAuthor/Package:\t\t${author}/${script}" zrep_msg info "Version installed:\t${installed_version}" zrep_msg info "Location on disk:\t${package_dir}" zrep_msg info "zrep URL:\t\t${zrep_url}" else zrep_msg debug "\nPackage '${package_name}' is not installed." fi } function zrep_read_usage() { local package_name="${1}" # Parse the package name to extract author and script local author="${package_name%/*}" local script="${package_name#*/}" local first_letter="${author:0:1:l}" # Construct the path to the USAGE file local usage_file="${config[main_zrep_install_dir]}/${first_letter}/${author}/${script}/USAGE" # Check if the USAGE file exists if [[ -f "${usage_file}" ]]; then # Display the content of the USAGE file zrep_msg sub "\n${package_name}:" local usage_buffer=$(<${usage_file}) zrep_msg info "${usage_buffer}" else zrep_msg debug "No USAGE file found for package '${package_name}'." fi } function main() { zrep_main_version_string zrep_load_config ${1} zrep_load_theme ${config[global_theme]} # Check if the second argument is "help" and the first argument is not empty if [[ "${2}" == "help" && -n "${1}" ]]; then zrep_read_usage "${1}" exit fi # Example command handling structure case "${1}" in init) zrep_init zrep_check_for_deps exit ;; install | i) zrep_install_package ${2} ;; remove | delete | rm | del) # Parse the command argument to extract the package name zrep_remove_package_name="${2:-}" if [[ -z "${zrep_remove_package_name}" ]]; then zrep_msg info "\nUsage: zrep ${1} author/package" else zrep_remove_package "${zrep_remove_package_name}" fi ;; update | u) zrep_update_package ${2} ;; version | -v | --version) zrep_version ;; list) zrep_list_installed_packages ;; help | -h | --help) zrep_help ;; enable) zrep_enable ${2} ;; disable) zrep_disable ${2} ;; info) zrep_package_info ${2} ;; *) zrep_help ;; esac } # Call main with all passed arguments main "$@"