#!/bin/sh
#
# pfetch - Simple POSIX sh fetch script.

# Wrapper around all escape sequences used by pfetch to allow for
# greater control over which sequences are used (if any at all).

[ -n "$ZSH_VERSION" ] && emulate sh

esc() {
    case $1 in
        CUU) e="${esc_c}[${2}A" ;; # cursor up
        CUD) e="${esc_c}[${2}B" ;; # cursor down
        CUF) e="${esc_c}[${2}C" ;; # cursor right
        CUB) e="${esc_c}[${2}D" ;; # cursor left

        # text formatting
        SGR)
            case ${PF_COLOR:=1} in
                (1)
                    e="${esc_c}[${2}m"
                ;;

                (0)
                    # colors disabled
                    e=
                ;;
            esac
        ;;

        # line wrap
        DECAWM)
            case $TERM in
                (dumb | minix | cons25)
                    # not supported
                    e=
                ;;

                (*)
                    e="${esc_c}[?7${2}"
                ;;
            esac
        ;;
    esac
}

# Print a sequence to the terminal.
esc_p() {
    esc "$@"
    printf '%s' "$e"
}

# This is just a simple wrapper around 'command -v' to avoid
# spamming '>/dev/null' throughout the script.
has() {
    command -v "$1" >/dev/null 2>&1
}

log() {
    # The 'log()' function handles the printing of information.
    # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info
    # happen independently of each other.
    #
    # The size of the ascii art is stored and the ascii is printed first.
    # Once the ascii is printed, the cursor is located right below the art
    # (See marker $[1]).
    #
    # Using the stored ascii size, the cursor is then moved to marker $[2].
    # This is simply a cursor up escape sequence using the "height" of the
    # ascii art.
    #
    # 'log()' then moves the cursor to the right the "width" of the ascii art
    # with an additional amount of padding to add a gap between the art and
    # the information (See marker $[3]).
    #
    # When 'log()' has executed, the cursor is then located at marker $[4].
    # When 'log()' is run a second time, the next line of information is
    # printed, moving the cursor to marker $[5].
    #
    # Markers $[4] and $[5] repeat all the way down through the ascii art
    # until there is no more information left to print.
    #
    # Every time 'log()' is called the script keeps track of how many lines
    # were printed. When printing is complete the cursor is then manually
    # placed below the information and the art according to the "heights"
    # of both.
    #
    # The math is simple: move cursor down $((ascii_height - info_height)).
    # If the aim is to move the cursor from marker $[5] to marker $[6],
    # plus the ascii height is 8 while the info height is 2 it'd be a move
    # of 6 lines downwards.
    #
    # However, if the information printed is "taller" (takes up more lines)
    # than the ascii art, the cursor isn't moved at all!
    #
    # Once the cursor is at marker $[6], the script exits. This is the gist
    # of how this "dynamic" printing and layout works.
    #
    # This method allows ascii art to be stored without markers for info
    # and it allows for easy swapping of info order and amount.
    #
    # $[2] ___      $[3] goldie@KISS
    # $[4](.. |     $[5] os KISS Linux
    #     (<> |
    #    / __  \
    #   ( /  \ /|
    #  _/\ __)/_)
    #  \/-____\/
    # $[1]
    #
    # $[6] /home/goldie $

    # End here if no data was found.
    [ -n "$2" ] || return

    # Store the values of '$1' and '$3' as we reset the argument list below.
    name=$1
    use_separator=$3

    # Use 'set --' as a means of stripping all leading and trailing
    # white-space from the info string. This also normalizes all
    # white-space inside of the string.
    #
    # Disable the shellcheck warning for word-splitting
    # as it's safe and intended ('set -f' disables globbing).
    # shellcheck disable=2046,2086
    {
        set -f
        set +f -- $2
        info=$*
    }

    # Move the cursor to the right, the width of the ascii art with an
    # additional gap for text spacing.
    esc_p CUF "$ascii_width"

    # Print the info name and color the text.
    esc_p SGR "3${PF_COL1-4}";
    esc_p SGR 1
    printf '%s' "$name"
    esc_p SGR 0

    # Print the info name and info data separator, if applicable.
    [ -n "$use_separator" ] || printf %s "$PF_SEP"

    # Move the cursor backward the length of the *current* info name and
    # then move it forwards the length of the *longest* info name. This
    # aligns each info data line.
    esc_p CUB "${#name}"
    esc_p CUF "${PF_ALIGN:-$info_length}"

    # Print the info data, color it and strip all leading whitespace
    # from the string.
    esc_p SGR "3${PF_COL2-9}"
    printf '%s' "$info"
    esc_p SGR 0
    printf '\n'

    # Keep track of the number of times 'log()' has been run.
    info_height=$((${info_height:-0} + 1))
}

wmic_read() {
    # This function is used to remove garbage from the output of 'wmic'.
    {
        read -r line # discard the first line
        while IFS= read -r line; do
            if [ -n "$line" ]; then
                # skip if the line starts with whitespace
                case $line in
                    [[:space:]]*) continue ;;
                esac
                # remove trailing whitespace
                printf '%s\n' "${line%"${line##*[![:space:]]}"}"
            fi
        done
    } <<-EOF
		$(wmic.exe "$@")
	EOF
}

get_title() {
    # Username is retrieved by first checking '$USER' with a fallback
    # to the 'id -un' command, then to the 'whoami' command, and finally
    # to 'unknown'.
    if [ -n "$USER" ]; then
        user="$USER"
    elif has id; then
        user=$(id -un)
    elif has whoami; then
        user=$(whoami)
    else
        user="unknown"
    fi

    # Hostname is retrieved by first checking '$HOSTNAME' with a fallback
    # to the 'hostname' command, then to the '/etc/hostname' file, and finally
    # to 'unknown'.
    if [ -z "$hostname" ]; then
        if [ -n "$HOSTNAME" ]; then
            hostname="$HOSTNAME"
        elif has hostname; then
            hostname=$(hostname)
        elif [ -r /etc/hostname ]; then
            read -r hostname < /etc/hostname
        else
            hostname="unknown"
        fi
    fi

    # Add escape sequences for coloring to user and host name unless $PF_COL3=COL1. As we embed
    # them directly in the arguments passed to log(), we cannot use esc_p().

    if ! [ "$PF_COL3" = "COL1" ]; then
        esc SGR 1
        user=$e$user
        esc SGR "3${PF_COL3:-1}"
        user=$e$user
        esc SGR 1
        user=$user$e
        esc SGR 1
        hostname=$e$hostname
        esc SGR "3${PF_COL3:-1}"
        hostname=$e$hostname
    fi

    log "${user}@${hostname}" " " " " >&6
}

get_os() {
    # This function is called twice, once to detect the distribution name
    # for the purposes of picking an ascii art early and secondly to display
    # the distribution name in the info output (if enabled).
    #
    # On first run, this function displays _nothing_, only on the second
    # invocation is 'log()' called.
    if [ -n "$distro" ]; then
        log os "${PF_OS:-$distro}" >&6
        return
    fi

    case $os in
        (Linux*|GNU*)
            # Some Linux distributions (which are based on others)
            # fail to identify as they **do not** change the upstream
            # distribution's identification packages or files.
            #
            # It is senseless to add a special case in the code for
            # each and every distribution (which _is_ technically no
            # different from what it is based on) as they're either too
            # lazy to modify upstream's identification files or they
            # don't have the know-how (or means) to ship their own
            # lsb-release package.
            #
            # This causes users to think there's a bug in system detection
            # tools like neofetch or pfetch when they technically *do*
            # function correctly.
            #
            # Exceptions are made for distributions which are independent,
            # not based on another distribution or follow different
            # standards.
            #
            # This applies only to distributions which follow the standard
            # by shipping unmodified identification files and packages
            # from their respective upstreams.
            if has lsb_release && distro=$(lsb_release -sd); then
                :

            # Android detection works by checking for the existence of
            # the following two directories. I don't think there's a simpler
            # method than this.
            elif [ -d /system/app ] && [ -d /system/priv-app ] && has getprop; then
                distro="Android $(getprop ro.build.version.release)"

            elif [ -f /etc/os-release ]; then
                # This used to be a simple '. /etc/os-release' but I believe
                # this is insecure as we blindly executed whatever was in the
                # file. This parser instead simply handles 'key=val', treating
                # the file contents as plain-text.
                while IFS='=' read -r key val; do
                    case $key in
                        (PRETTY_NAME)
                            distro=$val
                        ;;
                    esac
                done < /etc/os-release

            else
                # Special cases for (independent) distributions which
                # don't follow any os-release/lsb standards whatsoever.
                has crux && distro=$(crux)
                has guix && distro='Guix System'
            fi

            # 'os-release' and 'lsb_release' sometimes add quotes
            # around the distribution name, strip them.
            distro=${distro##[\"\']}
            distro=${distro%%[\"\']}

            # Check to see if we're running Bedrock Linux which is
            # very unique. This simply checks to see if the user's
            # PATH contains a Bedrock specific value.
            case $PATH in
                (*/bedrock/cross/*)
                    distro='Bedrock Linux'
                ;;
            esac

            # If the distribution is still unknown, use the os name.
            [ -n "$distro" ] || distro="$os"

            # Check to see if Linux is running in Windows under
            # WSL2 (Windows subsystem for Linux [version 2]) and
            # append a string accordingly.
            #
            # This checks to see if '$WSLENV' is defined. This
            # appends the Windows string even if '$WSLENV' is
            # empty. We only need to check that is has been _exported_.
            if [ -n "$WSLENV" ]; then
                distro="${distro}${WSLENV+ on Windows [WSL2]}"

            # If the kernel version string ends in "-WSL2",
            # we're very likely running under Windows in WSL2.
            elif [ -z "${kernel%%*-WSL2}" ]; then
                distro="$distro on Windows [WSL2]"

            # Check to see if Linux is running in Windows under
            # WSL1 (Windows subsystem for Linux [version 1]) and
            # append a string accordingly.
            #
            # If the kernel version string ends in "-Microsoft",
            # we're very likely running under Windows in WSL1.
            elif [ -z "${kernel%%*-Microsoft}" ]; then
                distro="$distro on Windows [WSL1]"
            fi
        ;;

        (CYGWIN*|MSYS*|MINGW*|Windows_NT)
            distro=$(wmic_read os get Caption)
        ;;

        (Darwin*)
            if has sw_vers; then
                mac_version=$(sw_vers -productVersion)
                mac_product=$(sw_vers -productName)
            else
                # Parse the SystemVersion.plist file to grab the macOS
                # version. The file is in the following format:
                #
                # <key>ProductVersion</key>
                # <string>10.14.6</string>
                #
                # 'IFS' is set to '<>' to enable splitting between the
                # keys and a second 'read' is used to operate on the
                # next line directly after a match.
                #
                # '_' is used to nullify a field. '_ _ line _' basically
                # says "populate $line with the third field's contents".
                while IFS='<>' read -r _ _ line _; do
                    case $line in
                        # Match 'ProductVersion' and read the next line
                        # directly as it contains the key's value.
                        ProductVersion)
                            IFS='<>' read -r _ _ mac_version _
                            continue
                        ;;

                        ProductName)
                            IFS='<>' read -r _ _ mac_product _
                            continue
                        ;;
                    esac
                done < /System/Library/CoreServices/SystemVersion.plist
            fi

            # Use the ProductVersion to determine which macOS/OS X codename
            # the system has. As far as I'm aware there's no "dynamic" way
            # of grabbing this information.
            case $mac_version in
                (10.4*)  distro="Mac OS X $mac_version Tiger"        ;;
                (10.5*)  distro="Mac OS X $mac_version Leopard"      ;;
                (10.6*)  distro="Mac OS X $mac_version Snow Leopard" ;;
                (10.7*)  distro="Mac OS X $mac_version Lion"         ;;
                (10.8*)  distro="OS X $mac_version Mountain Lion"    ;;
                (10.9*)  distro="OS X $mac_version Mavericks"        ;;
                (10.10*) distro="OS X $mac_version Yosemite"         ;;
                (10.11*) distro="OS X $mac_version El Capitan"       ;;
                (10.12*) distro="macOS $mac_version Sierra"          ;;
                (10.13*) distro="macOS $mac_version High Sierra"     ;;
                (10.14*) distro="macOS $mac_version Mojave"          ;;
                (10.15*) distro="macOS $mac_version Catalina"        ;;
                (11*)    distro="macOS $mac_version Big Sur"         ;;
                (12*)    distro="macOS $mac_version Monterey"        ;;
                (13*)    distro="macOS $mac_version Ventura"         ;;
                (14*)    distro="macOS $mac_version Sonoma"          ;;
                (15*)    distro="macOS $mac_version Sequoia"         ;;
                (*)      distro="macOS $mac_version"                 ;;
            esac

            # Use the ProductName to determine if we're running in iOS.
            case $mac_product in
                (iP*) distro="iOS $mac_version" ;;
            esac
        ;;

        (Haiku)
            # Haiku uses 'uname -v' for version information
            # instead of 'uname -r' which only prints '1'.
            distro=$(uname -sv)
        ;;

        (Minix|DragonFly)
            distro="$os $kernel"

            # Minix and DragonFly don't support the escape
            # sequences used, clear the exit trap.
            trap '' EXIT
        ;;

        (SunOS)
            # Grab the first line of the '/etc/release' file
            # discarding everything after '('.
            IFS='(' read -r distro _ < /etc/release
        ;;

        (OpenBSD*)
            # Show the OpenBSD version type (current if present).
            # kern.version=OpenBSD 6.6-current (GENERIC.MP) ...
            IFS=' =' read -r _ distro openbsd_ver _ <<-EOF
				$(sysctl kern.version)
			EOF

            distro="$distro $openbsd_ver"
        ;;

        (FreeBSD)
            distro="$os $(freebsd-version)"
        ;;

        (OSF1)
            distro="Digital UNIX"
        ;;

        (*)
            # Catch all to ensure '$distro' is never blank.
            # This also handles the BSDs.
            distro="$os $kernel"
        ;;
    esac
}

get_kernel() {
    case $os in
        # Don't print kernel output on some systems as the
        # OS name includes it.
        (*BSD*|Haiku|Minix)
            return
        ;;

        # Use wmic to get the OS version
        (CYGWIN*|MSYS*|MINGW*|Windows_NT)
            kernel=$(wmic_read os get Version)
        ;;

        # Use uname to get the Kickstart version
        (MorphOS)
            kernel=$(uname -v)
        ;;
    esac

    # '$kernel' is the cached output of 'uname -r'.
    log kernel "${PF_KERNEL:-$kernel}" >&6
}

get_host() {
    if [ -n "$PF_HOST" ]; then
        log host "$PF_HOST" >&6
        return
    fi

    case $os in
        (Linux*)
            # Despite what these files are called, version doesn't
            # always contain the version nor does name always contain
            # the name.
            [ -f /sys/devices/virtual/dmi/id/product_name ] &&
                read -r name < /sys/devices/virtual/dmi/id/product_name
            [ -f /sys/devices/virtual/dmi/id/product_version ] &&
                read -r version < /sys/devices/virtual/dmi/id/product_version
            [ -f /sys/firmware/devicetree/base/model ] &&
                read -r model < /sys/firmware/devicetree/base/model

            host="$name $version $model"
        ;;

        (Darwin*|FreeBSD*|DragonFly*)
            # '$arch' is the cached output from 'uname -m'.
            case $distro in
                iOS*) host=$arch                 ;;
                *)    host=$(sysctl -n hw.model) ;;
            esac
        ;;

        (NetBSD*)
            host=$(/sbin/sysctl -n hw.model)
        ;;

        (OpenBSD*)
            host="$(sysctl -n hw.product) $(sysctl -n hw.version)"
        ;;

        (*BSD*|Minix)
            host=$(sysctl -n hw.vendor hw.product)
        ;;
        (CYGWIN*|MSYS*|MINGW*|Windows_NT)
            host=$(wmic_read computersystem get manufacturer,model)
        ;;
    esac

    # Turn the host string into an argument list so we can iterate
    # over it and remove OEM strings and other information which
    # shouldn't be displayed.
    #
    # Disable the shellcheck warning for word-splitting
    # as it's safe and intended ('set -f' disables globbing).
    # shellcheck disable=2046,2086
    {
        set -f
        set +f -- $host
        host=
    }

    # Iterate over the host string word by word as a means of stripping
    # unwanted and OEM information from the string as a whole.
    #
    # This could have been implemented using a long 'sed' command with
    # a list of word replacements, however I want to show that something
    # like this is possible in pure sh.
    #
    # This string reconstruction is needed as some OEMs either leave the
    # identification information as "To be filled by OEM", "Default",
    # "undefined" etc and we shouldn't print this to the screen.
    for word do
        # This works by reconstructing the string by excluding words
        # found in the "blacklist" below. Only non-matches are appended
        # to the final host string.
        case $word in
           (To      | [Bb]e      | [Ff]illed | [Bb]y  | O.E.M.  | OEM  |\
            Not     | Applicable | Specified | System | Product | Name |\
            Version | Undefined  | Default   | string | INVALID | �    | os |\
            Type1ProductConfigId )
                continue
            ;;
        esac

        host="$host$word "
    done

    # '$arch' is the cached output from 'uname -m'.
    log host "${host:-$arch}" >&6
}

get_uptime() {
    # Uptime works by retrieving the data in total seconds and then
    # converting that data into days, hours and minutes using simple
    # math.
    case $os in
        (Linux*|Minix*|GNU*|SerenityOS*|CYGWIN*|MSYS*|MINGW*|Windows_NT|NetBSD*)
            if [ -r /proc/uptime ]; then
                IFS=. read -r s _ < /proc/uptime
            elif has uptime && has date; then
                boot=$(date -d"$(uptime -s)" +%s)
                now=$(date +%s)
                s=$((now - boot))
            fi
        ;;

        (Darwin*|*BSD*|DragonFly*)
            s=$(sysctl -n kern.boottime)

            # Extract the uptime in seconds from the following output:
            # [...] { sec = 1271934886, usec = 667779 } Thu Apr 22 12:14:46 2010
            s=${s#*=}
            s=${s%,*}

            # The uptime format from 'sysctl' needs to be subtracted from
            # the current time in seconds.
            s=$(($(date +%s) - s))
        ;;

        (Haiku)
            # The boot time is returned in microseconds, convert it to
            # regular seconds.
            s=$(($(system_time) / 1000000))
        ;;

        (MorphOS)
            IFS=':, ' read -r _ _ _ _ h m _ <<-EOF
				$(uptime)
			EOF
            s=$((${h:-0}*3600 + ${m:-0}*60))
        ;;

        (SunOS)
            # Split the output of 'kstat' on '.' and any white-space
            # which exists in the command output.
            #
            # The output is as follows:
            # unix:0:system_misc:snaptime	14809.906993005
            #
            # The parser extracts:          ^^^^^
            IFS='	.' read -r _ s _ <<-EOF
				$(kstat -p unix:0:system_misc:snaptime)
			EOF
        ;;

        (IRIX|OSF1|HP-UX)
            # Grab the uptime in a pretty format. Usually,
            # 00:00:00 from the 'ps' command.
            if [ "$os" = "HP-UX" ]; then
                t=$(UNIX95=1 ps -o etime= -p 1)
            else
                t=$(LC_ALL=POSIX ps -o etime= -p 1)
            fi

            # Split the pretty output into days or hours
            # based on the uptime.
            case $t in
                (*-*)   d=${t%%-*} t=${t#*-} ;;
            esac
            case $t in
                (*:*:*) h=${t%%:*} t=${t#*:} ;;
            esac

            h=${h#0} t=${t#0}

            # Convert the split pretty fields back into
            # seconds so we may re-convert them to our format.
            s=$((${d:-0}*86400 + ${h:-0}*3600 + ${t%%:*}*60 + ${t#*:}))
        ;;
    esac

    # End here if the uptime is empty.
    [ -n "$s" ] || return

    # Convert the uptime from seconds into days, hours and minutes.
    d=$((s / 60 / 60 / 24))
    h=$((s / 60 / 60 % 24))
    m=$((s / 60 % 60))

    # Only append days, hours and minutes if they're non-zero.
    case "$d" in ([!0]*) uptime="${uptime}${d}d "; esac
    case "$h" in ([!0]*) uptime="${uptime}${h}h "; esac
    case "$m" in ([!0]*) uptime="${uptime}${m}m "; esac

    log uptime "${uptime:-0m}" >&6
}

apk_pkg_count() {
    if [ -f /lib/apk/db/installed ] && has grep; then
        # Fast path
        grep '^$' /lib/apk/db/installed
    else
        apk info
    fi
}

nix_pkg_count() {
    nix-store -q --requisites /run/current-system/sw
    nix-store -q --requisites ~/.nix-profile
}

cargo_pkg_count() {
    while IFS= read -r line; do
        case $line in
            ' '*|'') continue ;;
        esac
        printf '%s\n' "$line"
    done <<-EOF
		$(cargo install --list)
	EOF
}

serenity_pkg_count() {
    while IFS=" " read -r type _; do
    [ "$type" != dependency ] && printf "\n"
    done < /usr/Ports/packages.db
}

count_pkg() {
    # This function is used to count the number of packages installed
    # from each individual package manager.
    # The first arguement is the name of the package manager. The second
    # arguement is a number to subtract from the total count of packages.
    # The other arguements are the command to list the packages.
    pkgmgr="$1"
    # check the name against the list of disabled package managers
    for disabled in $PF_DISABLED_PACKAGE_MANAGERS; do
        [ "$disabled" = "$pkgmgr" ] && return
    done
    subcount="$2"
    shift
    shift
    count=$("$@" | wc -l)
    # 'wc -l' can have leading and/or trailing whitespace
    # depending on the implementation, so strip them.
    # Procedure explained at https://github.com/dylanaraps/pure-sh-bible
    # (trim-leading-and-trailing-white-space-from-string)
    count=${count#"${count%%[![:space:]]*}"}
    count=${count%"${count##*[![:space:]]}"}
    count=$((count - subcount))
    [ $count = 0 ] && return
    total_pkgs=$((total_pkgs + count))
    if [ -z "$pkgs" ]; then
        pkgs="$count ($pkgmgr)"
    else
        pkgs="$pkgs, $count ($pkgmgr)"
    fi
    if [ -z "$tiny_pkgs" ]; then
        tiny_pkgs="$pkgmgr"
    else
        tiny_pkgs="$tiny_pkgs, $pkgmgr"
    fi
}

get_pkgs() {
    # This works by first checking for which package managers are
    # installed and then by printing each package manager's
    # package list with each package one per line.
    #
    # The output from this is then piped to 'wc -l' to count each
    # line, giving us the total package count of whatever package
    # managers are installed.

    # None of this works if 'wc' isn't installed.
    has wc || return

    total_pkgs=0

    case $os in
        (Linux*)
            # Commands which print packages one per line.
            has bonsai     && count_pkg bonsai 0 bonsai list
            has crux       && count_pkg pkginfo 0 pkginfo -i
            has pacman-key && count_pkg pacman 0 pacman -Qq
            has dpkg       && count_pkg dpkg 0 dpkg-query -f '.\n' -W
            has rpm        && count_pkg rpm 0 rpm -qa
            has xbps-query && count_pkg xbps 0 xbps-query -l
            has apk        && count_pkg apk 0 apk_pkg_count
            has guix       && count_pkg guix 0 guix package --list-installed
            has opkg       && count_pkg opkg 0 opkg list-installed
            has rad        && count_pkg rad 0 rad -cl
            has flatpak    && count_pkg flatpak 0 flatpak list
            has snap       && count_pkg snap 0 snap list
            has urpmq      && count_pkg urpmq 0 urpmq --list-media active
            has birb       && count_pkg birb 0 birb --list-installed
            has click      && count_pkg click 0 click list
            has pacman-g2  && count_pkg pacman-g2 0 pacman-g2 -Q
            has lvu        && count_pkg lvu 0 lvu installed
            has tce-status && count_pkg tce 0 tce-status -i
            has sorcery    && count_pkg sorcery 0 gaze installed
            has alps       && count_pkg alps 0 alps showinstalled
            has butch      && count_pkg butch 0 butch list
            has swupd      && count_pkg swupd 0 swupd bundle-list --quiet
            has pisi       && count_pkg pisi 0 pisi li
            has pacstall   && count_pkg pacstall 0 pacstall -L
            has bulge      && count_pkg bulge 0 bulge list
            has evox       && count_pkg evox 0 cat /var/evox/packages/DB
            has squirrel   && count_pkg squirrel 0 ls -w 1 /var/packages
            has anise      && count_pkg anise 0 anise s --installed
            has nix-store  && count_pkg nix 0 nix_pkg_count
            has pm         && _p="$(pm list packages)" &&
                count_pkg pm 0 printf '%s\n' "$_p"

            # 'mine' conflicts with minesweeper games.
            [ -f /etc/SDE-VERSION ] &&
                has mine && count_pkg mine 0 mine -q

            # Directories containing packages.
            has kiss       && count_pkg kiss 0 printf '%s\n' /var/db/kiss/installed/*/
            has cpt-list   && count_pkg cpt-list 0 printf '%s\n' /var/db/cpt/installed/*/
            has brew &&
                count_pkg brew 0 printf '%s\n' "$(brew --cellar)/"* "$(brew --caskroom)/"*
            has emerge     && count_pkg portage 0 printf '%s\n' /var/db/pkg/*/*/
            has pkgtool    && count_pkg pkgtool 0 printf '%s\n' /var/log/packages/*
            has eopkg      && count_pkg eopkg 0 printf '%s\n' /var/lib/eopkg/package/*

            has Compile    && count_pkg compile 0 printf '%s\n' /Programs/*/
            has inary      && count_pkg inary 0 printf '%s\n' /var/lib/inary/package/*
            has tekel      && count_pkg tekel 0 printf '%s\n' /data/app/"$USER"/* /data/app/system/*
            has crew       && count_pkg chromebrew 0 printf '%s\n' "${CREW_PREFIX:-/usr/local}"/etc/crew/meta/*.filelist
            has scratch    && count_pkg scratch 0 printf '%s\n' /var/lib/scratchpkg/db/*
            has kagami     && count_pkg kagami 0 printf '%s\n' /var/lib/kagami/pkgs/*
            has cave       && count_pkg cave 0 printf '%s\n' /var/db/paludis/repositories/cross-installed/*/data/*/ /var/db/paludis/repositories/installed/data/*/
            has hardman    && count_pkg hpkg 0 printf '%s\n' /var/hpkg/packages/*

            # List these last as they accompany regular package managers.
            has spm        && count_pkg spm 0 spm list -i
            has puyo       && count_pkg puyo 0 printf '%s\n' ~/.puyo/installed/*


            has appimaged  && count_pkg appimage 0 printf '%s\n' "$HOME"/.local/bin/*.[Aa]pp[Ii]mage "$HOME"/Applications/*.[Aa]pp[Ii]mage

            # Has devbox & is initialized
            has devbox &&
            case "$(devbox global list)" in
            *"not activated"*) ;;
            *)
                  count_pkg devbox 0 devbox global list
            ;;
            esac
        ;;

        (CYGWIN*|MSYS*|MINGW*|Windows_NT)
            # Commands which print packages one per line.
            has scoop && {
                scoopapps=$(command -v scoop)
                scoopapps="${scoopapps%/*}/../apps"
                count_pkg scoop 1 printf '%s\n' "$scoopapps"/*
            }
            has winget     && count_pkg winget 0 winget list
            has choco      && count_pkg chocolatey 2 choco list
            has pacman-key && count_pkg pacman 0 pacman -Qq
        ;;

        (Darwin*)
            # Commands which print packages one per line.
            has pkgin      && count_pkg pkgin 0 pkgin list
            has dpkg       && count_pkg dpkg 0 dpkg-query -f '.\n' -W

            # Directories containing packages.
            has brew &&
                count_pkg brew 0 printf '%s\n' "$(brew --cellar)/"* "$(brew --caskroom)/"*

            # 'port' prints a single line of output to 'stdout'
            # when no packages are installed and exits with
            # success causing a false-positive of 1 package
            # installed.
            #
            # 'port' should really exit with a non-zero code
            # in this case to allow scripts to cleanly handle
            # this behavior.
            has port       && {
                pkg_list=$(port installed 2>/dev/null)

                case "$pkg_list" in
                    ("No ports are installed.")
                        # do nothing
                    ;;

                    (*)
                        count_pkg macports 0 printf '%s' "$pkg_list"
                    ;;
                esac
            }
        ;;

        (FreeBSD*|DragonFly*)
            count_pkg FreeBSD 0 pkg info
        ;;

        (OpenBSD*)
            count_pkg OpenBSD 0 printf '%s\n' /var/db/pkg/*/
        ;;

        (NetBSD*)
            count_pkg pkgsrc 0 /usr/sbin/pkg_info
        ;;

        (Haiku)
            count_pkg Haiku 0 printf '%s\n' /boot/system/package-links/*
        ;;

        (Minix)
            count_pkg Minix 0 printf '%s\n' /usr/pkg/var/db/pkg/*/
        ;;

        (SunOS)
            has pkginfo && count_pkg pkginfo 0 pkginfo -i
            has pkg     && count_pkg pkg 0 pkg list
        ;;

        (IRIX)
            count_pkg IRIX 3 versions -b
        ;;

        (SerenityOS)
            count_pkg SerenityOS 0 serenity_pkg_count
        ;;
    esac

    # package managers that are on many different systems
    has cargo && count_pkg cargo 0 cargo_pkg_count
    if [ -n "$PF_ENABLE_SLOW_PACKAGE_MANAGERS" ]; then
        has pipx && count_pkg pipx 0 pipx list
        if has pip; then
            count_pkg pip 0 pip freeze
        elif has pip3; then
            count_pkg pip 0 pip3 freeze
        fi
    fi

    if [ $total_pkgs -gt 0 ]; then
        if [ "$PF_PACKAGE_MANAGERS" = "on" ]; then
            log pkgs "$pkgs" >&6
        elif [ "$PF_PACKAGE_MANAGERS" = "tiny" ]; then
            log pkgs "$total_pkgs ($tiny_pkgs)" >&6
        else
            log pkgs "$total_pkgs" >&6
        fi
    fi
}

get_memory() {
    case $os in
        # Used memory is calculated using the following "formula":
        # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable
        # Source: https://github.com/KittyKatt/screenFetch/issues/386
        (Linux*|CYGWIN*|MSYS*|MINGW*|Windows_NT)
            if [ -f /proc/meminfo ]; then
                # Parse the '/proc/meminfo' file splitting on ':' and 'k'.
                # The format of the file is 'key:   000kB' and an additional
                # split is used on 'k' to filter out 'kB'.
                while IFS=':k '  read -r key val _; do
                    case $key in
                        (MemTotal)
                            mem_used=$((mem_used + val))
                            mem_full=$val
                        ;;

                        (Shmem)
                            mem_used=$((mem_used + val))
                        ;;

                        (MemFree | Buffers | Cached | SReclaimable)
                            mem_used=$((mem_used - val))
                        ;;

                    esac
                done < /proc/meminfo

                mem_used=$((mem_used / 1024))
                mem_full=$((mem_full / 1024))
            elif has free; then
                # parse the output of 'free -m'
                {
                    read -r _ mem_full mem_used _ # discard the first line
                    read -r _ mem_full mem_used _
                } <<-EOF
					$(free -m)
				EOF
            fi
        ;;

        # Used memory is calculated using the following "formula":
        # (app + wired + occupied) * pagesize / 1024 / 1024
        (Darwin*)
            # Some older iOS devices don't have 'vm_stat'.
            has vm_stat || return

            # Older kernels don't have 'vm.page_pageable_internal_count'.
            p=$(sysctl -n vm.page_pageable_internal_count 2>/dev/null)
            [ -z "$p" ] && return

            mem_full=$(($(sysctl -n hw.memsize) / 1024 / 1024))
            hw_pagesize="$(sysctl -n hw.pagesize)"
            pages_used=$((p - $(sysctl -n vm.page_purgeable_count)))

            # Parse the 'vm_stat' file splitting on ':' and '.'.
            # The format of the file is 'key:   000.' and an additional
            # split is used on '.' to filter it out.
            while IFS=:. read -r key val; do
                case $key in
                    (*' wired'*|*' occupied'*)
                        pages_used=$((pages_used + ${val:-0}))
                    ;;
                esac

            # Using '<<-EOF' is the only way to loop over a command's
            # output without the use of a pipe ('|').
            # This ensures that any variables defined in the while loop
            # are still accessible in the script.
            done <<-EOF
                $(vm_stat)
			EOF

            mem_used=$((pages_used * hw_pagesize / 1024 / 1024))
        ;;

        (OpenBSD*)
            mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))

            # This is a really simpler parser for 'vmstat' which grabs
            # the used memory amount in a lazy way. 'vmstat' prints 3
            # lines of output with the needed value being stored in the
            # final line.
            #
            # This loop simply grabs the 3rd element of each line until
            # the EOF is reached. Each line overwrites the value of the
            # previous one so we're left with what we wanted. This isn't
            # slow as only 3 lines are parsed.
            while read -r _ _ line _; do
                mem_used=${line%%M}

            # Using '<<-EOF' is the only way to loop over a command's
            # output without the use of a pipe ('|').
            # This ensures that any variables defined in the while loop
            # are still accessible in the script.
            done <<-EOF
                $(vmstat)
			EOF
        ;;

        # Used memory is calculated using the following "formula":
        # mem_full - ((inactive + free + cache) * page_size / 1024)
        (FreeBSD*|DragonFly*)
            mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))

            # Use 'set --' to store the output of the command in the
            # argument list. POSIX sh has no arrays but this is close enough.
            #
            # Disable the shellcheck warning for word-splitting
            # as it's safe and intended ('set -f' disables globbing).
            # shellcheck disable=2046
            {
                set -f
                set +f -- $(sysctl -n hw.pagesize \
                                      vm.stats.vm.v_inactive_count \
                                      vm.stats.vm.v_free_count \
                                      vm.stats.vm.v_cache_count)
            }

            # Calculate the amount of used memory.
            # $1: hw.pagesize
            # $2: vm.stats.vm.v_inactive_count
            # $3: vm.stats.vm.v_free_count
            # $4: vm.stats.vm.v_cache_count
            mem_used=$((mem_full - (($2 + $3 + $4) * $1 / 1024 / 1024)))
        ;;

        (NetBSD*)
            mem_full=$(($(/sbin/sysctl -n hw.physmem64) / 1024 / 1024))

            # NetBSD implements a lot of the Linux '/proc' filesystem,
            # this uses the same parser as the Linux memory detection.
            while IFS=':k ' read -r key val _; do
                case $key in
                    (MemFree)
                        mem_free=$((val / 1024))
                        break
                    ;;
                esac
            done < /proc/meminfo

            mem_used=$((mem_full - mem_free))
        ;;

        (Haiku)
            # Read the first line of 'sysinfo -mem' splitting on
            # '(', ' ', and ')'. The needed information is then
            # stored in the 5th and 7th elements. Using '_' "consumes"
            # an element allowing us to proceed to the next one.
            #
            # The parsed format is as follows:
            # 3501142016 bytes free      (used/max  792645632 / 4293787648)
            IFS='( )' read -r _ _ _ _ mem_used _ mem_full <<-EOF
                $(sysinfo -mem)
			EOF

            mem_used=$((mem_used / 1024 / 1024))
            mem_full=$((mem_full / 1024 / 1024))
        ;;

        (Minix)
            # Minix includes the '/proc' filesystem though the format
            # differs from Linux. The '/proc/meminfo' file is only a
            # single line with space separated elements and elements
            # 2 and 3 contain the total and free memory numbers.
            read -r _ mem_full mem_free _ < /proc/meminfo

            mem_used=$(((mem_full - mem_free) / 1024))
            mem_full=$(( mem_full / 1024))
        ;;

        (MorphOS)
            while read -r line _ mem_used mem_full _; do
                if [ "$line" = "total" ]; then
                    mem_used=$(( mem_used / 1024 / 1024))
                    mem_full=$(( mem_full / 1024 / 1024))
                    break
                fi
            done <<-EOF
                $(avail)
			EOF
        ;;

        (SunOS)
            hw_pagesize=$(pagesize)

            # 'kstat' outputs memory in the following format:
            # unix:0:system_pages:pagestotal    1046397
            # unix:0:system_pages:pagesfree     885018
            #
            # This simply uses the first "element" (white-space
            # separated) as the key and the second element as the
            # value.
            #
            # A variable is then assigned based on the key.
            while read -r key val; do
                case $key in
                    (*total)
                        pages_full=$val
                    ;;

                    (*free)
                        pages_free=$val
                    ;;
                esac
            done <<-EOF
				$(kstat -p unix:0:system_pages:pagestotal \
                           unix:0:system_pages:pagesfree)
			EOF

            mem_full=$((pages_full * hw_pagesize / 1024 / 1024))
            mem_free=$((pages_free * hw_pagesize / 1024 / 1024))
            mem_used=$((mem_full - mem_free))
        ;;

        (IRIX)
            # Read the memory information from the 'top' command. Parse
            # and split each line until we reach the line starting with
            # "Memory".
            #
            # Example output: Memory: 160M max, 147M avail, .....
            while IFS=' :' read -r label mem_full _ mem_free _; do
                if [ "$label" = "Memory" ]; then
                    mem_full=${mem_full%M}
                    mem_free=${mem_free%M}
                    break
                fi
            done <<-EOF
                $(top -n)
			EOF

            mem_used=$((mem_full - mem_free))
        ;;

        (SerenityOS)
            IFS='{}' read -r _ memstat _ < /proc/memstat

            set -f -- "$IFS"
            IFS=,

            for pair in $memstat; do
                case $pair in
                    (*user_physical_allocated*)
                        mem_used=${pair##*:}
                    ;;

                    (*user_physical_available*)
                        mem_free=${pair##*:}
                    ;;
                esac
            done

            IFS=$1
            set +f --

            mem_used=$((mem_used * 4096 / 1024 / 1024))
            mem_free=$((mem_free * 4096 / 1024 / 1024))

            mem_full=$((mem_used + mem_free))
        ;;
    esac

    if [ -n "$mem_used" ] && [ -n "$mem_full" ]; then
        if [ -n "$ZSH_VERSION" ]; then
            # Zsh has decimal output here, I'm not sure why.
            # This removes the decimal point and everything after it.
            case $mem_used in
                (*.*)
                    mem_used=${mem_used%.*}
                ;;
            esac
        fi
        log memory "${mem_used}M / ${mem_full}M" >&6
    fi
}

get_disk() {
    # Fail if 'df' isn't installed.
    has df || return

    # Store the version of the 'df' command as the available
    # flags, options and implementation differs between operating
    # systems and we need to handle these edge-cases.
    df_version=$(df --version 2>&1)

    case $df_version in
        *GNU*)
            case $os in
                # The 'df' command from GNU coreutils works with the generic
                # flags in most scenarios, but on Cygwin/MSYS2 the 'source' entry
                # can have spaces if the Windows path to the Cygwin/MSYS2 installation
                # contains spaces, breaking the parsing of the 'df' output.
                # Using the --output flag with a layout that doesn't include the
                # source path is a workaround for this issue.
                (CYGWIN*|MSYS*|MINGW*|Windows_NT)
                    set -- -h --output=fstype,size,used,avail,pcent
                ;;

                (*)
                    set -- -P -h
                ;;
            esac
        ;;

        # The 'df' command is from AIX.
        *IMitv*)
            set -- -P -g
        ;;

        # The 'df' command is from IRIX.
        *befhikm*)
            non_human_df=1
            set -- -P -k
        ;;

        # The 'df' command is from OpenBSD.
        *hiklnP*)
            set -- -h
        ;;

        # The 'df' command is from Digital UNIX
        *Peikn*)
            non_human_df=1
            set -- -k
        ;;

        # The 'df' command is from NetBSD.
        *agln*)
            set -- -h
        ;;

        # The 'df' command is from Haiku.
        *Tracker*)
            haiku_df=1
        ;;

        # From testing it is safe to assume that
        # any other 'df' version provides these flags.
        *)
            set -- -P -h
        ;;
    esac

    # The main mount point to be checked by 'df'
    if [ -z "$PF_DISKPATH" ]; then
        case "$distro" in
            iOS*)     PF_DISKPATH="/private/var" ;;
            Android*) PF_DISKPATH="/data"        ;;
            Haiku*)   PF_DISKPATH="/boot"        ;;
            *)        PF_DISKPATH="/"            ;;
        esac
    fi

    # Fail if 'df' fails to run.
    df "$@" "$PF_DISKPATH" >/dev/null 2>&1 || return

    # Read the output of 'df' line by line. The first line
    # contains header information for the "table" so it is
    # skipped.
    #
    # The next lines are then split to grab the relevant
    # information and thankfully the output remains the
    # same between all but three 'df' implementations.
    if [ -n "$haiku_df" ]; then
        while IFS= read -r line; do
            case $line in
                (*Total\ Blocks:*)
                    full=${line##*:}
                    full=${full#"${full%%[! ]*}"}
                    fullblocks=${full#*\(}
                    fullblocks=${fullblocks%% *}
                    full=${full%% \(*}
                ;;
                (*Free\ Blocks:*)
                    free=${line##*\(}
                    free=${free%% *}
                ;;
            esac
        done <<-EOF
			$(df "$PF_DISKPATH")
		EOF

        usedblocks=$((fullblocks - free))

        # Convert the $used value to human readable format.
        used=$((usedblocks / 512))
        if [ $used -gt 1024 ]; then
            usedi=$((used / 1024))
            usedf=$((used % 1024 / 100))
            used="${usedi}.${usedf} GiB"
        else
            used="${used} MiB"
        fi

        perc=$((usedblocks * 100 / fullblocks))%
    else
        {
            read -r _ # discard the first line
            read -r _ full used _ perc _
        } <<-EOF
			$(df "$@" "$PF_DISKPATH")
		EOF
    fi

    # IRIX and Digital UNIX don't support human readable output,
    # so we need to convert the output to human readable format
    # manually.
    if [ -n "$non_human_df" ]; then
        if [ "$used" -gt 1024 ]; then
            usedi=$((used / 1024))
            usedf=$((used % 1024 / 100))
            used="${usedi}.${usedf}M"
            if [ $usedi -gt 1024 ]; then
                gusedi=$((usedi / 1024))
                usedf=$((usedi % 1024 / 100))
                used="${gusedi}.${usedf}G"
            fi
        else
            used="${used}K"
        fi
        if [ "$full" -gt 1024 ]; then
            fulli=$((full / 1024))
            fullf=$((full % 1024 / 100))
            full="${fulli}.${fullf}M"
            if [ $fulli -gt 1024 ]; then
                gfulli=$((fulli / 1024))
                fullf=$((fulli % 1024 / 100))
                full="${gfulli}.${fullf}G"
            fi
        else
            full="${full}K"
        fi
    fi

    log disk "$used / $full ($perc)" >&6
}

get_wm() {
    case $os in
        (Darwin*|CYGWIN*|MSYS*|MINGW*|Windows_NT)
            # Don't display window manager on macOS or Windows.
        ;;

        (*)
            # xprop can be used to grab the window manager's properties
            # which contains the window manager's name under '_NET_WM_NAME'.
            #
            # The upside to using 'xprop' is that you don't need to hardcode
            # a list of known window manager names. The downside is that
            # not all window managers conform to setting the '_NET_WM_NAME'
            # atom..
            #
            # List of window managers which fail to set the name atom:
            # catwm, fvwm, dwm, 2bwm, monster, wmaker and sowm [mine! ;)].
            #
            # The final downside to this approach is that it does _not_
            # support Wayland environments. The only solution which supports
            # Wayland is the 'ps' parsing mentioned below.
            #
            # A more naive implementation is to parse the last line of
            # '~/.xinitrc' to extract the second white-space separated
            # element.
            #
            # The issue with an approach like this is that this line data
            # does not always equate to the name of the window manager and
            # could in theory be _anything_.
            #
            # This also fails when the user launches xorg through a display
            # manager or other means.
            #
            #
            # Another naive solution is to parse 'ps' with a hardcoded list
            # of window managers to detect the current window manager (based
            # on what is running).
            #
            # The issue with this approach is the need to hardcode and
            # maintain a list of known window managers.
            #
            # Another issue is that process names do not always equate to
            # the name of the window manager. False-positives can happen too.
            #
            # This is the only solution which supports Wayland based
            # environments sadly. It'd be nice if some kind of standard were
            # established to identify Wayland environments.
            #
            # pfetch's goal is to remain _simple_, if you'd like a "full"
            # implementation of window manager detection use 'neofetch'.
            #
            # Neofetch use a combination of 'xprop' and 'ps' parsing to
            # support all window managers (including non-conforming and
            # Wayland) though it's a lot more complicated!

            # Don't display window manager if X isn't running.
            [ -n "$DISPLAY" ] || return

            # This is a two pass call to xprop. One call to get the window
            # manager's ID and another to print its properties.
            if has xprop; then
                # The output of the ID command is as follows:
                # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
                #
                # To extract the ID, everything before the last space
                # is removed.
                id=$(xprop -root -notype _NET_SUPPORTING_WM_CHECK)
                id=${id##* }

                # The output of the property command is as follows:
                # _NAME 8t
                # _NET_WM_PID = 252
                # _NET_WM_NAME = "bspwm"
                # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
                # WM_CLASS = "wm", "Bspwm"
                #
                # To extract the name, everything before '_NET_WM_NAME = \"'
                # is removed and everything after the next '"' is removed.
                wm=$(xprop -id "$id" -notype -len 25 -f _NET_WM_NAME 8t)
            fi

            # Handle cases of a window manager _not_ populating the
            # '_NET_WM_NAME' atom. Display nothing in this case.
            case $wm in
                (*'_NET_WM_NAME = '*)
                    wm=${wm##*_NET_WM_NAME = \"}
                    wm=${wm%%\"*}
                ;;

                (*)
                    # Fallback to checking the process list
                    # for the select few window managers which
                    # don't set '_NET_WM_NAME'.
                    while read -r ps_line; do
                        case $ps_line in
                            (*catwm*)     wm=catwm          ;;
                            (*fvwm*)      wm=fvwm           ;;
                            (*dwm*)       wm=dwm            ;;
                            (*2bwm*)      wm=2bwm           ;;
                            (*monsterwm*) wm=monsterwm      ;;
                            (*wmaker*)    wm='Window Maker' ;;
                            (*sowm*)      wm=sowm           ;;
                            (*penrose*)   wm=penrose        ;;
                            (*Hyprland*)  wm=Hyprland       ;;
                        esac
                    done <<-EOF
                        $(ps x)
					EOF
                ;;
            esac
        ;;
    esac

    log wm "$wm" >&6
}


get_de() {
    # This only supports Xorg related desktop environments though
    # this is fine as knowing the desktop environment on Windows,
    # macOS etc is useless (they'll always report the same value).
    #
    # Display the value of '$XDG_CURRENT_DESKTOP', if it's empty,
    # display the value of '$DESKTOP_SESSION'.
    log de "${XDG_CURRENT_DESKTOP:-$DESKTOP_SESSION}" >&6
}

get_shell() {
    # Display the basename of the '$SHELL' environment variable.
    log shell "${SHELL##*/}" >&6
}

get_editor() {
    # Display the value of '$VISUAL', if it's empty, display the
    # value of '$EDITOR'.
    editor=${VISUAL:-"$EDITOR"}

    log editor "${editor##*/}" >&6
}

get_term() {
    log term "${TERMINAL:-$TERM}" >&6
}

get_cpus() {
    # Display the number of CPUs.
    if has nproc; then
        cpus=$(nproc)
    else
        case $os in
            (Darwin*|FreeBSD*|DragonFly*|OpenBSD*)
                cpus=$(sysctl -n hw.ncpu)
            ;;

            (NetBSD*)
                cpus=$(/sbin/sysctl -n hw.ncpu)
            ;;
        esac
    fi

    [ -n "$cpus" ] && log cpus "$cpus" >&6
}

get_resolution() {
    case $os in
        Linux*|GNU*|*BSD*)
            if [ -n "$DISPLAY" ]; then
                if has xdpyinfo; then
                    resolution=$(xdpyinfo | grep 'dimensions')
                    resolution="${resolution#*dimensions:}"
                    resolution="${resolution% pixels*}"
                elif has xrandr; then
                    # shellcheck disable=2063
                    resolution="$(xrandr | grep '*')"
                    resolution="${resolution#* }"
                    resolution="${resolution% *}"
                fi
            fi
        ;;
        Darwin*)
            if has system_profiler; then
                while IFS= read -r line; do
                    case $line in
                        *Resolution:*)
                            {
                                IFS=' ' read -r _ width _ height _
                                resolution="${width}x${height}"
                            } <<-EOF
								$line
							EOF
                        ;;
                    esac
                done <<-EOF
					$(system_profiler SPDisplaysDataType)
				EOF
            fi
        ;;
        CYGWIN*|MSYS*|MINGW*|Windows_NT)
            width=$(wmic_read path Win32_VideoController get CurrentHorizontalResolution)
            height=$(wmic_read path Win32_VideoController get CurrentVerticalResolution)
            resolution="${width}x${height}"
        ;;
    esac

    log resolution "$resolution" >&6
}

get_palette() {
    # Print the first 8 terminal colors. This uses the existing
    # sequences to change text color with a sequence prepended
    # to reverse the foreground and background colors.
    #
    # This allows us to save hardcoding a second set of sequences
    # for background colors.
    #
    # False positive.
    # shellcheck disable=2154
    {
        esc SGR 7
        palette="$e$c1 $c1 $c2 $c2 $c3 $c3 $c4 $c4 $c5 $c5 $c6 $c6 "
        esc SGR 0
        palette="$palette$e"
    }

    # Print the palette with a new-line before and afterwards but no separator.
    printf '\n' >&6
    log "$palette
        " " " " " >&6
}

get_ascii() {
    # This is a simple function to read the contents of
    # an ascii file from 'stdin'. It allows for the use
    # of '<<-EOF' to prevent the break in indentation in
    # this source code.
    #
    # This function also sets the text colors according
    # to the ascii color.
    read_ascii() {
        # 'PF_COL1': Set the info name color according to ascii color.
        # 'PF_COL3': Set the title color to some other color. ¯\_(ツ)_/¯
        PF_COL1=${PF_COL1:-${1:-7}}
        PF_COL3=${PF_COL3:-$((${1:-7}%8+1))}

        # POSIX sh has no 'var+=' so 'var=${var}append' is used. What's
        # interesting is that 'var+=' _is_ supported inside '$(())'
        # (arithmetic) though there's no support for 'var++/var--'.
        #
        # There is also no $'\n' to add a "literal"(?) newline to the
        # string. The simplest workaround being to break the line inside
        # the string (though this has the caveat of breaking indentation).
        while IFS= read -r line; do
            ascii="$ascii$line
"
        done
    }

    if [ -f "$PF_CUSTOM_ASCII" ]; then
        read_ascii < "$PF_CUSTOM_ASCII"
    else
        # This checks for ascii art in the following order:
        # '$1':        Argument given to 'get_ascii()' directly.
        # '$PF_ASCII': Environment variable set by user.
        # '$distro':   The detected distribution name.
        # '$os':       The name of the operating system/kernel.
        #
        # NOTE: Each ascii art below is indented using tabs, this
        #       allows indentation to continue naturally despite
        #       the use of '<<-EOF'.
        #
        # False positive.
        # shellcheck disable=2154
        case ${1:-${PF_ASCII:-${distro:-$os}}} in
            [Aa][Ii][Xx])
                read_ascii 4 <<-EOF
					${c2}    __    __ _    _
					   /_ \\   || \\\\  //
					  //_\ \\  ||  \\\\//
					 / ___  \\ ||  //\\\\
					/_/   \\__\\|| //  \\\\
				EOF
            ;;

            [Aa]lma*)
                read_ascii 3 <<- EOF
					${c1}    {#@ .,  ${c3} 	,..<.
					${c1}    ._\`=#  ${c3}/#=#,""
					${c1}   \\##  \` ${c3}|\`  '=##
					${c4}   .${c1}\`'=.  ${c3}\\  ${c2},_,.,
					${c4} .."#-   ${c1}\`  ${c2}\`    \\##
					${c4}\## #   ./  ${c6}\\.  ${c2}.#\`,+
					${c4}   =##=' ${c6},   |, ${c2}|# ""
					${c6}       =##=++#|
					         ##\\''
					         \`'\`
				EOF
            ;;

            ([Aa]lpine*)
                read_ascii 4 <<-EOF
					${c4}   /\\ /\\
					  /${c7}/ ${c4}\\  \\
					 /${c7}/   ${c4}\\  \\
					/${c7}//    ${c4}\\  \\
					${c7}//      ${c4}\\  \\
					         \\
				EOF
            ;;

            ([Aa]ndroid*)
                read_ascii 2 <<-EOF
					${c2}  ;,           ,;
					   ';,.-----.,;'
					  ,'           ',
					 /    O     O    \\
					|                 |
					'-----------------'
				EOF
            ;;

            ([Aa]rch*)
                read_ascii 4 <<-EOF
					${c6}       /\\
					      /  \\
					     /\\   \\
					${c4}    /      \\
					   /   ,,   \\
					  /   |  |  -\\
					 /_-''    ''-_\\
				EOF
            ;;

            ([Aa]rco*)
                read_ascii 4 <<-EOF
					${c4}      /\\
					     /  \\
					    /    \\
					   /  /\\  \\
					  /  /  \\  \\
					 /  / ___\\  \\
					/__/   ``'--___\\
				EOF
            ;;

            ([Aa]rtix*)
                read_ascii 6 <<-EOF
					${c4}      /\\
					     /  \\
					    /\`'.,\\
					   /     ',
					  /      ,\`\\
					 /   ,.'\`.  \\
					/.,'\`     \`'.\\
				EOF
            ;;

            ([Bb]edrock*)
                read_ascii 4 <<-EOF
					${c7}__
					\\ \\___
					 \\  _ \\
					  \\___/
				EOF
            ;;

            ([Aa]mog[Oo][Ss]*)
                read_ascii 4 <<-EOF
					${c7}      -///:.
					     smhhhhmh\`
					    :NA${c4}mogO${c7}SNs
					    hNNmmmmNNN
					    NNNNNNNNNN
					   :NNNNNNNNNN
					   mNNssussNNN
					  sNn:    sNNo
					+ooo+     sNNo
					        +oooo\`
				EOF
            ;;

            ([Bb]uildroot*)
                read_ascii 3 <<-EOF
					${c3}   ___
					 / \`   \\
					|   :  :|
					-. _:__.-
					  \` ---- \`
				EOF
            ;;

            ([Cc]el[Oo][Ss]*)
                read_ascii 5 0 <<-EOF
					${c5}      .////\\\\\//\\.
					     //_         \\\\
					    /_  ${c7}##############
					${c5}   //              *\\
					${c7}###############    ${c5}|#
					   \/              */
					    \*   ${c7}##############
					${c5}     */,        .//
					      '_///\\\\\//_'
				EOF
            ;;

            ([Cc]ent[Oo][Ss]*)
                read_ascii 5 <<-EOF
					${c2} ____${c3}^${c5}____
					${c2} |\\  ${c3}|${c5}  /|
					${c2} | \\ ${c3}|${c5} / |
					${c5}<---- ${c4}---->
					${c4} | / ${c2}|${c3} \\ |
					${c4} |/__${c2}|${c3}__\\|
					${c2}     v
				EOF
            ;;

            ([Cc]rystal*[Ll]inux)
                read_ascii 5 5 <<-EOF
					${c5}        -//.
					      -//.
					    -//. .
					  -//.  '//-
					 /+:      :+/
					  .//'  .//.
					    . .//.
					    .//.
					  .//.
				EOF
            ;;

            CYGWIN*|MSYS*|MINGW*|[Ww]indows*)
                read_ascii 4 <<-EOF
					${c4}######  ######
					######  ######
					######  ######

					######  ######
					######  ######
					######  ######
				EOF
            ;;

            ([Dd]ahlia*)
                read_ascii 1 <<-EOF
					${c1}      _
					  ___/ \\___
					 |   _-_   |
					 | /     \ |
					/ |       | \\
					\\ |       | /
					 | \ _ _ / |
					 |___ - ___|
					     \\_/
				EOF
            ;;

            ([Dd]ebian*)
                read_ascii 1 <<-EOF
					${c1}  _____
					 /  __ \\
					|  /    |
					|  \\___-
					-_
					  --_
				EOF
            ;;

            ([Dd]evuan*)
                read_ascii 6 <<-EOF
					${c4} ..:::.
					    ..-==-
					        .+#:
					         =@@
					      :+%@#:
					.:=+#@@%*:
					#@@@#=:
				EOF
            ;;

            ([Dd]ragon[Ff]ly*)
                read_ascii 1 <<-EOF
					    ,${c1}_${c7},
					 ('-_${c1}|${c7}_-')
					  >--${c1}|${c7}--<
					 (_-'${c1}|${c7}'-_)
					     ${c1}|
					     |
					     |
				EOF
            ;;

            ([Dd]igital\ [Uu][Nn][Ii][Xx])
                read_ascii 1 <<-EOF
					      ${c1}digital

					${c2}|| ||\\\\ ||||\\\\  //
					|| |||\\\\|||| \\\\//
					||_||||\\\\||| //\\\\
					\___/|| \\|||//  \\\\
				EOF
            ;;

            ([Ee]lementary*)
                read_ascii <<-EOF
					${c7}  _______
					 / ____  \\
					/  |  /  /\\
					|__\\ /  / |
					\\   /__/  /
					 \\_______/
				EOF
            ;;

            ([Ee]ndeavour*)
                read_ascii 5 <<-EOF
					      ${c1}/${c5}\\
					    ${c1}/${c5}/  \\${c4}\\
					   ${c1}/${c5}/    \\ ${c4}\\
					 ${c1}/ ${c5}/     _) ${c4})
					${c1}/_${c5}/___-- ${c4}__-
					 /____--
				EOF
            ;;

            ([Ee]volution*)
                read_ascii 4 <<-EOF
					      ${c7},coddoc'
					   'cddddddddddc'
					 'ddd${c2}OWWXXXXXXK${c7}ddo.
					.dddd${c2}OMX${c7}ddddddddddd.
					odddd${c2}OMXk00OkOO${c7}ddddo
					.dddd${c2}OMX${c7}kOOOxOkdddd.
					 .ddd${c2}OWWXXXXXXK${c7}ddd'
					   'dddddddddddd'
					      'cddddd,
				EOF
            ;;

            ([Ff]edora*)
                read_ascii 4 <<-EOF
					        ${c4},'''''.
					       |   ,.  |
					       |  |  '_'
					  ,....|  |..
					.'  ,_;|   ..'
					|  |   |  |
					|  ',_,'  |
					 '.     ,'
					   '''''
				EOF
            ;;

            ([Ff]ree[Bb][Ss][Dd]*)
                read_ascii 1 <<-EOF
					${c1}/\\,-'''''-,/\\
					\\_)       (_/
					|           |
					|           |
					 ;         ;
					  '-_____-'
				EOF
            ;;

            ([Gg]aruda*)
                read_ascii 4 <<-EOF
					${c3}         _______
					      __/       \\_
					    _/     /      \\_
					${c7}  _/      /_________\\
					_/                  |
					${c2}\\     ____________
					 \\_            __/
					   \\__________/
				EOF
            ;;

            ([Gg]entoo*)
                read_ascii 5 <<-EOF
					${c5} _-----_
					(       \\
					\\    0   \\
					${c7} \\        )
					 /      _/
					(     _-
					\\____-
				EOF
            ;;

            ([Gg][Nn][Uu]*|[Hh]urd*)
                read_ascii 7 <<-EOF
					${c7} ---${c3}[ ]${c7}<----
					 v   |      |
					${c3}[ ]${c7}  --->${c3}[ ]${c7}|
					 ^        | |
					 |  ${c3}[ ]${c7}---|--
					  ---------
				EOF
            ;;

            ([Gg]uix[Ss][Dd]*|[Gg]uix*)
                read_ascii 3 <<-EOF
					${c3}|.__          __.|
					|__ \\        / __|
					   \\ \\      / /
					    \\ \\    / /
					     \\ \\  / /
					      \\ \\/ /
					       \\__/
				EOF
            ;;

            ([Hh]aiku*)
                read_ascii 3 <<-EOF
					${c3}       ,^,
					      /   \\
					*--_ ;     ; _--*
					\\   '"     "'   /
					 '.           .'
					.-'"         "'-.
					 '-.__.   .__.-'
					       |_|
				EOF
            ;;

            ([Hh][Pp]-[Uu][Xx]*)
                read_ascii 4 <<-EOF
					  ${c4}____${c7}__${c4}______
					 /   ${c7}/ /${c4}      \\
					/   ${c7}/ /_  ____${c4} \\
					|  ${c7}/ __ \\/ __ \\${c4}|
					| ${c7}/ / / / /_/ /${c4}|
					|${c7}/_/ /_/ .___/${c4} |
					\\     ${c7}/ /${c4}      /
					 \\___${c7}/_/${c4}______/
				EOF
            ;;

            ([Hh]ydro[Oo][Ss]*)
                read_ascii 4 <<-EOF
					${c1}╔╗╔╗──╔╗───╔═╦══╗
					║╚╝╠╦╦╝╠╦╦═╣║║══╣
					║╔╗║║║╬║╔╣╬║║╠══║
					╚╝╚╬╗╠═╩╝╚═╩═╩══╝
					───╚═╝
				EOF
            ;;

            ([Hh]yperbola*)
                read_ascii <<-EOF
					${c7}    |\`__.\`/
					    \____/
					    .--.
					   /    \\
					  /  ___ \\
					 / .\`   \`.\\
					/.\`      \`.\\
				EOF
            ;;

            ([Ii]glunix*)
                read_ascii <<-EOF
					       |
					       |          |
					                  |
					  |    ________
					  |  /\\   |    \\
					    /  \\  |     \\  |
					   /    \\        \\ |
					  /      \\________\\
					  \\      /        /
					   \\    /        /
					    \\  /        /
					     \\/________/
				EOF
            ;;

            ([Ii]nstant[Oo][Ss]*)
                read_ascii <<-EOF
					 ,-''-,
					: .''. :
					: ',,' :
					 '-____:__
					       :  \`.
					       \`._.'
				EOF
            ;;

            ([Ii][Rr][Ii][Xx]*)
                read_ascii 1 <<-EOF
					${c1} __
					 \\ \\   __
					  \\ \\ / /
					   \\ v /
					   / . \\
					  /_/ \\ \\
					       \\_\\
				EOF
            ;;

            ([Kk][Dd][Ee]*[Nn]eon*)
                read_ascii 6 <<-EOF
					${c7}   .${c6}__${c7}.${c6}__${c7}.
					${c6}  /  _${c7}.${c6}_  \\
					 /  /   \\  \\
					${c7} . ${c6}|  ${c7}O${c6}  | ${c7}.
					${c6} \\  \\_${c7}.${c6}_/  /
					  \\${c7}.${c6}__${c7}.${c6}__${c7}.${c6}/
				EOF
            ;;

            ([Ll]inux*[Ll]ite*|[Ll]ite*)
                read_ascii 3 <<-EOF
					${c3}   /\\
					  /  \\
					 / ${c7}/ ${c3}/
				> ${c7}/ ${c3}/
					\\ ${c7}\\ ${c3}\\
					 \\_${c7}\\${c3}_\\
					${c7}    \\
				EOF
            ;;

            ([Ll]inux*[Mm]int*|[Mm]int)
                read_ascii 2 <<-EOF
					${c2} ___________
					|_          \\
					  | ${c7}| _____ ${c2}|
					  | ${c7}| | | | ${c2}|
					  | ${c7}| | | | ${c2}|
					  | ${c7}\\_____/ ${c2}|
					  \\_________/
				EOF
            ;;

            ([Ll]inux*)
                read_ascii 4 <<-EOF
					${c4}    ___
					   (${c7}.. ${c4}|
					   (${c5}<> ${c4}|
					  / ${c7}__  ${c4}\\
					 ( ${c7}/  \\ ${c4}/|
					${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4})
					${c5}\/${c4}-____${c5}\/
				EOF
            ;;

            ([Mm]ac[Oo][Ss]*|[Dd]arwin*)
                read_ascii 1 <<-EOF
					${c2}       .:'
					    _ :'_
					${c3} .'\`_\`-'_\`\`.
					${c1}:________.-'
					:_______:
					${c4} :_______\`-;
					${c5}  \`._.-._.'
				EOF
            ;;

            ([Mm]ageia*)
                read_ascii 2 <<-EOF
					${c6}   *
					    *
					   **
					${c7} /\\__/\\
					/      \\
					\\      /
					 \\____/
				EOF
            ;;

            ([Mm]anjaro*)
                read_ascii 2 <<-EOF
					${c2}||||||||| ||||
					||||||||| ||||
					||||      ||||
					|||| |||| ||||
					|||| |||| ||||
					|||| |||| ||||
					|||| |||| ||||
				EOF
            ;;

            ([Mm]inix*)
                read_ascii 4 <<-EOF
					${c4} ,,        ,,
					;${c7},${c4} ',    ,' ${c7},${c4};
					; ${c7}',${c4} ',,' ${c7},'${c4} ;
					;   ${c7}',${c4}  ${c7},'${c4}   ;
					;  ${c7};, '' ,;${c4}  ;
					;  ${c7};${c4};${c7}',,'${c4};${c7};${c4}  ;
					', ${c7};${c4};;  ;;${c7};${c4} ,'
					  '${c7};${c4}'    '${c7};${c4}'
				EOF
            ;;

            ([Mm]orphOS*)
                read_ascii 1 <<-EOF
					${c4}  __ \/ __
					 /o \{}/ o\\
					 \   ()   /
					  \`> /\ <\`
					  (o/\/\o)
					   )    (
				EOF
            ;;

            ([Mm][Xx]*)
                read_ascii <<-EOF
					${c7}    \\\\  /
					     \\\\/
					      \\\\
					   /\\/ \\\\
					  /  \\  /\\
					 /    \\/  \\
				/__________\\
				EOF
            ;;

            ([Nn]et[Bb][Ss][Dd]*)
                read_ascii 3 <<-EOF
					${c7}\\\\${c3}\`-______,----__
					${c7} \\\\        ${c3}__,---\`_
					${c7}  \\\\       ${c3}\`.____
					${c7}   \\\\${c3}-______,----\`-
					${c7}    \\\\
					     \\\\
					      \\\\
				EOF
            ;;

            ([Nn]ix[Oo][Ss]*)
                read_ascii 4 <<-EOF
					${c4}  \\\\  \\\\ //
					 ==\\\\__\\\\/ //
					   //   \\\\//
					==//     //==
					 //\\\\___//
					// /\\\\  \\\\==
					  // \\\\  \\\\
				EOF
            ;;

            [Nn]obara*)
                read_ascii 4 <<- EOF
					${c7} _._.  _..,._
					|##############.
					|################.
					|#####/  .  \\#####.
					|####|  ###   > ###
					|#####  \`\`\`  |#####
					|######==_   |#####
					|######"##|  |#####
					 \`""\`\`    '  \`\\##/
				EOF
            ;;

            ([Oo]pen[Bb][Ss][Dd]*)
                read_ascii 3 <<-EOF
					${c3}      _____
					    \\-     -/
					 \\_/         \\
					 |        ${c7}O O${c3} |
					 |_  <   )  3 )
					 / \\         /
					    /-_____-\\
				EOF
            ;;

            ([Oo]pen[Ss][Uu][Ss][Ee]*[Tt]umbleweed*)
                read_ascii 2 <<-EOF
					${c2}  _____   ______
					 / ____\\ / ____ \\
					/ /    \`/ /    \\ \\
					\\ \\____/ /,____/ /
					 \\______/ \\_____/
				EOF
            ;;

            ([Oo]pen[Ss][Uu][Ss][Ee]*|[Oo]pen*SUSE*|SUSE*|suse*)
                read_ascii 2 <<-EOF
					${c2}  _______
					__|   __ \\
					     / .\\ \\
					     \\__/ |
					   _______|
					   \\_______
					__________/
				EOF
            ;;

            ([Oo]pen[Ww]rt*)
                read_ascii 1 <<-EOF
					${c1} _______
					|       |.-----.-----.-----.
					|   -   ||  _  |  -__|     |
					|_______||   __|_____|__|__|
					 ________|__|    __
					|  |  |  |.----.|  |_
					|  |  |  ||   _||   _|
					|________||__|  |____|
				EOF
            ;;

            ([Oo]racle*)
            read_ascii 1 <<- EOF
					${c1}     ___________
					 ./##+++++++++++##\\.
					/#/               \\#\\
					|#|               |#|
					\\#\\               /#/
					 "##.............##"
					   \`'''''''''''''\`
				EOF
            ;;

            ([Pp]arabola*)
                read_ascii 5 <<-EOF
					${c5}  __ __ __  _
					.\`_//_//_/ / \`.
					          /  .\`
					         / .\`
					        /.\`
					       /\`
				EOF
            ;;

            ([Pp]op!_[Oo][Ss]*)
                read_ascii 6 <<-EOF
					${c6}     .///////,
					   //${c7}76767${c6}//////
					  //${c7}76${c6}//${c7}76${c6}//${c7}767${c6}//
					 ////${c7}7676'${c6}//${c7}76${c6}////
					 /////${c7}76${c6}////${c7}7${c6}/////
					  /////${c7}76${c6}//${c7}76${c6}////
					   ///${c7}76767676${c6}//
					     \`///////'
				EOF
            ;;

            ([Pp]ure[Oo][Ss]*)
                read_ascii <<-EOF
					${c7} _____________
					|  _________  |
					| |         | |
					| |         | |
					| |_________| |
					|_____________|
				EOF
            ;;

            ([Rr]aspbian*)
                read_ascii 1 <<-EOF
					${c2}  __  __
					 (_\\)(/_)
					${c1} (_(__)_)
					(_(_)(_)_)
					 (_(__)_)
					   (__)
				EOF
            ;;

            ([Ss]erenity[Oo][Ss]*)
                read_ascii 4 <<-EOF
					${c7}    _____
					${c1}  ,-${c7}     -,
					${c1} ;${c7} (       ;
					${c1}| ${c7}. \_${c1}.,${c7}    |
					${c1}|  ${c7}o  _${c1} ',${c7}  |
					${c1} ;   ${c7}(_)${c1} )${c7} ;
					${c1}  '-_____-${c7}'
				EOF
            ;;

            ([Ss]lackware*)
                read_ascii 4 <<-EOF
					${c4}   ________
					  /  ______|
					  | |______
					  \\______  \\
					   ______| |
					| |________/
					|____________
				EOF
            ;;

            ([Ss]olus*)
                read_ascii 4 <<-EOF
					${c6}
					     /|
					    / |\\
					   /  | \\ _
					  /___|__\\_\\
					 \\         /
					  \`-------´
				EOF
            ;;

            [Ss]team*)
                read_ascii 5 <<- EOF
					${c5}        ____
					    .=+#######=.
					  ;########-"""-#\\.
					 ###${c7}####${c5}#/ .'\`'.'##.
					${c7}:########  |   | ${c5}|##
					${c7}\`''"=#='    \`'\`,.##${c5}#
					${c7}      \`'.  .+#######
					\`##.  .." #########
					  '=#=.,+########"
					    \`"=######="\`
				EOF
            ;;

            ([Ss]un[Oo][Ss]|[Ss]olaris*)
                read_ascii 3 <<-EOF
					${c3}       .   .;   .
					   .   :;  ::  ;:   .
					   .;. ..      .. .;.
					..  ..             ..  ..
					 .;,                 ,;.
				EOF
            ;;

            ([Uu]buntu*)
                read_ascii 3 <<-EOF
					${c3}         _
					     ---(_)
					 _/  ---  \\
					(_) |   |
					  \\  --- _/
					     ---(_)
				EOF
            ;;

            [Vv]anilla*)
                read_ascii 3 <<- EOF
					${c3}        .#.
					       :#=#:
					.#=":.:##=##:.:"=#.
					\`:#=#####=####=##:\`
					 \`:####=\` \`=####:\`
					   .:##,. .,##:.
					  :##=##:-:##=##:
					 .#=##:\`   \`:##=#.
					  \`\`           \`\`
				EOF
            ;;

            ([Vv]oid*)
                read_ascii 2 <<-EOF
					${c2}    _______
					 _ \\______ -
					| \\  ___  \\ |
					| | /   \ | |
					| | \___/ | |
					| \\______ \\_|
					 -_______\\
				EOF
            ;;

            ([Xx]eonix*)
                read_ascii 2 <<-EOF
					${c2}    ___  ___
					___ \  \/  / ___
					\  \ \    / /  /
					 \  \/    \/  /
					  \    /\    /
					   \__/  \__/
				EOF
            ;;

            (*)
                # On no match of a distribution ascii art, this function calls
                # itself again, this time to look for a more generic OS related
                # ascii art (KISS Linux -> Linux).
                [ -n "$1" ] || {
                    get_ascii "$os"
                    return
                }

                printf 'error: %s is not currently supported.\n' "$os" >&6
                printf 'error: Open an issue for support to be added.\n' >&6
                exit 1
            ;;
        esac
    fi

    # Store the "width" (longest line) and "height" (number of lines)
    # of the ascii art for positioning. This script prints to the screen
    # *almost* like a TUI does. It uses escape sequences to allow dynamic
    # printing of the information through user configuration.
    #
    # Iterate over each line of the ascii art to retrieve the above
    # information.
    while IFS= read -r line; do
        # Strip '\033[3Xm' color codes from the ascii art
        # so they don't affect the width variable.
        while true; do
            case $line in
                *\[3?m*)
                    line="${line%%\[3?m*}${line#*\[3?m}"
                ;;
                *)
                    break
                ;;
            esac
        done

        ascii_height=$((${ascii_height:-0} + 1))

        # This was a ternary operation but they aren't supported in
        # Minix's shell.
        [ "${#line}" -gt "${ascii_width:-0}" ] &&
            ascii_width=${#line}

    # Using '<<-EOF' is the only way to loop over a command's
    # output without the use of a pipe ('|').
    # This ensures that any variables defined in the while loop
    # are still accessible in the script.
    done <<-EOF
		$(printf %s "$ascii")
	EOF

    # Add a gap between the ascii art and the information.
    ascii_width=$((ascii_width + 3))

    # Print the ascii art and position the cursor back where we
    # started prior to printing it.
    {
        esc_p SGR 1
        printf '%s' "$ascii"
        esc_p SGR 0
        esc_p CUU "$ascii_height"
    } >&6
}

main() {
    case $* in
        -v|--version)
            printf '%s 1.7.0\n' "${0##*/}"
            return 0
        ;;

        -d|--debug)
            # Below exec is not run, stderr is shown.
        ;;

        '')
            exec 2>/dev/null
        ;;

        *)
            printf '%s' "\
${0##*/}     show system information
${0##*/} -d  debug mode
${0##*/} -v  show version
"
            return 0
        ;;
    esac

    # Fail if uname is unavailable.
    if ! has uname; then
        printf 'Missing dependency: uname\n'
        exit 1
    fi

    # Hide 'stdout' and selectively print to it using '>&6'.
    # This gives full control over what it displayed on the screen.
    exec 6>&1 >/dev/null

    # Store raw escape sequence character for later reuse.
    esc_c=$(printf '\033')

    # Allow the user to execute their own script and modify or
    # extend pfetch's behavior.
    # shellcheck source=/dev/null
    [ -f "$PF_SOURCE" ] && . "$PF_SOURCE"

    [ -n "$NO_COLOR" ] && PF_COLOR=0

    # Ensure that the 'TMPDIR' is writable as heredocs use it and
    # fail without the write permission. This was found to be the
    # case on Android where the temporary directory requires root.
    [ -w "${TMPDIR:-/tmp}" ] || export TMPDIR=~

    # Generic color list.
    # Disable warning about unused variables.
    # shellcheck disable=2034
    for _c in c0 c1 c2 c3 c4 c5 c6 c7; do
        esc SGR "3${_c#?}"
        export "$_c=$e"
    done

    # Disable line wrapping and catch the EXIT signal to enable it again
    # on exit. Ideally you'd somehow query the current value and retain
    # it but I'm yet to see this irk anyone.
    esc_p DECAWM l >&6
    trap 'esc_p DECAWM h >&6' EXIT

    # Store the output of 'uname' to avoid calling it multiple times
    # throughout the script. 'read <<EOF' is the simplest way of reading
    # a command into a list of variables.
    read -r os kernel arch <<-EOF
		$(uname -srm)
	EOF

    # Always run 'get_os' for the purposes of detecting which ascii
    # art to display.
    get_os

    # Allow the user to specify the order and inclusion of information
    # functions through the 'PF_INFO' environment variable.
    [ "$PF_INFO" = "all" ] &&
        PF_INFO="ascii title os host kernel uptime pkgs memory disk wm de shell editor term resolution cpus palette"
    # shellcheck disable=2086
    {
        # Disable globbing and set the positional parameters to the
        # contents of 'PF_INFO'.
        set -f
        set +f -- ${PF_INFO-ascii title os host kernel uptime pkgs memory}

        # Iterate over the info functions to determine the lengths of the
        # "info names" for output alignment. The option names and subtitles
        # match 1:1 so this is thankfully simple.
        for info do
            has "get_$info" || continue

            # This was a ternary operation but they aren't supported in
            # Minix's shell.
            [ "${#info}" -gt "${info_length:-0}" ] &&
                info_length=${#info}
        done

        # Add an additional space of length to act as a gap.
        info_length=$((info_length + 1))

        # Iterate over the above list and run any existing "get_" functions.
        for info do
            "get_$info"
        done
    }

    # Position the cursor below both the ascii art and information lines
    # according to the height of both. If the information exceeds the ascii
    # art in height, don't touch the cursor (0/unset), else move it down
    # N lines.
    #
    # This was a ternary operation but they aren't supported in Minix's shell.
    [ "${info_height:-0}" -lt "${ascii_height:-0}" ] &&
        cursor_pos=$((ascii_height - info_height))

    # Print '$cursor_pos' amount of newlines to correctly position the
    # cursor. This used to be a 'printf $(seq X X)' however 'seq' is only
    # typically available (by default) on GNU based systems!
    while [ "${i:=0}" -le "${cursor_pos:-0}" ]; do
        printf '\n'
        i=$((i + 1))
    done >&6
}

main "$@"
