[download]

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