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