master ohmu/dihfiles / bin / pfetch
ohmu  ·  2026-09-26
   1#!/bin/sh
   2#
   3# pfetch - Simple POSIX sh fetch script.
   4
   5# Wrapper around all escape sequences used by pfetch to allow for
   6# greater control over which sequences are used (if any at all).
   7esc() {
   8    case $1 in
   9        CUU) e="${esc_c}[${2}A" ;; # cursor up
  10        CUD) e="${esc_c}[${2}B" ;; # cursor down
  11        CUF) e="${esc_c}[${2}C" ;; # cursor right
  12        CUB) e="${esc_c}[${2}D" ;; # cursor left
  13
  14        # text formatting
  15        SGR)
  16            case ${PF_COLOR:=1} in
  17                (1)
  18                    e="${esc_c}[${2}m"
  19                ;;
  20
  21                (0)
  22                    # colors disabled
  23                    e=
  24                ;;
  25            esac
  26        ;;
  27
  28        # line wrap
  29        DECAWM)
  30            case $TERM in
  31                (dumb | minix | cons25)
  32                    # not supported
  33                    e=
  34                ;;
  35
  36                (*)
  37                    e="${esc_c}[?7${2}"
  38                ;;
  39            esac
  40        ;;
  41    esac
  42}
  43
  44# Print a sequence to the terminal.
  45esc_p() {
  46    esc "$@"
  47    printf '%s' "$e"
  48}
  49
  50# This is just a simple wrapper around 'command -v' to avoid
  51# spamming '>/dev/null' throughout this function. This also guards
  52# against aliases and functions.
  53has() {
  54    _cmd=$(command -v "$1") 2>/dev/null || return 1
  55    [ -x "$_cmd" ] || return 1
  56}
  57
  58log() {
  59    # The 'log()' function handles the printing of information.
  60    # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info
  61    # happen independently of each other.
  62    #
  63    # The size of the ascii art is stored and the ascii is printed first.
  64    # Once the ascii is printed, the cursor is located right below the art
  65    # (See marker $[1]).
  66    #
  67    # Using the stored ascii size, the cursor is then moved to marker $[2].
  68    # This is simply a cursor up escape sequence using the "height" of the
  69    # ascii art.
  70    #
  71    # 'log()' then moves the cursor to the right the "width" of the ascii art
  72    # with an additional amount of padding to add a gap between the art and
  73    # the information (See marker $[3]).
  74    #
  75    # When 'log()' has executed, the cursor is then located at marker $[4].
  76    # When 'log()' is run a second time, the next line of information is
  77    # printed, moving the cursor to marker $[5].
  78    #
  79    # Markers $[4] and $[5] repeat all the way down through the ascii art
  80    # until there is no more information left to print.
  81    #
  82    # Every time 'log()' is called the script keeps track of how many lines
  83    # were printed. When printing is complete the cursor is then manually
  84    # placed below the information and the art according to the "heights"
  85    # of both.
  86    #
  87    # The math is simple: move cursor down $((ascii_height - info_height)).
  88    # If the aim is to move the cursor from marker $[5] to marker $[6],
  89    # plus the ascii height is 8 while the info height is 2 it'd be a move
  90    # of 6 lines downwards.
  91    #
  92    # However, if the information printed is "taller" (takes up more lines)
  93    # than the ascii art, the cursor isn't moved at all!
  94    #
  95    # Once the cursor is at marker $[6], the script exits. This is the gist
  96    # of how this "dynamic" printing and layout works.
  97    #
  98    # This method allows ascii art to be stored without markers for info
  99    # and it allows for easy swapping of info order and amount.
 100    #
 101    # $[2] ___      $[3] goldie@KISS
 102    # $[4](.· |     $[5] os KISS Linux
 103    #     (<> |
 104    #    / __  \
 105    #   ( /  \ /|
 106    #  _/\ __)/_)
 107    #  \/-____\/
 108    # $[1]
 109    #
 110    # $[6] /home/goldie $
 111
 112    # End here if no data was found.
 113    [ "$2" ] || return
 114
 115    # Store the values of '$1' and '$3' as we reset the argument list below.
 116    name=$1
 117    use_seperator=$3
 118
 119    # Use 'set --' as a means of stripping all leading and trailing
 120    # white-space from the info string. This also normalizes all
 121    # white-space inside of the string.
 122    #
 123    # Disable the shellcheck warning for word-splitting
 124    # as it's safe and intended ('set -f' disables globbing).
 125    # shellcheck disable=2046,2086
 126    {
 127        set -f
 128        set +f -- $2
 129        info=$*
 130    }
 131
 132    # Move the cursor to the right, the width of the ascii art with an
 133    # additional gap for text spacing.
 134    esc_p CUF "$ascii_width"
 135
 136    # Print the info name and color the text.
 137    esc_p SGR "3${PF_COL1-4}";
 138    esc_p SGR 1
 139    printf '%s' "$name"
 140    esc_p SGR 0
 141
 142    # Print the info name and info data separator, if applicable.
 143    [ "$use_seperator" ] || printf %s "$PF_SEP"
 144
 145    # Move the cursor backward the length of the *current* info name and
 146    # then move it forwards the length of the *longest* info name. This
 147    # aligns each info data line.
 148    esc_p CUB "${#name}"
 149    esc_p CUF "${PF_ALIGN:-$info_length}"
 150
 151    # Print the info data, color it and strip all leading whitespace
 152    # from the string.
 153    esc_p SGR "3${PF_COL2-9}"
 154    printf '%s' "$info"
 155    esc_p SGR 0
 156    printf '\n'
 157
 158    # Keep track of the number of times 'log()' has been run.
 159    info_height=$((${info_height:-0} + 1))
 160}
 161
 162get_title() {
 163    # Username is retrieved by first checking '$USER' with a fallback
 164    # to the 'id -un' command.
 165    user=${USER:-$(id -un)}
 166
 167    # Hostname is retrieved by first checking '$HOSTNAME' with a fallback
 168    # to the 'hostname' command.
 169    #
 170    # Disable the warning about '$HOSTNAME' being undefined in POSIX sh as
 171    # the intention for using it is allowing the user to overwrite the
 172    # value on invocation.
 173    # shellcheck disable=3028,2039
 174    hostname=${HOSTNAME:-${hostname:-$(hostname)}}
 175
 176    # If the hostname is still not found, fallback to the contents of the
 177    # /etc/hostname file.
 178    [ "$hostname" ] || read -r hostname < /etc/hostname
 179
 180    # Add escape sequences for coloring to user and host name. As we embed
 181    # them directly in the arguments passed to log(), we cannot use esc_p().
 182    esc SGR 1
 183    user=$e$user
 184    esc SGR "3${PF_COL3:-1}"
 185    user=$e$user
 186    esc SGR 1
 187    user=$user$e
 188    esc SGR 1
 189    hostname=$e$hostname
 190    esc SGR "3${PF_COL3:-1}"
 191    hostname=$e$hostname
 192
 193    log "${user}@${hostname}" " " " " >&6
 194}
 195
 196get_os() {
 197    # This function is called twice, once to detect the distribution name
 198    # for the purposes of picking an ascii art early and secondly to display
 199    # the distribution name in the info output (if enabled).
 200    #
 201    # On first run, this function displays _nothing_, only on the second
 202    # invocation is 'log()' called.
 203    [ "$distro" ] && {
 204        log os "$distro" >&6
 205        return
 206    }
 207    
 208    if [ -n "$1" ]; then
 209        distro="$1"
 210        return
 211    fi
 212
 213    case $os in
 214        (Linux*)
 215            # Some Linux distributions (which are based on others)
 216            # fail to identify as they **do not** change the upstream
 217            # distribution's identification packages or files.
 218            #
 219            # It is senseless to add a special case in the code for
 220            # each and every distribution (which _is_ technically no
 221            # different from what it is based on) as they're either too
 222            # lazy to modify upstream's identification files or they
 223            # don't have the know-how (or means) to ship their own
 224            # lsb-release package.
 225            #
 226            # This causes users to think there's a bug in system detection
 227            # tools like neofetch or pfetch when they technically *do*
 228            # function correctly.
 229            #
 230            # Exceptions are made for distributions which are independent,
 231            # not based on another distribution or follow different
 232            # standards.
 233            #
 234            # This applies only to distributions which follow the standard
 235            # by shipping unmodified identification files and packages
 236            # from their respective upstreams.
 237            if has lsb_release; then
 238                distro=$(lsb_release -sd)
 239
 240            # Android detection works by checking for the existence of
 241            # the follow two directories. I don't think there's a simpler
 242            # method than this.
 243            elif [ -d /system/app ] && [ -d /system/priv-app ]; then
 244                distro="Android $(getprop ro.build.version.release)"
 245
 246            elif [ -f /etc/os-release ]; then
 247                # This used to be a simple '. /etc/os-release' but I believe
 248                # this is insecure as we blindly executed whatever was in the
 249                # file. This parser instead simply handles 'key=val', treating
 250                # the file contents as plain-text.
 251                while IFS='=' read -r key val; do
 252                    case $key in
 253                        (PRETTY_NAME)
 254                            distro=$val
 255                        ;;
 256                    esac
 257                done < /etc/os-release
 258
 259            else
 260                # Special cases for (independent) distributions which
 261                # don't follow any os-release/lsb standards whatsoever.
 262                has crux && distro=$(crux)
 263                has guix && distro='Guix System'
 264            fi
 265
 266            # 'os-release' and 'lsb_release' sometimes add quotes
 267            # around the distribution name, strip them.
 268            distro=${distro##[\"\']}
 269            distro=${distro%%[\"\']}
 270
 271            # Check to see if we're running Bedrock Linux which is
 272            # very unique. This simply checks to see if the user's
 273            # PATH contains a Bedrock specific value.
 274            case $PATH in
 275                (*/bedrock/cross/*)
 276                    distro='Bedrock Linux'
 277                ;;
 278            esac
 279
 280            # Check to see if Linux is running in Windows 10 under
 281            # WSL1 (Windows subsystem for Linux [version 1]) and
 282            # append a string accordingly.
 283            #
 284            # If the kernel version string ends in "-Microsoft",
 285            # we're very likely running under Windows 10 in WSL1.
 286            if [ "$WSLENV" ]; then
 287                distro="${distro}${WSLENV+ on Windows 10 [WSL2]}"
 288
 289            # Check to see if Linux is running in Windows 10 under
 290            # WSL2 (Windows subsystem for Linux [version 2]) and
 291            # append a string accordingly.
 292            #
 293            # This checks to see if '$WSLENV' is defined. This
 294            # appends the Windows 10 string even if '$WSLENV' is
 295            # empty. We only need to check that is has been _exported_.
 296            elif [ -z "${kernel%%*-Microsoft}" ]; then
 297                distro="$distro on Windows 10 [WSL1]"
 298            fi
 299        ;;
 300
 301        (Darwin*)
 302            # Parse the SystemVersion.plist file to grab the macOS
 303            # version. The file is in the following format:
 304            #
 305            # <key>ProductVersion</key>
 306            # <string>10.14.6</string>
 307            #
 308            # 'IFS' is set to '<>' to enable splitting between the
 309            # keys and a second 'read' is used to operate on the
 310            # next line directly after a match.
 311            #
 312            # '_' is used to nullify a field. '_ _ line _' basically
 313            # says "populate $line with the third field's contents".
 314            while IFS='<>' read -r _ _ line _; do
 315                case $line in
 316                    # Match 'ProductVersion' and read the next line
 317                    # directly as it contains the key's value.
 318                    ProductVersion)
 319                        IFS='<>' read -r _ _ mac_version _
 320                        continue
 321                    ;;
 322
 323                    ProductName)
 324                        IFS='<>' read -r _ _ mac_product _
 325                        continue
 326                    ;;
 327                esac
 328            done < /System/Library/CoreServices/SystemVersion.plist
 329
 330            # Use the ProductVersion to determine which macOS/OS X codename
 331            # the system has. As far as I'm aware there's no "dynamic" way
 332            # of grabbing this information.
 333            case $mac_version in
 334                (10.4*)  distro='Mac OS X Tiger' ;;
 335                (10.5*)  distro='Mac OS X Leopard' ;;
 336                (10.6*)  distro='Mac OS X Snow Leopard' ;;
 337                (10.7*)  distro='Mac OS X Lion' ;;
 338                (10.8*)  distro='OS X Mountain Lion' ;;
 339                (10.9*)  distro='OS X Mavericks' ;;
 340                (10.10*) distro='OS X Yosemite' ;;
 341                (10.11*) distro='OS X El Capitan' ;;
 342                (10.12*) distro='macOS Sierra' ;;
 343                (10.13*) distro='macOS High Sierra' ;;
 344                (10.14*) distro='macOS Mojave' ;;
 345                (10.15*) distro='macOS Catalina' ;;
 346                (11*)    distro='macOS Big Sur' ;;
 347                (12*)    distro='macOS Monterey' ;;
 348                (*)      distro='macOS' ;;
 349            esac
 350
 351            # Use the ProductName to determine if we're running in iOS.
 352            case $mac_product in
 353                (iP*) distro='iOS' ;;
 354            esac
 355
 356            distro="$distro $mac_version"
 357        ;;
 358
 359        (Haiku)
 360            # Haiku uses 'uname -v' for version information
 361            # instead of 'uname -r' which only prints '1'.
 362            distro=$(uname -sv)
 363        ;;
 364
 365        (Minix|DragonFly)
 366            distro="$os $kernel"
 367
 368            # Minix and DragonFly don't support the escape
 369            # sequences used, clear the exit trap.
 370            trap '' EXIT
 371        ;;
 372
 373        (SunOS)
 374            # Grab the first line of the '/etc/release' file
 375            # discarding everything after '('.
 376            IFS='(' read -r distro _ < /etc/release
 377        ;;
 378
 379        (OpenBSD*)
 380            # Show the OpenBSD version type (current if present).
 381            # kern.version=OpenBSD 6.6-current (GENERIC.MP) ...
 382            IFS=' =' read -r _ distro openbsd_ver _ <<-EOF
 383				$(sysctl kern.version)
 384			EOF
 385
 386            distro="$distro $openbsd_ver"
 387        ;;
 388
 389        (FreeBSD)
 390            distro="$os $(freebsd-version)"
 391        ;;
 392
 393        (*)
 394            # Catch all to ensure '$distro' is never blank.
 395            # This also handles the BSDs.
 396            distro="$os $kernel"
 397        ;;
 398    esac
 399}
 400
 401get_kernel() {
 402    case $os in
 403        # Don't print kernel output on some systems as the
 404        # OS name includes it.
 405        (*BSD*|Haiku|Minix)
 406            return
 407        ;;
 408    esac
 409
 410    # '$kernel' is the cached output of 'uname -r'.
 411    log kernel "$kernel" >&6
 412}
 413
 414get_host() {
 415    case $os in
 416        (Linux*)
 417            # Despite what these files are called, version doesn't
 418            # always contain the version nor does name always contain
 419            # the name.
 420            read -r name    < /sys/devices/virtual/dmi/id/product_name
 421            read -r version < /sys/devices/virtual/dmi/id/product_version
 422            if [[ -f /sys/firmware/devicetree/base/model ]]; then
 423            	read -r model   < /sys/firmware/devicetree/base/model
 424            fi
 425
 426            host="$name $version $model"
 427        ;;
 428
 429        (Darwin* | FreeBSD* | DragonFly*)
 430            host=$(sysctl -n hw.model)
 431        ;;
 432
 433        (NetBSD*)
 434            host=$(sysctl -n machdep.dmi.system-vendor \
 435                             machdep.dmi.system-product)
 436        ;;
 437
 438        (OpenBSD*)
 439            host=$(sysctl -n hw.version)
 440        ;;
 441
 442        (*BSD* | Minix)
 443            host=$(sysctl -n hw.vendor hw.product)
 444        ;;
 445    esac
 446
 447    # Turn the host string into an argument list so we can iterate
 448    # over it and remove OEM strings and other information which
 449    # shouldn't be displayed.
 450    #
 451    # Disable the shellcheck warning for word-splitting
 452    # as it's safe and intended ('set -f' disables globbing).
 453    # shellcheck disable=2046,2086
 454    {
 455        set -f
 456        set +f -- $host
 457        host=
 458    }
 459
 460    # Iterate over the host string word by word as a means of stripping
 461    # unwanted and OEM information from the string as a whole.
 462    #
 463    # This could have been implemented using a long 'sed' command with
 464    # a list of word replacements, however I want to show that something
 465    # like this is possible in pure sh.
 466    #
 467    # This string reconstruction is needed as some OEMs either leave the
 468    # identification information as "To be filled by OEM", "Default",
 469    # "undefined" etc and we shouldn't print this to the screen.
 470    for word do
 471        # This works by reconstructing the string by excluding words
 472        # found in the "blacklist" below. Only non-matches are appended
 473        # to the final host string.
 474        case $word in
 475           (To      | [Bb]e      | [Ff]illed | [Bb]y  | O.E.M.  | OEM  |\
 476            Not     | Applicable | Specified | System | Product | Name |\
 477            Version | Undefined  | Default   | string | INVALID | �    | os |\
 478            Type1ProductConfigId )
 479                continue
 480            ;;
 481        esac
 482
 483        host="$host$word "
 484    done
 485
 486    # '$arch' is the cached output from 'uname -m'.
 487    log host "${host:-$arch}" >&6
 488}
 489
 490get_uptime() {
 491    # Uptime works by retrieving the data in total seconds and then
 492    # converting that data into days, hours and minutes using simple
 493    # math.
 494    case $os in
 495        (Linux* | Minix* | SerenityOS*)
 496            IFS=. read -r s _ < /proc/uptime
 497        ;;
 498
 499        (Darwin* | *BSD* | DragonFly*)
 500            s=$(sysctl -n kern.boottime)
 501
 502            # Extract the uptime in seconds from the following output:
 503            # [...] { sec = 1271934886, usec = 667779 } Thu Apr 22 12:14:46 2010
 504            s=${s#*=}
 505            s=${s%,*}
 506
 507            # The uptime format from 'sysctl' needs to be subtracted from
 508            # the current time in seconds.
 509            s=$(($(date +%s) - s))
 510        ;;
 511
 512        (Haiku)
 513            # The boot time is returned in microseconds, convert it to
 514            # regular seconds.
 515            s=$(($(system_time) / 1000000))
 516        ;;
 517
 518        (SunOS)
 519            # Split the output of 'kstat' on '.' and any white-space
 520            # which exists in the command output.
 521            #
 522            # The output is as follows:
 523            # unix:0:system_misc:snaptime	14809.906993005
 524            #
 525            # The parser extracts:          ^^^^^
 526            IFS='	.' read -r _ s _ <<-EOF
 527				$(kstat -p unix:0:system_misc:snaptime)
 528			EOF
 529        ;;
 530
 531        (IRIX)
 532            # Grab the uptime in a pretty format. Usually,
 533            # 00:00:00 from the 'ps' command.
 534            t=$(LC_ALL=POSIX ps -o etime= -p 1)
 535
 536            # Split the pretty output into days or hours
 537            # based on the uptime.
 538            case $t in
 539                (*-*)   d=${t%%-*} t=${t#*-} ;;
 540                (*:*:*) h=${t%%:*} t=${t#*:} ;;
 541            esac
 542
 543            h=${h#0} t=${t#0}
 544
 545            # Convert the split pretty fields back into
 546            # seconds so we may re-convert them to our format.
 547            s=$((${d:-0}*86400 + ${h:-0}*3600 + ${t%%:*}*60 + ${t#*:}))
 548        ;;
 549    esac
 550
 551    # Convert the uptime from seconds into days, hours and minutes.
 552    d=$((s / 60 / 60 / 24))
 553    h=$((s / 60 / 60 % 24))
 554    m=$((s / 60 % 60))
 555
 556    # Only append days, hours and minutes if they're non-zero.
 557    case "$d" in ([!0]*) uptime="${uptime}${d}d "; esac
 558    case "$h" in ([!0]*) uptime="${uptime}${h}h "; esac
 559    case "$m" in ([!0]*) uptime="${uptime}${m}m "; esac
 560
 561    log uptime "${uptime:-0m}" >&6
 562}
 563
 564get_pkgs() {
 565    # This works by first checking for which package managers are
 566    # installed and finally by printing each package manager's
 567    # package list with each package one per line.
 568    #
 569    # The output from this is then piped to 'wc -l' to count each
 570    # line, giving us the total package count of whatever package
 571    # managers are installed.
 572    packages=$(
 573        case $os in
 574            (Linux*)
 575                # Commands which print packages one per line.
 576                has bonsai     && bonsai list
 577                has crux       && pkginfo -i
 578                has pacman-key && pacman -Qq
 579                has dpkg       && dpkg-query -f '.\n' -W
 580                has rpm        && rpm -qa
 581                has xbps-query && xbps-query -l
 582                has apk        && apk info
 583                has guix       && guix package --list-installed
 584                has opkg       && opkg list-installed
 585
 586                # Directories containing packages.
 587                has kiss       && printf '%s\n' /var/db/kiss/installed/*/
 588                has cpt-list   && printf '%s\n' /var/db/cpt/installed/*/
 589                has brew       && printf '%s\n' "$(brew --cellar)/"*
 590                has emerge     && printf '%s\n' /var/db/pkg/*/*/
 591                has pkgtool    && printf '%s\n' /var/log/packages/*
 592                has eopkg      && printf '%s\n' /var/lib/eopkg/package/*
 593
 594                # 'nix' requires two commands.
 595                has nix-store  && {
 596                    nix-store -q --requisites /run/current-system/sw
 597                    nix-store -q --requisites ~/.nix-profile
 598                }
 599            ;;
 600
 601            (Darwin*)
 602                # Commands which print packages one per line.
 603                has pkgin      && pkgin list
 604                has dpkg       && dpkg-query -f '.\n' -W
 605
 606                # Directories containing packages.
 607                has brew       && printf '%s\n' /usr/local/Cellar/*
 608
 609                # 'port' prints a single line of output to 'stdout'
 610                # when no packages are installed and exits with
 611                # success causing a false-positive of 1 package
 612                # installed.
 613                #
 614                # 'port' should really exit with a non-zero code
 615                # in this case to allow scripts to cleanly handle
 616                # this behavior.
 617                has port       && {
 618                    pkg_list=$(port installed)
 619
 620                    case "$pkg_list" in
 621                        ("No ports are installed.")
 622                            # do nothing
 623                        ;;
 624
 625                        (*)
 626                            printf '%s\n' "$pkg_list"
 627                        ;;
 628                    esac
 629                }
 630            ;;
 631
 632            (FreeBSD*|DragonFly*)
 633                pkg info
 634            ;;
 635
 636            (OpenBSD*)
 637                printf '%s\n' /var/db/pkg/*/
 638            ;;
 639
 640            (NetBSD*)
 641                pkg_info
 642            ;;
 643
 644            (Haiku)
 645                printf '%s\n' /boot/system/package-links/*
 646            ;;
 647
 648            (Minix)
 649                printf '%s\n' /usr/pkg/var/db/pkg/*/
 650            ;;
 651
 652            (SunOS)
 653                has pkginfo && pkginfo -i
 654                has pkg     && pkg list
 655            ;;
 656
 657            (IRIX)
 658                versions -b
 659            ;;
 660
 661            (SerenityOS)
 662                while IFS=" " read -r type _; do
 663                    [ "$type" != dependency ] &&
 664                        printf "\n"
 665                done < /usr/Ports/packages.db
 666            ;;
 667        esac | wc -l
 668    )
 669
 670    # 'wc -l' can have leading and/or trailing whitespace
 671    # depending on the implementation, so strip them.
 672    # Procedure explained at https://github.com/dylanaraps/pure-sh-bible
 673    # (trim-leading-and-trailing-white-space-from-string)
 674    packages=${packages#"${packages%%[![:space:]]*}"}
 675    packages=${packages%"${packages##*[![:space:]]}"}
 676
 677    case $os in
 678        # IRIX's package manager adds 3 lines of extra
 679        # output which we must account for here.
 680        (IRIX)
 681            packages=$((packages - 3))
 682        ;;
 683
 684        # OpenBSD's wc prints whitespace before the output
 685        # which needs to be stripped.
 686        (OpenBSD)
 687            packages=$((packages))
 688        ;;
 689    esac
 690
 691    case $packages in
 692        (1?*|[2-9]*)
 693            log pkgs "$packages" >&6
 694        ;;
 695    esac
 696}
 697
 698get_memory() {
 699    case $os in
 700        # Used memory is calculated using the following "formula":
 701        # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable
 702        # Source: https://github.com/KittyKatt/screenFetch/issues/386
 703        (Linux*)
 704            # Parse the '/proc/meminfo' file splitting on ':' and 'k'.
 705            # The format of the file is 'key:   000kB' and an additional
 706            # split is used on 'k' to filter out 'kB'.
 707            while IFS=':k '  read -r key val _; do
 708                case $key in
 709                    (MemTotal)
 710                        mem_used=$((mem_used + val))
 711                        mem_full=$val
 712                    ;;
 713
 714                    (Shmem)
 715                        mem_used=$((mem_used + val))
 716                    ;;
 717
 718                    (MemFree | Buffers | Cached | SReclaimable)
 719                        mem_used=$((mem_used - val))
 720                    ;;
 721
 722                    # If detected this will be used over the above calculation
 723                    # for mem_used. Available since Linux 3.14rc.
 724                    # See kernel commit 34e431b0ae398fc54ea69ff85ec700722c9da773
 725                    (MemAvailable)
 726                        mem_avail=$val
 727                    ;;
 728                esac
 729            done < /proc/meminfo
 730
 731            case $mem_avail in
 732                (*[0-9]*)
 733                    mem_used=$(((mem_full - mem_avail) / 1024))
 734                ;;
 735
 736                *)
 737                    mem_used=$((mem_used / 1024))
 738                ;;
 739            esac
 740
 741            mem_full=$((mem_full / 1024))
 742        ;;
 743
 744        # Used memory is calculated using the following "formula":
 745        # (wired + active + occupied) * 4 / 1024
 746        (Darwin*)
 747            mem_full=$(($(sysctl -n hw.memsize) / 1024 / 1024))
 748
 749            # Parse the 'vmstat' file splitting on ':' and '.'.
 750            # The format of the file is 'key:   000.' and an additional
 751            # split is used on '.' to filter it out.
 752            while IFS=:. read -r key val; do
 753                case $key in
 754                    (*' wired'*|*' active'*|*' occupied'*)
 755                        mem_used=$((mem_used + ${val:-0}))
 756                    ;;
 757                esac
 758
 759            # Using '<<-EOF' is the only way to loop over a command's
 760            # output without the use of a pipe ('|').
 761            # This ensures that any variables defined in the while loop
 762            # are still accessible in the script.
 763            done <<-EOF
 764                $(vm_stat)
 765			EOF
 766
 767            mem_used=$((mem_used * 4 / 1024))
 768        ;;
 769
 770        (OpenBSD*)
 771            mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))
 772
 773            # This is a really simpler parser for 'vmstat' which grabs
 774            # the used memory amount in a lazy way. 'vmstat' prints 3
 775            # lines of output with the needed value being stored in the
 776            # final line.
 777            #
 778            # This loop simply grabs the 3rd element of each line until
 779            # the EOF is reached. Each line overwrites the value of the
 780            # previous one so we're left with what we wanted. This isn't
 781            # slow as only 3 lines are parsed.
 782            while read -r _ _ line _; do
 783                mem_used=${line%%M}
 784
 785            # Using '<<-EOF' is the only way to loop over a command's
 786            # output without the use of a pipe ('|').
 787            # This ensures that any variables defined in the while loop
 788            # are still accessible in the script.
 789            done <<-EOF
 790                $(vmstat)
 791			EOF
 792        ;;
 793
 794        # Used memory is calculated using the following "formula":
 795        # mem_full - ((inactive + free + cache) * page_size / 1024)
 796        (FreeBSD*|DragonFly*)
 797            mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))
 798
 799            # Use 'set --' to store the output of the command in the
 800            # argument list. POSIX sh has no arrays but this is close enough.
 801            #
 802            # Disable the shellcheck warning for word-splitting
 803            # as it's safe and intended ('set -f' disables globbing).
 804            # shellcheck disable=2046
 805            {
 806                set -f
 807                set +f -- $(sysctl -n hw.pagesize \
 808                                      vm.stats.vm.v_inactive_count \
 809                                      vm.stats.vm.v_free_count \
 810                                      vm.stats.vm.v_cache_count)
 811            }
 812
 813            # Calculate the amount of used memory.
 814            # $1: hw.pagesize
 815            # $2: vm.stats.vm.v_inactive_count
 816            # $3: vm.stats.vm.v_free_count
 817            # $4: vm.stats.vm.v_cache_count
 818            mem_used=$((mem_full - (($2 + $3 + $4) * $1 / 1024 / 1024)))
 819        ;;
 820
 821        (NetBSD*)
 822            mem_full=$(($(sysctl -n hw.physmem64) / 1024 / 1024))
 823
 824            # NetBSD implements a lot of the Linux '/proc' filesystem,
 825            # this uses the same parser as the Linux memory detection.
 826            while IFS=':k ' read -r key val _; do
 827                case $key in
 828                    (MemFree)
 829                        mem_free=$((val / 1024))
 830                        break
 831                    ;;
 832                esac
 833            done < /proc/meminfo
 834
 835            mem_used=$((mem_full - mem_free))
 836        ;;
 837
 838        (Haiku)
 839            # Read the first line of 'sysinfo -mem' splitting on
 840            # '(', ' ', and ')'. The needed information is then
 841            # stored in the 5th and 7th elements. Using '_' "consumes"
 842            # an element allowing us to proceed to the next one.
 843            #
 844            # The parsed format is as follows:
 845            # 3501142016 bytes free      (used/max  792645632 / 4293787648)
 846            IFS='( )' read -r _ _ _ _ mem_used _ mem_full <<-EOF
 847                $(sysinfo -mem)
 848			EOF
 849
 850            mem_used=$((mem_used / 1024 / 1024))
 851            mem_full=$((mem_full / 1024 / 1024))
 852        ;;
 853
 854        (Minix)
 855            # Minix includes the '/proc' filesystem though the format
 856            # differs from Linux. The '/proc/meminfo' file is only a
 857            # single line with space separated elements and elements
 858            # 2 and 3 contain the total and free memory numbers.
 859            read -r _ mem_full mem_free _ < /proc/meminfo
 860
 861            mem_used=$(((mem_full - mem_free) / 1024))
 862            mem_full=$(( mem_full / 1024))
 863        ;;
 864
 865        (SunOS)
 866            hw_pagesize=$(pagesize)
 867
 868            # 'kstat' outputs memory in the following format:
 869            # unix:0:system_pages:pagestotal	1046397
 870            # unix:0:system_pages:pagesfree		885018
 871            #
 872            # This simply uses the first "element" (white-space
 873            # separated) as the key and the second element as the
 874            # value.
 875            #
 876            # A variable is then assigned based on the key.
 877            while read -r key val; do
 878                case $key in
 879                    (*total)
 880                        pages_full=$val
 881                    ;;
 882
 883                    (*free)
 884                        pages_free=$val
 885                    ;;
 886                esac
 887            done <<-EOF
 888				$(kstat -p unix:0:system_pages:pagestotal \
 889                           unix:0:system_pages:pagesfree)
 890			EOF
 891
 892            mem_full=$((pages_full * hw_pagesize / 1024 / 1024))
 893            mem_free=$((pages_free * hw_pagesize / 1024 / 1024))
 894            mem_used=$((mem_full - mem_free))
 895        ;;
 896
 897        (IRIX)
 898            # Read the memory information from the 'top' command. Parse
 899            # and split each line until we reach the line starting with
 900            # "Memory".
 901            #
 902            # Example output: Memory: 160M max, 147M avail, .....
 903            while IFS=' :' read -r label mem_full _ mem_free _; do
 904                case $label in
 905                    (Memory)
 906                        mem_full=${mem_full%M}
 907                        mem_free=${mem_free%M}
 908                        break
 909                    ;;
 910                esac
 911            done <<-EOF
 912                $(top -n)
 913			EOF
 914
 915            mem_used=$((mem_full - mem_free))
 916        ;;
 917
 918        (SerenityOS)
 919            IFS='{}' read -r _ memstat _ < /proc/memstat
 920
 921            set -f -- "$IFS"
 922            IFS=,
 923
 924            for pair in $memstat; do
 925                case $pair in
 926                    (*user_physical_allocated*)
 927                        mem_used=${pair##*:}
 928                    ;;
 929
 930                    (*user_physical_available*)
 931                        mem_free=${pair##*:}
 932                    ;;
 933                esac
 934            done
 935
 936            IFS=$1
 937            set +f --
 938
 939            mem_used=$((mem_used * 4096 / 1024 / 1024))
 940            mem_free=$((mem_free * 4096 / 1024 / 1024))
 941
 942            mem_full=$((mem_used + mem_free))
 943        ;;
 944    esac
 945
 946    log memory "${mem_used:-?}M / ${mem_full:-?}M" >&6
 947}
 948
 949get_wm() {
 950    case $os in
 951        (Darwin*)
 952            # Don't display window manager on macOS.
 953        ;;
 954
 955        (*)
 956            # xprop can be used to grab the window manager's properties
 957            # which contains the window manager's name under '_NET_WM_NAME'.
 958            #
 959            # The upside to using 'xprop' is that you don't need to hardcode
 960            # a list of known window manager names. The downside is that
 961            # not all window managers conform to setting the '_NET_WM_NAME'
 962            # atom..
 963            #
 964            # List of window managers which fail to set the name atom:
 965            # catwm, fvwm, dwm, 2bwm, monster, wmaker and sowm [mine! ;)].
 966            #
 967            # The final downside to this approach is that it does _not_
 968            # support Wayland environments. The only solution which supports
 969            # Wayland is the 'ps' parsing mentioned below.
 970            #
 971            # A more naive implementation is to parse the last line of
 972            # '~/.xinitrc' to extract the second white-space separated
 973            # element.
 974            #
 975            # The issue with an approach like this is that this line data
 976            # does not always equate to the name of the window manager and
 977            # could in theory be _anything_.
 978            #
 979            # This also fails when the user launches xorg through a display
 980            # manager or other means.
 981            #
 982            #
 983            # Another naive solution is to parse 'ps' with a hardcoded list
 984            # of window managers to detect the current window manager (based
 985            # on what is running).
 986            #
 987            # The issue with this approach is the need to hardcode and
 988            # maintain a list of known window managers.
 989            #
 990            # Another issue is that process names do not always equate to
 991            # the name of the window manager. False-positives can happen too.
 992            #
 993            # This is the only solution which supports Wayland based
 994            # environments sadly. It'd be nice if some kind of standard were
 995            # established to identify Wayland environments.
 996            #
 997            # pfetch's goal is to remain _simple_, if you'd like a "full"
 998            # implementation of window manager detection use 'neofetch'.
 999            #
1000            # Neofetch use a combination of 'xprop' and 'ps' parsing to
1001            # support all window managers (including non-conforming and
1002            # Wayland) though it's a lot more complicated!
1003
1004            # Don't display window manager if X isn't running.
1005            [ "$DISPLAY" ] || return
1006
1007            # This is a two pass call to xprop. One call to get the window
1008            # manager's ID and another to print its properties.
1009            has xprop && {
1010                # The output of the ID command is as follows:
1011                # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
1012                #
1013                # To extract the ID, everything before the last space
1014                # is removed.
1015                id=$(xprop -root -notype _NET_SUPPORTING_WM_CHECK)
1016                id=${id##* }
1017
1018                # The output of the property command is as follows:
1019                # _NAME 8t
1020                # _NET_WM_PID = 252
1021                # _NET_WM_NAME = "bspwm"
1022                # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
1023                # WM_CLASS = "wm", "Bspwm"
1024                #
1025                # To extract the name, everything before '_NET_WM_NAME = \"'
1026                # is removed and everything after the next '"' is removed.
1027                wm=$(xprop -id "$id" -notype -len 25 -f _NET_WM_NAME 8t)
1028            }
1029
1030            # Handle cases of a window manager _not_ populating the
1031            # '_NET_WM_NAME' atom. Display nothing in this case.
1032            case $wm in
1033                (*'_NET_WM_NAME = '*)
1034                    wm=${wm##*_NET_WM_NAME = \"}
1035                    wm=${wm%%\"*}
1036                ;;
1037
1038                (*)
1039                    # Fallback to checking the process list
1040                    # for the select few window managers which
1041                    # don't set '_NET_WM_NAME'.
1042                    while read -r ps_line; do
1043                        case $ps_line in
1044                            (*catwm*)     wm=catwm ;;
1045                            (*fvwm*)      wm=fvwm ;;
1046                            (*dwm*)       wm=dwm ;;
1047                            (*2bwm*)      wm=2bwm ;;
1048                            (*monsterwm*) wm=monsterwm ;;
1049                            (*wmaker*)    wm='Window Maker' ;;
1050                            (*sowm*)      wm=sowm ;;
1051							(*penrose*)   wm=penrose ;;
1052                        esac
1053                    done <<-EOF
1054                        $(ps x)
1055					EOF
1056                ;;
1057            esac
1058        ;;
1059    esac
1060
1061    log wm "$wm" >&6
1062}
1063
1064
1065get_de() {
1066    # This only supports Xorg related desktop environments though
1067    # this is fine as knowing the desktop environment on Windows,
1068    # macOS etc is useless (they'll always report the same value).
1069    #
1070    # Display the value of '$XDG_CURRENT_DESKTOP', if it's empty,
1071    # display the value of '$DESKTOP_SESSION'.
1072    log de "${XDG_CURRENT_DESKTOP:-$DESKTOP_SESSION}" >&6
1073}
1074
1075get_shell() {
1076    # Display the basename of the '$SHELL' environment variable.
1077    log shell "${SHELL##*/}" >&6
1078}
1079
1080get_editor() {
1081    # Display the value of '$VISUAL', if it's empty, display the
1082    # value of '$EDITOR'.
1083    editor=${VISUAL:-"$EDITOR"}
1084
1085    log editor "${editor##*/}" >&6
1086}
1087
1088get_palette() {
1089    # Print the first 8 terminal colors. This uses the existing
1090    # sequences to change text color with a sequence prepended
1091    # to reverse the foreground and background colors.
1092    #
1093    # This allows us to save hardcoding a second set of sequences
1094    # for background colors.
1095    #
1096    # False positive.
1097    # shellcheck disable=2154
1098    {
1099        esc SGR 7
1100        palette="$e$c1 $c1 $c2 $c2 $c3 $c3 $c4 $c4 $c5 $c5 $c6 $c6 "
1101        esc SGR 0
1102        palette="$palette$e"
1103    }
1104
1105    # Print the palette with a new-line before and afterwards but no seperator.
1106    printf '\n' >&6
1107    log "$palette
1108        " " " " " >&6
1109}
1110
1111get_ascii() {
1112    # This is a simple function to read the contents of
1113    # an ascii file from 'stdin'. It allows for the use
1114    # of '<<-EOF' to prevent the break in indentation in
1115    # this source code.
1116    #
1117    # This function also sets the text colors according
1118    # to the ascii color.
1119    read_ascii() {
1120        # 'PF_COL1': Set the info name color according to ascii color.
1121        # 'PF_COL3': Set the title color to some other color. ¯\_(ツ)_/¯
1122        PF_COL1=${PF_COL1:-${1:-7}}
1123        PF_COL3=${PF_COL3:-$((${1:-7}%8+1))}
1124
1125        # POSIX sh has no 'var+=' so 'var=${var}append' is used. What's
1126        # interesting is that 'var+=' _is_ supported inside '$(())'
1127        # (arithmetic) though there's no support for 'var++/var--'.
1128        #
1129        # There is also no $'\n' to add a "literal"(?) newline to the
1130        # string. The simplest workaround being to break the line inside
1131        # the string (though this has the caveat of breaking indentation).
1132        while IFS= read -r line; do
1133            ascii="$ascii$line
1134"
1135        done
1136    }
1137
1138    # This checks for ascii art in the following order:
1139    # '$1':        Argument given to 'get_ascii()' directly.
1140    # '$PF_ASCII': Environment variable set by user.
1141    # '$distro':   The detected distribution name.
1142    # '$os':       The name of the operating system/kernel.
1143    #
1144    # NOTE: Each ascii art below is indented using tabs, this
1145    #       allows indentation to continue naturally despite
1146    #       the use of '<<-EOF'.
1147    #
1148    # False positive.
1149    # shellcheck disable=2154
1150    case ${1:-${PF_ASCII:-${distro:-$os}}} in
1151        ([Aa]lpine*)
1152            read_ascii 4 <<-EOF
1153				${c4}   /\\ /\\
1154				  /${c7}/ ${c4}\\  \\
1155				 /${c7}/   ${c4}\\  \\
1156				/${c7}//    ${c4}\\  \\
1157				${c7}//      ${c4}\\  \\
1158				         ${c4}\\
1159			EOF
1160        ;;
1161
1162        ([Aa]ndroid*)
1163            read_ascii 2 <<-EOF
1164				${c2}  ;,           ,;
1165				${c2}   ';,.-----.,;'
1166				${c2}  ,'           ',
1167				${c2} /    O     O    \\
1168				${c2}|                 |
1169				${c2}'-----------------'
1170			EOF
1171        ;;
1172
1173        ([Aa]rch*)
1174            read_ascii 4 <<-EOF
1175				${c6}       /\\
1176				${c6}      /  \\
1177				${c6}     /\\   \\
1178				${c4}    /      \\
1179				${c4}   /   ,,   \\
1180				${c4}  /   |  |  -\\
1181				${c4} /_-''    ''-_\\
1182			EOF
1183        ;;
1184
1185        ([Aa]rco*)
1186            read_ascii 4 <<-EOF
1187				${c4}      /\\
1188				${c4}     /  \\
1189				${c4}    / /\\ \\
1190				${c4}   / /  \\ \\
1191				${c4}  / /    \\ \\
1192				${c4} / / _____\\ \\
1193				${c4}/_/  \`----.\\_\\
1194			EOF
1195        ;;
1196
1197        ([Aa]rtix*)
1198            read_ascii 6 <<-EOF
1199				${c4}      /\\
1200				${c4}     /  \\
1201				${c4}    /\`'.,\\
1202				${c4}   /     ',
1203				${c4}  /      ,\`\\
1204				${c4} /   ,.'\`.  \\
1205				${c4}/.,'\`     \`'.\\
1206			EOF
1207        ;;
1208
1209        ([Bb]edrock*)
1210            read_ascii 4 <<-EOF
1211				${c7}__
1212				${c7}\\ \\___
1213				${c7} \\  _ \\
1214				${c7}  \\___/
1215			EOF
1216        ;;
1217
1218        ([Bb]uildroot*)
1219            read_ascii 3 <<-EOF
1220				${c3}   ___
1221				${c3} / \`   \\
1222				${c3}|   :  :|
1223				${c3}-. _:__.-
1224				${c3}  \` ---- \`
1225			EOF
1226        ;;
1227
1228        ([Cc]el[Oo][Ss]*)
1229            read_ascii 5 0 <<-EOF
1230				${c5}      .////\\\\\//\\.
1231				${c5}     //_         \\\\
1232				${c5}    /_  ${c7}##############
1233				${c5}   //              *\\
1234				${c7}###############    ${c5}|#
1235				${c5}   \/              */
1236				${c5}    \*   ${c7}##############
1237				${c5}     */,        .//
1238				${c5}      '_///\\\\\//_'
1239			EOF
1240        ;;
1241
1242        ([Cc]ent[Oo][Ss]*)
1243            read_ascii 5 <<-EOF
1244				${c2} ____${c3}^${c5}____
1245				${c2} |\\  ${c3}|${c5}  /|
1246				${c2} | \\ ${c3}|${c5} / |
1247				${c5}<---- ${c4}---->
1248				${c4} | / ${c2}|${c3} \\ |
1249				${c4} |/__${c2}|${c3}__\\|
1250				${c2}     v
1251			EOF
1252        ;;
1253
1254        ([Cc]rystal*[Ll]inux)
1255            read_ascii 5 5 <<-EOF
1256				${c5}        -//.     
1257				${c5}      -//.       
1258				${c5}    -//. .       
1259				${c5}  -//.  '//-     
1260				${c5} /+:      :+/    
1261				${c5}  .//'  .//.     
1262				${c5}    . .//.       
1263				${c5}    .//.         
1264				${c5}  .//.           
1265			EOF
1266        ;;
1267
1268        ([Dd]ahlia*)
1269            read_ascii 1 <<-EOF
1270				${c1}      _
1271				${c1}  ___/ \\___
1272				${c1} |   _-_   |
1273				${c1} | /     \ |
1274				${c1}/ |       | \\
1275				${c1}\\ |       | /
1276				${c1} | \ _ _ / |
1277				${c1} |___ - ___|
1278				${c1}     \\_/
1279			EOF
1280        ;;
1281
1282        ([Dd]ebian*)
1283            read_ascii 1 <<-EOF
1284				${c1}  _____
1285				${c1} /  __ \\
1286				${c1}|  /    |
1287				${c1}|  \\___-
1288				${c1}-_
1289				${c1}  --_
1290			EOF
1291        ;;
1292
1293		([Dd]evuan*)
1294			read_ascii 6 <<-EOF
1295				${c4} ..:::.      
1296				${c4}    ..-==-   
1297				${c4}        .+#: 
1298				${c4}         =@@ 
1299				${c4}      :+%@#: 
1300				${c4}.:=+#@@%*:   
1301				${c4}#@@@#=:      
1302			EOF
1303		;;
1304
1305        ([Dd]ragon[Ff]ly*)
1306            read_ascii 1 <<-EOF
1307				    ,${c1}_${c7},
1308				 ('-_${c1}|${c7}_-')
1309				  >--${c1}|${c7}--<
1310				 (_-'${c1}|${c7}'-_)
1311				     ${c1}|
1312				     ${c1}|
1313				     ${c1}|
1314			EOF
1315        ;;
1316
1317        ([Ee]lementary*)
1318            read_ascii <<-EOF
1319				${c7}  _______
1320				${c7} / ____  \\
1321				${c7}/  |  /  /\\
1322				${c7}|__\\ /  / |
1323				${c7}\\   /__/  /
1324				 ${c7}\\_______/
1325			EOF
1326        ;;
1327
1328        ([Ee]ndeavour*)
1329            read_ascii 4 <<-EOF
1330				      ${c1}/${c4}\\
1331				    ${c1}/${c4}/  \\${c6}\\
1332				   ${c1}/${c4}/    \\ ${c6}\\
1333				 ${c1}/ ${c4}/     _) ${c6})
1334				${c1}/_${c4}/___-- ${c6}__-
1335				 ${c6}/____--
1336			EOF
1337        ;;
1338
1339        ([Ff]edora*)
1340            read_ascii 4 <<-EOF
1341				        ${c4},'''''.
1342				       ${c4}|   ,.  |
1343				       ${c4}|  |  '_'
1344				${c4}  ,....|  |..
1345				${c4}.'  ,_;|   ..'
1346				${c4}|  |   |  |
1347				${c4}|  ',_,'  |
1348				${c4} '.     ,'
1349				   ${c4}'''''
1350			EOF
1351        ;;
1352
1353        ([Ff]ree[Bb][Ss][Dd]*)
1354            read_ascii 1 <<-EOF
1355				${c1}/\\,-'''''-,/\\
1356				${c1}\\_)       (_/
1357				${c1}|           |
1358				${c1}|           |
1359				 ${c1};         ;
1360				  ${c1}'-_____-'
1361			EOF
1362        ;;
1363
1364        ([Gg]aruda*)
1365            read_ascii 4 <<-EOF
1366				${c3}         _______
1367				${c3}      __/       \\_
1368				${c3}    _/     /      \\_
1369				${c7}  _/      /_________\\
1370				${c7}_/                  |
1371				${c2}\\     ____________
1372				${c2} \\_            __/
1373				${c2}   \\__________/
1374			EOF
1375        ;;
1376
1377        ([Gg]entoo*)
1378            read_ascii 5 <<-EOF
1379				${c5} _-----_
1380				${c5}(       \\
1381				${c5}\\    0   \\
1382				${c7} \\        )
1383				${c7} /      _/
1384				${c7}(     _-
1385				${c7}\\____-
1386			EOF
1387        ;;
1388
1389        ([Gg][Nn][Uu]*)
1390            read_ascii 3 <<-EOF
1391				${c2}    _-\`\`-,   ,-\`\`-_
1392				${c2}  .'  _-_|   |_-_  '.
1393				${c2}./    /_._   _._\\    \\.
1394				${c2}:    _/_._\`:'_._\\_    :
1395				${c2}\\:._/  ,\`   \\   \\ \\_.:/
1396				${c2}   ,-';'.@)  \\ @) \\
1397				${c2}   ,'/'  ..- .\\,-.|
1398				${c2}   /'/' \\(( \\\` ./ )
1399				${c2}    '/''  \\_,----'
1400				${c2}      '/''   ,;/''
1401				${c2}         \`\`;'
1402			EOF
1403        ;;
1404
1405        ([Gg]uix[Ss][Dd]*|[Gg]uix*)
1406            read_ascii 3 <<-EOF
1407				${c3}|.__          __.|
1408				${c3}|__ \\        / __|
1409				   ${c3}\\ \\      / /
1410				    ${c3}\\ \\    / /
1411				     ${c3}\\ \\  / /
1412				      ${c3}\\ \\/ /
1413				       ${c3}\\__/
1414			EOF
1415        ;;
1416
1417        ([Hh]aiku*)
1418            read_ascii 3 <<-EOF
1419				${c3}       ,^,
1420				 ${c3}     /   \\
1421				${c3}*--_ ;     ; _--*
1422				${c3}\\   '"     "'   /
1423				 ${c3}'.           .'
1424				${c3}.-'"         "'-.
1425				 ${c3}'-.__.   .__.-'
1426				       ${c3}|_|
1427			EOF
1428        ;;
1429
1430        ([Hh]ydroOS*)
1431			read_ascii 4 <<-EOF
1432				${c1}╔╗╔╗──╔╗───╔═╦══╗
1433				${c1}║╚╝╠╦╦╝╠╦╦═╣║║══╣
1434				${c1}║╔╗║║║╬║╔╣╬║║╠══║
1435				${c1}╚╝╚╬╗╠═╩╝╚═╩═╩══╝
1436				${c1}───╚═╝
1437			EOF
1438        ;;
1439
1440        ([Hh]yperbola*)
1441            read_ascii <<-EOF
1442				${c7}    |\`__.\`/
1443				   ${c7} \____/
1444				   ${c7} .--.
1445				  ${c7} /    \\
1446				 ${c7} /  ___ \\
1447				 ${c7}/ .\`   \`.\\
1448				${c7}/.\`      \`.\\
1449			EOF
1450        ;;
1451
1452        ([Ii]glunix*)
1453            read_ascii <<-EOF
1454				${c0}       |
1455				${c0}       |          |
1456				${c0}                  |
1457				${c0}  |    ________
1458				${c0}  |  /\\   |    \\
1459				${c0}    /  \\  |     \\  |
1460				${c0}   /    \\        \\ |
1461				${c0}  /      \\________\\
1462				${c0}  \\      /        /
1463				${c0}   \\    /        /
1464				${c0}    \\  /        /
1465				${c0}     \\/________/
1466			EOF
1467        ;;
1468
1469        ([Ii]nstant[Oo][Ss]*)
1470            read_ascii <<-EOF
1471				${c0} ,-''-,
1472				${c0}: .''. :
1473				${c0}: ',,' :
1474				${c0} '-____:__
1475				${c0}       :  \`.
1476				${c0}       \`._.'
1477			EOF
1478        ;;
1479
1480        ([Ii][Rr][Ii][Xx]*)
1481            read_ascii 1 <<-EOF
1482				${c1} __
1483				${c1} \\ \\   __
1484				${c1}  \\ \\ / /
1485				${c1}   \\ v /
1486				${c1}   / . \\
1487				${c1}  /_/ \\ \\
1488				${c1}       \\_\\
1489			EOF
1490        ;;
1491
1492        ([Kk][Dd][Ee]*[Nn]eon*)
1493            read_ascii 6 <<-EOF
1494				${c7}   .${c6}__${c7}.${c6}__${c7}.
1495				${c6}  /  _${c7}.${c6}_  \\
1496				${c6} /  /   \\  \\
1497				${c7} . ${c6}|  ${c7}O${c6}  | ${c7}.
1498				${c6} \\  \\_${c7}.${c6}_/  /
1499				${c6}  \\${c7}.${c6}__${c7}.${c6}__${c7}.${c6}/
1500			EOF
1501        ;;
1502
1503        ([Ll]inux*[Ll]ite*|[Ll]ite*)
1504            read_ascii 3 <<-EOF
1505				${c3}   /\\
1506				${c3}  /  \\
1507				${c3} / ${c7}/ ${c3}/
1508			${c3}> ${c7}/ ${c3}/
1509				${c3}\\ ${c7}\\ ${c3}\\
1510				 ${c3}\\_${c7}\\${c3}_\\
1511				${c7}    \\
1512			EOF
1513        ;;
1514
1515        ([Ll]inux*[Mm]int*|[Mm]int)
1516            read_ascii 2 <<-EOF
1517				${c2} ___________
1518				${c2}|_          \\
1519				  ${c2}| ${c7}| _____ ${c2}|
1520				  ${c2}| ${c7}| | | | ${c2}|
1521				  ${c2}| ${c7}| | | | ${c2}|
1522				  ${c2}| ${c7}\\__${c7}___/ ${c2}|
1523				  ${c2}\\_________/
1524			EOF
1525        ;;
1526
1527
1528        ([Ll]inux*)
1529            read_ascii 4 <<-EOF
1530				${c4}    ___
1531				   ${c4}(${c7}.. ${c4}|
1532				   ${c4}(${c5}<> ${c4}|
1533				  ${c4}/ ${c7}__  ${c4}\\
1534				 ${c4}( ${c7}/  \\ ${c4}/|
1535				${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4})
1536				${c5}\/${c4}-____${c5}\/
1537			EOF
1538        ;;
1539
1540        ([Mm]ac[Oo][Ss]*|[Dd]arwin*)
1541            read_ascii 1 <<-EOF
1542				${c2}       .:'
1543				${c2}    _ :'_
1544				${c3} .'\`_\`-'_\`\`.
1545				${c1}:________.-'
1546				${c1}:_______:
1547				${c4} :_______\`-;
1548				${c5}  \`._.-._.'
1549			EOF
1550        ;;
1551
1552        ([Mm]ageia*)
1553            read_ascii 2 <<-EOF
1554				${c6}   *
1555				${c6}    *
1556				${c6}   **
1557				${c7} /\\__/\\
1558				${c7}/      \\
1559				${c7}\\      /
1560				${c7} \\____/
1561			EOF
1562        ;;
1563
1564        ([Mm]anjaro*)
1565            read_ascii 2 <<-EOF
1566				${c2}||||||||| ||||
1567				${c2}||||||||| ||||
1568				${c2}||||      ||||
1569				${c2}|||| |||| ||||
1570				${c2}|||| |||| ||||
1571				${c2}|||| |||| ||||
1572				${c2}|||| |||| ||||
1573			EOF
1574        ;;
1575
1576        ([Mm]inix*)
1577            read_ascii 4 <<-EOF
1578				${c4} ,,        ,,
1579				${c4};${c7},${c4} ',    ,' ${c7},${c4};
1580				${c4}; ${c7}',${c4} ',,' ${c7},'${c4} ;
1581				${c4};   ${c7}',${c4}  ${c7},'${c4}   ;
1582				${c4};  ${c7};, '' ,;${c4}  ;
1583				${c4};  ${c7};${c4};${c7}',,'${c4};${c7};${c4}  ;
1584				${c4}', ${c7};${c4};;  ;;${c7};${c4} ,'
1585				 ${c4} '${c7};${c4}'    '${c7};${c4}'
1586			EOF
1587        ;;
1588
1589        ([Mm][Xx]*)
1590            read_ascii <<-EOF
1591				${c7}    \\\\  /
1592				 ${c7}    \\\\/
1593				 ${c7}     \\\\
1594				 ${c7}  /\\/ \\\\
1595				${c7}  /  \\  /\\
1596				${c7} /    \\/  \\
1597			${c7}/__________\\
1598			EOF
1599        ;;
1600
1601        ([Nn]et[Bb][Ss][Dd]*)
1602            read_ascii 3 <<-EOF
1603				${c7}\\\\${c3}\`-______,----__
1604				${c7} \\\\        ${c3}__,---\`_
1605				${c7}  \\\\       ${c3}\`.____
1606				${c7}   \\\\${c3}-______,----\`-
1607				${c7}    \\\\
1608				${c7}     \\\\
1609				${c7}      \\\\
1610			EOF
1611        ;;
1612
1613        ([Nn]ix[Oo][Ss]*)
1614            read_ascii 4 <<-EOF
1615				${c4}  \\\\  \\\\ //
1616				${c4} ==\\\\__\\\\/ //
1617				${c4}   //   \\\\//
1618				${c4}==//     //==
1619				${c4} //\\\\___//
1620				${c4}// /\\\\  \\\\==
1621				${c4}  // \\\\  \\\\
1622			EOF
1623        ;;
1624
1625        ([Oo]pen[Bb][Ss][Dd]*)
1626            read_ascii 3 <<-EOF
1627				${c3}      _____
1628				${c3}    \\-     -/
1629				${c3} \\_/         \\
1630				${c3} |        ${c7}O O${c3} |
1631				${c3} |_  <   )  3 )
1632				${c3} / \\         /
1633				 ${c3}   /-_____-\\
1634			EOF
1635        ;;
1636
1637        ([Oo]pen[Ss][Uu][Ss][Ee]*[Tt]umbleweed*)
1638            read_ascii 2 <<-EOF
1639				${c2}  _____   ______
1640				${c2} / ____\\ / ____ \\
1641				${c2}/ /    \`/ /    \\ \\
1642				${c2}\\ \\____/ /,____/ /
1643				${c2} \\______/ \\_____/
1644			EOF
1645        ;;
1646
1647        ([Oo]pen[Ss][Uu][Ss][Ee]*|[Oo]pen*SUSE*|SUSE*|suse*)
1648            read_ascii 2 <<-EOF
1649				${c2}  _______
1650				${c2}__|   __ \\
1651				${c2}     / .\\ \\
1652				${c2}     \\__/ |
1653				${c2}   _______|
1654				${c2}   \\_______
1655				${c2}__________/
1656			EOF
1657        ;;
1658
1659        ([Oo]pen[Ww]rt*)
1660            read_ascii 1 <<-EOF
1661				${c1} _______
1662				${c1}|       |.-----.-----.-----.
1663				${c1}|   -   ||  _  |  -__|     |
1664				${c1}|_______||   __|_____|__|__|
1665				${c1} ________|__|    __
1666				${c1}|  |  |  |.----.|  |_
1667				${c1}|  |  |  ||   _||   _|
1668				${c1}|________||__|  |____|
1669			EOF
1670        ;;
1671
1672        ([Pp]arabola*)
1673            read_ascii 5 <<-EOF
1674				${c5}  __ __ __  _
1675				${c5}.\`_//_//_/ / \`.
1676				${c5}          /  .\`
1677				${c5}         / .\`
1678				${c5}        /.\`
1679				${c5}       /\`
1680			EOF
1681        ;;
1682
1683        ([Pp]op!_[Oo][Ss]*)
1684            read_ascii 6 <<-EOF
1685				${c6}______
1686				${c6}\\   _ \\        __
1687				 ${c6}\\ \\ \\ \\      / /
1688				  ${c6}\\ \\_\\ \\    / /
1689				   ${c6}\\  ___\\  /_/
1690				   ${c6} \\ \\    _
1691				  ${c6} __\\_\\__(_)_
1692				  ${c6}(___________)
1693			EOF
1694        ;;
1695
1696        ([Pp]ure[Oo][Ss]*)
1697            read_ascii <<-EOF
1698				${c7} _____________
1699				${c7}|  _________  |
1700				${c7}| |         | |
1701				${c7}| |         | |
1702				${c7}| |_________| |
1703				${c7}|_____________|
1704			EOF
1705        ;;
1706
1707        ([Rr]aspbian*)
1708            read_ascii 1 <<-EOF
1709				${c2}  __  __
1710				${c2} (_\\)(/_)
1711				${c1} (_(__)_)
1712				${c1}(_(_)(_)_)
1713				${c1} (_(__)_)
1714				${c1}   (__)
1715			EOF
1716        ;;
1717
1718        ([Ss]erenity[Oo][Ss]*)
1719            read_ascii 4 <<-EOF
1720				${c7}    _____
1721				${c1}  ,-${c7}     -,
1722				${c1} ;${c7} (       ;
1723				${c1}| ${c7}. \_${c1}.,${c7}    |
1724				${c1}|  ${c7}o  _${c1} ',${c7}  |
1725				${c1} ;   ${c7}(_)${c1} )${c7} ;
1726				${c1}  '-_____-${c7}'
1727			EOF
1728        ;;
1729
1730        ([Ss]lackware*)
1731            read_ascii 4 <<-EOF
1732				${c4}   ________
1733				${c4}  /  ______|
1734				${c4}  | |______
1735				${c4}  \\______  \\
1736				${c4}   ______| |
1737				${c4}| |________/
1738				${c4}|____________
1739			EOF
1740        ;;
1741
1742        ([Ss]olus*)
1743            read_ascii 4 <<-EOF
1744				${c6} 
1745				${c6}     /|
1746				${c6}    / |\\
1747				${c6}   /  | \\ _
1748				${c6}  /___|__\\_\\
1749				${c6} \\         /
1750				${c6}  \`-------´
1751			EOF
1752        ;;
1753
1754        ([Ss]un[Oo][Ss]|[Ss]olaris*)
1755            read_ascii 3 <<-EOF
1756				${c3}       .   .;   .
1757				${c3}   .   :;  ::  ;:   .
1758				${c3}   .;. ..      .. .;.
1759				${c3}..  ..             ..  ..
1760				${c3} .;,                 ,;.
1761			EOF
1762        ;;
1763
1764        ([Uu]buntu*)
1765            read_ascii 3 <<-EOF
1766				${c3}         _
1767				${c3}     ---(_)
1768				${c3} _/  ---  \\
1769				${c3}(_) |   |
1770				 ${c3} \\  --- _/
1771				    ${c3} ---(_)
1772			EOF
1773        ;;
1774
1775        ([Vv]oid*)
1776            read_ascii 2 <<-EOF
1777				${c2}    _______
1778				${c2} _ \\______ -
1779				${c2}| \\  ___  \\ |
1780				${c2}| | /   \ | |
1781				${c2}| | \___/ | |
1782				${c2}| \\______ \\_|
1783				${c2} -_______\\
1784			EOF
1785        ;;
1786
1787        ([Xx]eonix*)
1788            read_ascii 2 <<-EOF
1789				${c2}    ___  ___
1790				${c2}___ \  \/  / ___
1791				${c2}\  \ \    / /  /
1792				${c2} \  \/    \/  /
1793				${c2}  \    /\    /
1794				${c2}   \__/  \__/
1795			EOF
1796        ;;
1797
1798        (*)
1799            # On no match of a distribution ascii art, this function calls
1800            # itself again, this time to look for a more generic OS related
1801            # ascii art (KISS Linux -> Linux).
1802            [ "$1" ] || {
1803                get_ascii "$os"
1804                return
1805            }
1806
1807            printf 'error: %s is not currently supported.\n' "$os" >&6
1808            printf 'error: Open an issue for support to be added.\n' >&6
1809            exit 1
1810        ;;
1811    esac
1812
1813    # Store the "width" (longest line) and "height" (number of lines)
1814    # of the ascii art for positioning. This script prints to the screen
1815    # *almost* like a TUI does. It uses escape sequences to allow dynamic
1816    # printing of the information through user configuration.
1817    #
1818    # Iterate over each line of the ascii art to retrieve the above
1819    # information. The 'sed' is used to strip '\033[3Xm' color codes from
1820    # the ascii art so they don't affect the width variable.
1821    while read -r line; do
1822        ascii_height=$((${ascii_height:-0} + 1))
1823
1824        # This was a ternary operation but they aren't supported in
1825        # Minix's shell.
1826        [ "${#line}" -gt "${ascii_width:-0}" ] &&
1827            ascii_width=${#line}
1828
1829    # Using '<<-EOF' is the only way to loop over a command's
1830    # output without the use of a pipe ('|').
1831    # This ensures that any variables defined in the while loop
1832    # are still accessible in the script.
1833    done <<-EOF
1834 		$(printf %s "$ascii" | sed 's/\[3.m//g')
1835	EOF
1836
1837    # Add a gap between the ascii art and the information.
1838    ascii_width=$((ascii_width + 4))
1839
1840    # Print the ascii art and position the cursor back where we
1841    # started prior to printing it.
1842    {
1843        esc_p SGR 1
1844        printf '%s' "$ascii"
1845        esc_p SGR 0
1846        esc_p CUU "$ascii_height"
1847    } >&6
1848}
1849
1850main() {
1851    case $* in
1852        -v)
1853            printf '%s 0.7.0\n' "${0##*/}"
1854            return 0
1855        ;;
1856
1857        -d)
1858            # Below exec is not run, stderr is shown.
1859        ;;
1860
1861        '')
1862            exec 2>/dev/null
1863        ;;
1864    esac
1865
1866    # Hide 'stdout' and selectively print to it using '>&6'.
1867    # This gives full control over what it displayed on the screen.
1868    exec 6>&1 >/dev/null
1869
1870    # Store raw escape sequence character for later reuse.
1871    esc_c=$(printf '\033')
1872
1873    # Allow the user to execute their own script and modify or
1874    # extend pfetch's behavior.
1875    # shellcheck source=/dev/null
1876    ! [ -f "$PF_SOURCE" ] || . "$PF_SOURCE"
1877
1878    # Ensure that the 'TMPDIR' is writable as heredocs use it and
1879    # fail without the write permission. This was found to be the
1880    # case on Android where the temporary directory requires root.
1881    [ -w "${TMPDIR:-/tmp}" ] || export TMPDIR=~
1882
1883    # Generic color list.
1884    # Disable warning about unused variables.
1885    # shellcheck disable=2034
1886    for _c in c1 c2 c3 c4 c5 c6 c7 c8; do
1887        esc SGR "3${_c#?}" 0
1888        export "$_c=$e"
1889    done
1890
1891    # Disable line wrapping and catch the EXIT signal to enable it again
1892    # on exit. Ideally you'd somehow query the current value and retain
1893    # it but I'm yet to see this irk anyone.
1894    esc_p DECAWM l >&6
1895    trap 'esc_p DECAWM h >&6' EXIT
1896
1897    # Store the output of 'uname' to avoid calling it multiple times
1898    # throughout the script. 'read <<EOF' is the simplest way of reading
1899    # a command into a list of variables.
1900    read -r os kernel arch <<-EOF
1901		$(uname -srm)
1902	EOF
1903
1904    # Always run 'get_os' for the purposes of detecting which ascii
1905    # art to display.
1906    get_os "$1"
1907
1908    # Allow the user to specify the order and inclusion of information
1909    # functions through the 'PF_INFO' environment variable.
1910    # shellcheck disable=2086
1911    {
1912        # Disable globbing and set the positional parameters to the
1913        # contents of 'PF_INFO'.
1914        set -f
1915        set +f -- ${PF_INFO-ascii title os host kernel uptime pkgs memory}
1916
1917        # Iterate over the info functions to determine the lengths of the
1918        # "info names" for output alignment. The option names and subtitles
1919        # match 1:1 so this is thankfully simple.
1920        for info do
1921            command -v "get_$info" >/dev/null || continue
1922
1923            # This was a ternary operation but they aren't supported in
1924            # Minix's shell.
1925            [ "${#info}" -gt "${info_length:-0}" ] &&
1926                info_length=${#info}
1927        done
1928
1929        # Add an additional space of length to act as a gap.
1930        info_length=$((info_length + 1))
1931
1932        # Iterate over the above list and run any existing "get_" functions.
1933        for info do
1934            "get_$info"
1935        done
1936    }
1937
1938    # Position the cursor below both the ascii art and information lines
1939    # according to the height of both. If the information exceeds the ascii
1940    # art in height, don't touch the cursor (0/unset), else move it down
1941    # N lines.
1942    #
1943    # This was a ternary operation but they aren't supported in Minix's shell.
1944    [ "${info_height:-0}" -lt "${ascii_height:-0}" ] &&
1945        cursor_pos=$((ascii_height - info_height))
1946
1947    # Print '$cursor_pos' amount of newlines to correctly position the
1948    # cursor. This used to be a 'printf $(seq X X)' however 'seq' is only
1949    # typically available (by default) on GNU based systems!
1950    while [ "${i:=0}" -le "${cursor_pos:-0}" ]; do
1951        printf '\n'
1952        i=$((i + 1))
1953    done >&6
1954}
1955
1956main "$@"