From c579d52927a9172aac431e0845a64698a3f4f850 Mon Sep 17 00:00:00 2001 From: Naman Rohit Date: Tue, 4 Nov 2025 21:27:41 -0500 Subject: [PATCH] Updated qfetch with new color system, added a disk space metric, a new ascii art (cat), and fixed qfetch to work properly with WSL. --- qfetch/qfetch.py | 286 ++++++++++++++++++++++++++++++----------------- 1 file changed, 184 insertions(+), 102 deletions(-) diff --git a/qfetch/qfetch.py b/qfetch/qfetch.py index ad55b20..e0f169a 100755 --- a/qfetch/qfetch.py +++ b/qfetch/qfetch.py @@ -14,32 +14,62 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -import os, platform, subprocess, re +import os +import platform +import subprocess +import re import random import argparse -from itertools import zip_longest + """Argparse code""" -about = "qfetch is a simple, no-fuss CLI tool written in Python that gives you a clean snapshot of your system info on Linux and macOS. It shows details like your OS, architecture, packages, shell version, terminal type, memory usage, uptime, and even throws in some fun ASCII art for good measure. It’s designed to be straightforward and looks good right out of the box.\n\n(Designed/Tested for Ubuntu, Debian, and MacOS)\n\nMade with ❤️ by qvipin" +about = ( + "qfetch is a simple, no-fuss CLI tool written in Python that gives you a clean " + "snapshot of your system info on Linux and macOS. It shows details like your OS, " + "architecture, packages, shell version, terminal type, memory usage, disk usage, uptime, " + "and even throws in some fun ASCII art for good measure. It’s designed to be straightforward " + "and looks good right out of the box.\n\n(Designed/Tested for Ubuntu, Debian, and MacOS)\n\n" + "Made with ❤️ by qvipin" +) parser = argparse.ArgumentParser(description=about, formatter_class=argparse.RawTextHelpFormatter) -parser.add_argument("--art", "-a", metavar="", choices=["Playboy-Bunny", "Tux", "Phoenix", "Robot", "Random-Art"], help='select the Ascii Art you want qfetch to use.', required=False) -parser.add_argument("--sys-info", "-s", metavar="", choices=["sys_info_default", "sys_info_no_nerd_font"], help='select how you want qfetch to present your sysinfo', required=False) +parser.add_argument( + "--art", + "-a", + metavar="", + choices=["Playboy-Bunny", "Tux", "Phoenix", "Robot", "Cat", "Random-Art"], + help='select the ASCII Art you want qfetch to use.', + required=False, +) + +parser.add_argument( + "--sys-info", + "-s", + metavar="", + choices=["sys_info_default", "sys_info_no_nerd_font"], + help='select how you want qfetch to present your sysinfo', + required=False, +) + +parser.add_argument( + "--theme", + "-t", + metavar="", + choices=["default", "red", "green", "blue", "yellow", "cyan", "magenta"], + help="select a color theme for side bars and symbols", + required=False, +) args = parser.parse_args() -"""Ascii Art""" +"""ASCII Art""" -####### # The top line of ascii art is tabbed by 1 to work with the side printing logic -####### - art = [ -r''' |\ |\ + r''' |\ |\ \ \| | \ | | .--''/ @@ -47,7 +77,7 @@ \ / {>o<}=' ''', -r''' .---. + r''' .---. / \ \.@-@./ /`\_/`\ @@ -56,9 +86,9 @@ /`\_`> <_/ \ \__/'---'\__/ ''', -r''' .\\ //. + r''' .\\ //. . \ \ / /. -.\ ,\ /` /,.- +.\ ,\ /` /,.- -. \ /'/ / . ` - `-' \ - '. /.\` @@ -67,20 +97,31 @@ .`.' .' ''', -r''' __ + r''' __ _ |@@| / \ \--/ __ ) O|----| | __ / / \ }{ /\ )_ / _\ - )/ /\__/\ \__O (__ + )/ /\__/\ \__O (__ |/ (--/\--) \__/ -/ _)( )(_ + / _)( )(_ `---''---` +''', + r''' + |\---/| + | ,_, | + \_`_/-..----. + ___/ ` ' ,""+ \ +(__...' __\ |`.___.'; + (_,...'(_,.`__)/'.....+ ''' - ] +rand_art = random.choice(art) # Random art choice + + """OS Check""" + def os_check(): operating_sys = platform.system() if operating_sys == "Darwin": @@ -89,158 +130,199 @@ def os_check(): return "Windows" elif operating_sys == "Linux": return "Linux" - else: - raise Exception("[!] Couldn't detect operating system, make a Github Issue if you're on MacOS or Linux") + else: + raise Exception( + "[!] Couldn't detect operating system, make a Github Issue if you're on MacOS or Linux" + ) """Art-related Code""" -### random art if choosed (ik this is a bad way but whatever) -rand_art = random.choice(art) +def theme_color_code(): + """Return ANSI light color code based on theme""" + colors = { + "default": "95", + "red": "91", + "green": "92", + "yellow": "93", + "blue": "94", + "magenta": "95", + "cyan": "96", + } + return colors.get(args.theme or "default") + def ascii_art(): - # current choices ["Playboy-Bunny", "Tux", "Phoenix", "Robot"] - + """Return ASCII art string based on user selection""" final_art = art[2] - + if args.art: art_dict = { "Playboy-Bunny": art[0], "Tux": art[1], "Phoenix": art[2], "Robot": art[3], - "Random-Art": rand_art - } + "Cat": art[4], + "Random-Art": rand_art, + } final_art = art_dict.get(args.art) + return final_art - - -def longest_line(): - # This is to determine the longest line in the rand art for spacing - art_lines = ((ascii_art()).splitlines()) +def longest_line(): + """Determine the longest line in the ASCII art""" + art_lines = ascii_art().splitlines() l_line = len(art_lines[0]) - for l in range(len(art_lines)): - if len(art_lines[l]) > l_line: - l_line = len(art_lines[l]) - return l_line + for line in art_lines: + if len(line) > l_line: + l_line = len(line) + return l_line """System information elements""" -# -# Additional modules for system information -# - def package_count(): if os_check() == "macOS": brew_count = len(os.listdir("/opt/homebrew/cellar")) - final_count = str(brew_count) + " (brew)" # + any other package manager added in the future + final_count = str(brew_count) + " (brew)" elif os_check() == "Linux": - apt_count = subprocess.check_output("dpkg --get-selections | wc -l", shell=True, text=True).strip() - final_count = str(apt_count) + " (apt)" # + any other package manager added in the future + apt_count = subprocess.check_output( + "dpkg --get-selections | wc -l", shell=True, text=True + ).strip() + final_count = str(apt_count) + " (apt)" return final_count + def shell_ver(): - if os_check() == "macOS" or "Linux": + if os_check() in ["macOS", "Linux"]: shell = subprocess.check_output("echo $SHELL", shell=True, text=True).strip() - if "zsh" in str(shell): + is_wsl = "microsoft" in platform.release().lower() + + if "zsh" in shell: zsh_ver = subprocess.check_output("zsh --version", shell=True, text=True).strip() - return str(zsh_ver[:-25]) - elif "bash" in str(shell): - bash_ver = subprocess.check_output("bash --version", shell=True, text=True).strip() - return "bash" + str(bash_ver[17:-72]) # Indexed for clean output with bash --version + return str(zsh_ver.split()[1]) + elif "bash" in shell: + if is_wsl: + return "bash (WSL)" + else: + bash_ver = subprocess.check_output("bash --version", shell=True, text=True).strip() + return "bash " + bash_ver.split()[3] else: raise Exception("[!] This operating system isn't supported by qfetch.") + def find_term(): - if os_check() == "macOS" or "Linux": + if os_check() in ["macOS", "Linux"]: term = subprocess.check_output("echo $TERM", shell=True, text=True).strip() return term else: raise Exception("[!] This operating system isn't supported by qfetch.") + def memory_current(): if os_check() == "macOS": mem_total = int(subprocess.check_output("sysctl -n hw.memsize", shell=True, text=True).strip()) // (1024 ** 2) page_size = int(subprocess.check_output("sysctl -n vm.pagesize", shell=True, text=True).strip()) vm_stat_output = subprocess.check_output("vm_stat", shell=True, text=True).strip() - - # extract memory page counts + pages_active = int(re.search(r"Pages active:\s+(\d+)", vm_stat_output).group(1)) pages_wired = int(re.search(r"Pages wired down:\s+(\d+)", vm_stat_output).group(1)) pages_speculative = int(re.search(r"Pages speculative:\s+(\d+)", vm_stat_output).group(1)) - # calculate used memory in mibibytes mem_used = ((pages_active + pages_wired + pages_speculative) * page_size) // (1024 * 1024) - return f"{mem_used}MiB / {mem_total}MiB" - + elif os_check() == "Linux": linux_mem = subprocess.check_output( - "free -m | awk '/Mem:/ {printf \"%dMiB / %dMiB\\n\", $3, $2}'", - shell=True, - text=True).strip() # take free -m and format it for what we want + "free -m | awk '/Mem:/ {printf \"%dMiB / %dMiB\\n\", $3, $2}'", + shell=True, + text=True, + ).strip() return linux_mem + else: - raise Exception("[!] This operating system isn't supported by qfetch.") - + raise Exception("[!] This operating system isn't supported by qfetch.") + + def uptime(): if os_check() in ['macOS', 'Linux']: uptime_command = subprocess.check_output("uptime", shell=True, text=True) match = re.search( - r"\d{1,2}:\d{2}\s+up\s+(?:(\d+)\s+day(?:s)?,\s+)?(?:(\d+):(\d+))?(?:(\d+)\s+min(?:ute)?s?)?", - uptime_command + r"\d{1,2}:\d{2}\s+up\s+(?:(\d+)\s+day(?:s)?,\s+)?(?:(\d+):(\d+))?(?:(\d+)\s+min(?:ute)?s?)?", + uptime_command, ) - + if not match: raise Exception("[!] Something went wrong, your uptime might be broken. Please make a Github Issue.") - + days = int(match.group(1) or 0) hours = int(match.group(2) or 0) if match.group(2) else 0 minutes = int(match.group(3) or 0) if match.group(3) else int(match.group(4) or 0) - - def p(val, unit): + + def p(val, unit): return f"{val} {unit}{'' if val == 1 else 's'}" return f"{p(days, 'Day')}, {p(hours, 'Hour')}, {p(minutes, 'Minute')}" else: raise Exception("[!] This operating system isn't supported by qfetch.") - - + + +def disk_usage(): + if os_check() in ["Linux", "macOS"]: + usage = subprocess.check_output( + "df -h / | awk 'NR==2 {print $3 \" / \" $2}'", + shell=True, + text=True, + ).strip() + return usage + return "N/A" + + def print_color_palette(): lines = [] - for i in range(2): # 0 for normal, 1 for bright + for i in range(2): line = "" - for j in range(8): # 8 base colors + for j in range(8): color = 30 + j if i == 1: - color += 60 # bright variant + color += 60 block = f"\033[{color}m█\033[0m" line += block * 3 lines.append(line) return lines - -# -# Main system information function -# + +def theme_bar(): + """Return colored side bar""" + code = theme_color_code() + return f"\033[{code}m▐\033[0m" + + +def colored_symbol(symbol): + """Return symbol colored with the theme""" + code = theme_color_code() + return f"\033[{code}m{symbol}\033[0m" + + +"""Main system information function""" def sysinfo(): - # sys info w/ options - sys_info_default = [ # ansi codes for colors - f"{'\033[38;2;128;146;224m\033[0m' if os_check() == 'macOS' else '\033[38;2;128;146;224m\033[0m'} \033[38;2;211;211;255m▐\033[0m {platform.platform(terse=True)}", - f"\033[38;2;128;146;224m\033[0m \033[38;2;211;211;255m▐\033[0m {platform.machine()}", - f"\033[38;2;128;146;224m\033[0m \033[38;2;211;211;255m▐\033[0m {package_count()}", - f"\033[38;2;128;146;224m\033[0m \033[38;2;211;211;255m▐\033[0m {shell_ver()}", - f"\033[38;2;128;146;224m\033[0m \033[38;2;211;211;255m▐\033[0m {find_term()}", - f"\033[38;2;128;146;224m\033[0m \033[38;2;211;211;255m▐\033[0m {memory_current()}", - f"\033[38;2;128;146;224m\033[0m \033[38;2;211;211;255m▐\033[0m {uptime()}", + bar = theme_bar() + + sys_info_default = [ + f"{colored_symbol('') if os_check() == 'macOS' else colored_symbol('')} {bar} {platform.platform(terse=True)}", + f"{colored_symbol('')} {bar} {platform.machine()}", + f"{colored_symbol('')} {bar} {package_count()}", + f"{colored_symbol('')} {bar} {shell_ver()}", + f"{colored_symbol('')} {bar} {find_term()}", + f"{colored_symbol('')} {bar} {memory_current()}", + f"{colored_symbol('')} {bar} {disk_usage()}", + f"{colored_symbol('')} {bar} {uptime()}", "", - *print_color_palette() - ] - + *print_color_palette(), + ] + sys_info_no_nerd_font = [ f"OS: {platform.platform(terse=True)}", f"Arch: {platform.machine()}", @@ -248,11 +330,12 @@ def sysinfo(): f"Shell: {shell_ver()}", f"Term: {find_term()}", f"Memory: {memory_current()}", + f"Disk: {disk_usage()}", f"Uptime: {uptime()}", "", - *print_color_palette() - ] - + *print_color_palette(), + ] + if args.sys_info == "sys_info_default": return sys_info_default elif args.sys_info == "sys_info_no_nerd_font": @@ -260,26 +343,25 @@ def sysinfo(): else: return sys_info_default + """Main Function""" def main(): - # OS Check if os_check() == "Windows": print("[!] Oops! qfetch doesn't support Windows yet.") - # width logic - if longest_line() > 15: - width = longest_line() + 3 - else: - width = 15 + art_lines = ascii_art().splitlines() - # main side text loop - for line, stat in zip_longest(((ascii_art())).splitlines(), (sysinfo()), fillvalue=""): - # spacing control - spacing = ' ' * (width - len(line)) # 15 total wide, so minus art line length and the rest is spacing - final_line = str(line) + spacing + str(stat) + width = max(longest_line() + 3, 15) + + for i, stat in enumerate(sysinfo()): + line = art_lines[i] if i < len(art_lines) else "" + spacing = ' ' * (width - len(line)) + colored_line = f"\033[{theme_color_code()}m{line}\033[0m" + final_line = colored_line + spacing + str(stat) print(final_line) - + if __name__ == "__main__": main() +