#!/bin/python3 import subprocess import os import shutil from pathlib import Path class Command: def execute(command, use_os=False): '''Execute bash command Arguments: command {str} -- full command Keyword Arguments: os {bool} -- should the os module be used instead of subprocess (default: {False}) Returns: int/bool -- returncode/True if os is used ''' if not use_os: command = command.split(' ') return subprocess.call(command) else: os.system(command) return True def get_output(command): '''Get standard output of command Arguments: command {str} -- full command Returns: str -- stdout ''' command = command.split(' ') return subprocess.run( command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).stdout.decode('utf-8') def get_return_code(command): '''Get return code of command Arguments: command {str} -- full command Returns: int -- returncode ''' command = command.split(' ') return subprocess.run( command, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).returncode class Color: '''Contains list of colors for printing''' def get_256_color(color): '''Get color using tput (256-colors base) Arguments: color {str/int} -- color id Returns: color -- color prefix for strings ''' return Command.get_output(f'tput setaf {color}') def get_special_color(index): '''Get special colors using tput (like bold, etc..) Arguments: index {str} -- arguments following `tput` command Returns: color -- color prefix for strings ''' return Command.get_output(f'tput {index}') RED = get_256_color(196) BLUE = get_256_color(51) GREEN = get_256_color(46) YELLOW = get_256_color(226) GOLD = get_256_color(214) GREY = get_256_color(238) RESET = get_special_color('sgr0') BOLD = get_special_color('bold') class Print: def question(question): '''Print syntax for question Arguments: question {str} -- text to print (question) ''' print(f'{Color.GREEN} // {question}{Color.RESET}') def question_options(options): '''Print syntax for options for question Arguments: options {str} -- options to print ''' print(f'{Color.GREEN} # {options}{Color.RESET}') def action(action): '''Print syntax for actions Arguments: action {str} -- text to print ''' print(f'{Color.GOLD} >> {action}{Color.RESET}') def err(text): '''Print syntax for error Arguments: text {str} -- error text to print ''' print(f'{Color.RED} !! {text}{Color.RESET}') def cancel(text): '''Print syntax for cancellation Arguments: text {str} -- text to print ''' print(f'{Color.GREY} >> {text}{Color.RESET}') def warning(text): '''Print syntax for warnings Arguments: text {str} -- warn text to print ''' print(f'{Color.YELLOW} ** {text}{Color.RESET}') class Input: def yes_no(question): '''Generate question and wait for yes/no answer from user Arguments: question {str} -- question text Returns: bool -- question result (yes==true) ''' Print.question(question) while True: ans = input(' Y/N: ') if ans.lower() == 'y' or ans == '': return True elif ans.lower() == 'n': return False else: Print.err('Invalid option (Y/N)') def multiple(question, options): '''Generate question and wait for user to pick one of options specified Arguments: question {str} -- question text options {list} -- list of possible options (strings) Returns: str -- one of options ''' def get_input_range(max): while True: inp = input(' ->') try: inp = int(inp) for n in range(0, max + 1): if inp == n: return inp break else: Print.err(f'Invalid input, range: 1-{max}') except ValueError: Print.err(f'Invalid input (must be number: 1-{max})') continue Print.question(question) options_str = '0: No/Cancel / ' for index, option in enumerate(options): options_str += f'{index + 1}: {option} / ' # Remove ending ' / ' options_str = options_str[:-3] Print.question_options(options_str) answer = get_input_range(len(options)) if answer != 0: return options[answer - 1] else: return False class Install: def _generate_install_text(install_text, package_name, yay=False, git=False): if install_text == 'default': install_text = f'Do you wish to install {package_name}?' if install_text[:10] == 'default + ': install_text = f'Do you wish to install {package_name}? {install_text[10:]}' if yay: install_text += '[AUR (YAY) Package]' if git: install_text += '[AUR (GIT) Package]' return install_text def check_not_installed(package_name): '''Check if specified package is currently not installed Arguments: package_name {str/list} -- name of package/es to check Returns: bool -- is/are package/all packages not installed? ''' if type(package_name) == str: out = Command.get_return_code(f'pacman -Qi {package_name}') if out == 1: # NOT INSTALLED return True else: # INSTALLED return False elif type(package_name) == list: for package in package_name: if not Install.check_not_installed(package): break else: return True return False else: raise TypeError( 'check_not_installed() only takes string or list inputs') def git_aur(repository, install_text='default', force=False): '''Install package directly from AUR using only git and makepkg Arguments: repository {string} -- repository name Keyword Arguments: install_text {str} -- text presented to the user before installing (default: {'default' -> 'Do you wish to install [repository]'}) force {bool} -- should the repository be installed even if detected as installed (default: {False}) Returns: bool -- installed ''' if Install.check_not_installed('git'): Print.warning( f'Unable to install github repository: {repository}, git is not installed') return False if Install.check_not_installed(repository) or force: install_text = Install._generate_install_text( install_text, repository, git=True) if Input.yes_no(install_text): url = f'https://aur.archlinux.org/{repository}.git' Command.execute(f'git clone {url}') os.chdir(repository) Command.execute('makepkg -si') os.chdir(Path(__file__).parent.absolute()) shutil.rmtree(repository) return True else: Print.cancel('Skipping...') else: Print.cancel( f'assuming {repository} already installed ({repository} is installed)') def package(package_name, install_text='default', use_yay=False, reinstall=False): '''Installation of package Arguments: package_name {str} -- name of package to be installed Keyword Arguments: install_text {str} -- text presented to user before installing (default: {'default' -> 'Do you wish to install [package_name]'}) use_yay {bool} -- use yay for package installation (default: {False}) reinstall {bool} -- should the package be reinstalled (default {False}) Returns: bool -- installed ''' if use_yay: if Install.check_not_installed('yay'): Print.warning( f'Unable to install AUR package: {package_name}, yay is not installed') return False if Install.check_not_installed(package_name) or reinstall: install_text = Install._generate_install_text( install_text, package_name, yay=use_yay) if Input.yes_no(install_text): if use_yay: Command.execute(f'yay -S {package_name}') else: Command.execute(f'sudo pacman -S {package_name}') return True else: Print.cancel('Skipping...') return False else: Print.cancel(f'{package_name} already installed') return False def multiple_packages(packages, install_text, options=False, reinstall=False): '''Installation of multiple packages Arguments: packages {list} -- list of packages to choose from install_text {str} -- text presented to user when picking which package to install options {list} -- list of options to choose from (must be in same order as packages) Returns: bool/str -- False if none / chosen package name ''' if Install.check_not_installed(packages) or reinstall: if not options: options = packages choice = Input.multiple(f'{install_text}', options) if choice: for index, option in enumerate(options): if choice == option: Command.execute(f'sudo pacman -S {packages[index]}') return packages[index] else: Print.cancel('Skipping...') return False else: for package in packages: if not Install.check_not_installed(package): Print.cancel(f'{package} already installed') return False def upgrade_pacman(): if Input.yes_no('Do you wish to Sync(S), refresh(y) and upgrade(u) pacman - Recommended?'): Command.execute('sudo pacman -Syu') else: Print.warning('Pacman upgrade cancelled.')