├── piptui ├── custom │ ├── __init__.py │ ├── apNPSApplicationEvents.py │ ├── customForm.py │ └── customTheme.py ├── __version__.py ├── __main__.py ├── __init__.py ├── run_threaded.py ├── settings.py ├── infoBox.py ├── pkgBox.py ├── searchBox.py ├── settingsForm.py ├── app.py ├── mainForm.py ├── pkgInfoForm.py ├── actionForms.py └── logBox.py ├── MANIFEST.in ├── requirements.txt ├── .piptui ├── config.ini └── themes │ ├── colorful.json │ ├── elegant.json │ ├── black_on_white.json │ └── transparent.json ├── theme_example.json ├── LICENSE ├── README.md ├── setup.py └── .gitignore /piptui/custom/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /piptui/__version__.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.3.8" 2 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include .piptui/* 2 | include .piptui/themes/* -------------------------------------------------------------------------------- /piptui/__main__.py: -------------------------------------------------------------------------------- 1 | from .app import main 2 | 3 | main() 4 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | npyscreen 2 | requests 3 | windows-curses; sys_platform == "win32" 4 | -------------------------------------------------------------------------------- /.piptui/config.ini: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | check_for_update = true 3 | use_user_arg = true 4 | theme = elegant.json 5 | -------------------------------------------------------------------------------- /piptui/__init__.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | 3 | from .run_threaded import threaded 4 | 5 | INSTALLED = [] 6 | 7 | 8 | @threaded 9 | def get_insalled(): 10 | global INSTALLED 11 | for pkg in sorted(pkg_resources.working_set, key=lambda x: x.key): 12 | INSTALLED.append(str(pkg)) 13 | 14 | 15 | get_insalled() 16 | -------------------------------------------------------------------------------- /piptui/run_threaded.py: -------------------------------------------------------------------------------- 1 | import functools 2 | from threading import Thread 3 | 4 | 5 | def threaded(func): 6 | @functools.wraps(func) 7 | def wrapper(*args, **kwargs): 8 | thread = Thread(target=func, args=args, kwargs=kwargs) 9 | wrapper.__thread__ = thread 10 | try: 11 | thread.start() 12 | except KeyboardInterrupt: 13 | thread.stop() 14 | return thread 15 | 16 | return wrapper 17 | -------------------------------------------------------------------------------- /.piptui/themes/colorful.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEFAULT": "RED_BLACK", 3 | "FORMDEFAULT": "YELLOW_BLACK", 4 | "NO_EDIT": "BLUE_BLACK", 5 | "STANDOUT": "CYAN_BLACK", 6 | "CURSOR": "WHITE_BLACK", 7 | "CURSOR_INVERSE": "BLACK_WHITE", 8 | "LABEL": "BLUE_BLACK", 9 | "LABELBOLD": "YELLOW_BLACK", 10 | "CONTROL": "GREEN_BLACK", 11 | "WARNING": "RED_BLACK", 12 | "CRITICAL": "BLACK_RED", 13 | "GOOD": "GREEN_BLACK", 14 | "GOODHL": "GREEN_BLACK", 15 | "VERYGOOD": "BLACK_GREEN", 16 | "CAUTION": "YELLOW_BLACK", 17 | "CAUTIONHL": "BLACK_YELLOW" 18 | } -------------------------------------------------------------------------------- /.piptui/themes/elegant.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEFAULT": "WHITE_BLACK", 3 | "FORMDEFAULT": "WHITE_BLACK", 4 | "NO_EDIT": "BLUE_BLACK", 5 | "STANDOUT": "CYAN_BLACK", 6 | "CURSOR": "CYAN_BLACK", 7 | "CURSOR_INVERSE": "BLACK_CYAN", 8 | "LABEL": "GREEN_BLACK", 9 | "LABELBOLD": "WHITE_BLACK", 10 | "CONTROL": "YELLOW_BLACK", 11 | "WARNING": "RED_BLACK", 12 | "CRITICAL": "BLACK_RED", 13 | "GOOD": "GREEN_BLACK", 14 | "GOODHL": "GREEN_BLACK", 15 | "VERYGOOD": "BLACK_GREEN", 16 | "CAUTION": "YELLOW_BLACK", 17 | "CAUTIONHL": "BLACK_YELLOW" 18 | } -------------------------------------------------------------------------------- /.piptui/themes/black_on_white.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEFAULT" : "BLACK_WHITE", 3 | "FORMDEFAULT" : "BLACK_WHITE", 4 | "NO_EDIT" : "BLUE_WHITE", 5 | "STANDOUT" : "CYAN_WHITE", 6 | "CURSOR" : "BLACK_WHITE", 7 | "CURSOR_INVERSE": "WHITE_BLACK", 8 | "LABEL" : "RED_WHITE", 9 | "LABELBOLD" : "BLACK_WHITE", 10 | "CONTROL" : "BLUE_WHITE", 11 | "WARNING" : "RED_WHITE", 12 | "CRITICAL" : "BLACK_RED", 13 | "GOOD" : "GREEN_BLACK", 14 | "GOODHL" : "GREEN_WHITE", 15 | "VERYGOOD" : "WHITE_GREEN", 16 | "CAUTION" : "YELLOW_WHITE", 17 | "CAUTIONHL" : "BLACK_YELLOW" 18 | } -------------------------------------------------------------------------------- /theme_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEFAULT":"WHITE_BLACK", 3 | "FORMDEFAULT":"WHITE_BLACK", 4 | "NO_EDIT":"BLUE_BLACK", 5 | "STANDOUT":"CYAN_BLACK", 6 | "CURSOR":"WHITE_BLACK", 7 | "CURSOR_INVERSE":"BLACK_WHITE", 8 | "LABEL":"GREEN_BLACK", 9 | "LABELBOLD":"WHITE_BLACK", 10 | "CONTROL":"YELLOW_BLACK", 11 | "IMPORTANT":"GREEN_BLACK", 12 | "SAFE":"GREEN_BLACK", 13 | "WARNING":"YELLOW_BLACK", 14 | "DANGER":"RED_BLACK", 15 | "CRITICAL":"BLACK_RED", 16 | "GOOD":"GREEN_BLACK", 17 | "GOODHL":"GREEN_BLACK", 18 | "VERYGOOD":"BLACK_GREEN", 19 | "CAUTION":"YELLOW_BLACK", 20 | "CAUTIONHL":"BLACK_YELLOW" 21 | } -------------------------------------------------------------------------------- /.piptui/themes/transparent.json: -------------------------------------------------------------------------------- 1 | { 2 | "DEFAULT" : "WHITE_ON_DEFAULT", 3 | "FORMDEFAULT" : "WHITE_ON_DEFAULT", 4 | "NO_EDIT" : "BLUE_ON_DEFAULT", 5 | "STANDOUT" : "CYAN_ON_DEFAULT", 6 | "CURSOR" : "WHITE_BLACK", 7 | "CURSOR_INVERSE": "BLACK_WHITE", 8 | "LABEL" : "RED_ON_DEFAULT", 9 | "LABELBOLD" : "BLACK_ON_DEFAULT", 10 | "CONTROL" : "BLUE_ON_DEFAULT", 11 | "WARNING" : "RED_BLACK", 12 | "CRITICAL" : "BLACK_RED", 13 | "GOOD" : "GREEN_BLACK", 14 | "GOODHL" : "GREEN_BLACK", 15 | "VERYGOOD" : "BLACK_GREEN", 16 | "CAUTION" : "YELLOW_BLACK", 17 | "CAUTIONHL" : "BLACK_YELLOW" 18 | } -------------------------------------------------------------------------------- /piptui/settings.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | from pathlib import Path 3 | import os 4 | 5 | HOME_DIR = str(Path.home()) 6 | PIPTUI_DIR = HOME_DIR + '/.piptui/' 7 | CONFIG_PATH = PIPTUI_DIR + 'config.ini' 8 | THEME_FOLDER = PIPTUI_DIR + 'themes/' 9 | 10 | config = configparser.ConfigParser() 11 | config.read(CONFIG_PATH) 12 | 13 | 14 | def get_config_value(option, as_bool=False): 15 | if as_bool is True: 16 | return config.getboolean('DEFAULT', option) 17 | else: 18 | return config.get('DEFAULT', option) 19 | 20 | 21 | def set_config_value(option, value): 22 | with open(CONFIG_PATH, 'w') as configfile: 23 | config.set('DEFAULT', option, value) 24 | config.write(configfile) 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 nitanmarcel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /piptui/custom/apNPSApplicationEvents.py: -------------------------------------------------------------------------------- 1 | import collections 2 | 3 | from npyscreen import StandardApp, apNPSApplicationEvents 4 | 5 | 6 | class PipTuiEvents(object): 7 | def __init__(self): 8 | self.interal_queue = collections.deque() 9 | super(apNPSApplicationEvents).__init__() 10 | 11 | def get(self, maximum=None): 12 | if maximum is None: 13 | maximum = -1 14 | counter = 1 15 | while counter != maximum: 16 | try: 17 | yield self.interal_queue.pop() 18 | except IndexError: 19 | pass 20 | counter += 1 21 | 22 | 23 | class PipTuiApp(StandardApp): 24 | def __init__(self): 25 | super(StandardApp, self).__init__() 26 | self.event_directory = {} 27 | self.event_queues = {} 28 | self.initalize_application_event_queues() 29 | self.initialize_event_handling() 30 | 31 | def process_event_queues(self, max_events_per_queue=None): 32 | for queue in self.event_queues.values(): 33 | try: 34 | for event in queue.get(maximum=max_events_per_queue): 35 | try: 36 | self.process_event(event) 37 | except StopIteration: 38 | pass 39 | except RuntimeError: 40 | pass 41 | -------------------------------------------------------------------------------- /piptui/infoBox.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from npyscreen import BoxTitle, MultiLineEdit 3 | 4 | from .run_threaded import threaded 5 | 6 | 7 | class InfoBox(BoxTitle): 8 | _contained_widget = MultiLineEdit 9 | 10 | def create(self): 11 | self.display() 12 | 13 | @threaded 14 | def display_info(self): 15 | self.value = 'Loading package description!' 16 | self.update() 17 | current_selection = self.parent.parentApp.MainForm.PkgBoxObj.value 18 | pkg_val = self.parent.parentApp.MainForm.PkgBoxObj.values 19 | if len(pkg_val) > 0: 20 | pkg = self.parent.parentApp.MainForm.PkgBoxObj.values[current_selection].split()[ 21 | 0] 22 | request = requests.get('https://pypi.org/pypi/{}/json'.format(pkg)) 23 | if request.status_code == 200: 24 | data = request.json() 25 | info = data.get('info') 26 | description = info.get('description') 27 | summary = info.get('summary') 28 | self.value = description or summary or "Package description unavailable" 29 | else: 30 | self.value = "Couldn't find this package on pypi.org! ERROR: " + \ 31 | str(request.status_code) 32 | else: 33 | self.value = "No packages found" 34 | self.update() 35 | -------------------------------------------------------------------------------- /piptui/pkgBox.py: -------------------------------------------------------------------------------- 1 | import webbrowser 2 | 3 | import requests 4 | from npyscreen import BoxTitle, Event 5 | 6 | from . import INSTALLED 7 | 8 | 9 | class PkgBox(BoxTitle): 10 | def create(self): 11 | self.display() 12 | self.values = INSTALLED 13 | self.update() 14 | 15 | def when_value_edited(self): 16 | self.parent.parentApp.queue_event( 17 | Event("event_package_select")) 18 | 19 | def open_in_browser(self): 20 | pkg = self.values[self.value].split()[0] 21 | request = requests.get('https://pypi.org/pypi/{}/json'.format(pkg)) 22 | if request.status_code == 200: 23 | js_data = request.json() 24 | info = js_data.get('info') 25 | project_url = info.get('project_url') 26 | webbrowser.open(project_url) 27 | else: 28 | print('\a') 29 | 30 | def open_homepage(self): 31 | pkg = self.values[self.value].split()[0] 32 | request = requests.get('https://pypi.org/pypi/{}/json'.format(pkg)) 33 | if request.status_code == 200: 34 | js_data = request.json() 35 | info = js_data.get('info') 36 | home_page = info.get('home_page') 37 | if home_page: 38 | webbrowser.open(home_page) 39 | else: 40 | print('\a') 41 | else: 42 | print('\a') 43 | -------------------------------------------------------------------------------- /piptui/custom/customForm.py: -------------------------------------------------------------------------------- 1 | import curses 2 | 3 | from npyscreen import FormBaseNew, ActionForm 4 | 5 | 6 | class FormBaseNewHinted(FormBaseNew): 7 | FRAMED = False 8 | 9 | def display_menu_advert_at(self): 10 | return self.lines - 1, 1 11 | 12 | def draw_form(self): 13 | super(FormBaseNewHinted, self).draw_form() 14 | menu_advert = '^A: Install\t^R: Uninstall\t^U: Update\t^S: Search\t^O: Open in Browser\t^H: Open HomePage\t^D: Details\t^Q: Quit' 15 | if isinstance(menu_advert, bytes): 16 | menu_advert = menu_advert.decode('utf-8', 'replace') 17 | y, x = self.display_menu_advert_at() 18 | self.add_line(y, x, 19 | menu_advert, 20 | self.make_attributes_list(menu_advert, curses.A_NORMAL), 21 | self.columns - x - 1, 22 | ) 23 | 24 | 25 | class ActionFormHinted(FormBaseNew): 26 | def display_menu_advert_at(self): 27 | return self.lines - 1, 1 28 | 29 | def draw_form(self): 30 | super(ActionFormHinted, self).draw_form() 31 | menu_advert = '^A: Install Release\t^Q: Home' 32 | if isinstance(menu_advert, bytes): 33 | menu_advert = menu_advert.decode('utf-8', 'replace') 34 | y, x = self.display_menu_advert_at() 35 | self.add_line(y, x, 36 | menu_advert, 37 | self.make_attributes_list(menu_advert, curses.A_NORMAL), 38 | self.columns - x - 1, 39 | ) 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PipTUI 2 | Pip GUI in your terminal! 3 | 4 | ![main](https://user-images.githubusercontent.com/41646249/62012144-02f9b300-b18b-11e9-864c-ef41a069c53e.png) 5 | ![search](https://user-images.githubusercontent.com/41646249/62012153-291f5300-b18b-11e9-988a-2fa9d81d8df8.png) 6 | 7 | 8 | ### Installation 9 | 10 | ```bash 11 | pip3 install PipTUI 12 | ``` 13 | 14 | 15 | ### Running it: 16 | ```bash 17 | piptui 18 | ``` 19 | 20 | ### Shortcuts 21 | 22 | * Open settings: `F1` 23 | * Install package: `Ctrl-A` 24 | * Uninstall package: `Ctrl-R` 25 | * Update package: `Ctrl-U` 26 | * Search package: `Ctrl-S` 27 | * Open in browser: `Ctrl-O` 28 | * Open HomePage: `Ctrl-H` 29 | * Show Package Details: `Ctrl-D` 30 | 31 | 32 | 33 | ### DISCLAIMER 34 | 35 | This app only works if `pip` is installed and it's not a pip alternative, it's just GUI wrapper for the pip commands! 36 | 37 | If you're using other ways to install python packages some changes should be made in your end. A little example is to set a pip alias if you're using apps like `pipx` to install python packages! 38 | 39 | 40 | ### Theming 41 | 42 | 43 | To add new themes check `theme_example.json` and add the json file in the `$HOME.piptui/themes` folder. 44 | 45 | 46 | Available Colors: 47 | 48 | * BLACK_RED 49 | * MAGENTA_BLACK 50 | * BLUE_BLACK 51 | * YELLOW_BLACK 52 | * BLACK_YELLOW 53 | * BLACK_GREEN 54 | * RED_BLACK 55 | * YELLOW_WHITE 56 | * CYAN_BLACK 57 | * GREEN_WHITE 58 | * RED_WHITE 59 | * CYAN_WHITE 60 | * BLUE_WHITE 61 | * GREEN_BLACK 62 | * MAGENTA_WHITE 63 | * BLACK_WHITE 64 | * CYAN_ON_DEFAULT 65 | * BLUE_ON_DEFAULT 66 | * WHITE_ON_DEFAULT 67 | * GREEN_ON_DEFAULT 68 | * BLACK_ON_DEFAULT 69 | * MAGENTA_ON_DEFAULT 70 | * YELLOW_ON_DEFAULT 71 | * RED_ON_DEFAULT 72 | 73 | ### Buy me a beer: 74 | 75 | - PayPal: https://www.paypal.me/marcelalexandrunitan?locale.x=en_US 76 | - Revolut: Hit me on telegram with a payment link: https://t.me/nitanmarcel 77 | 78 | 79 | 80 | ### License: MIT 81 | -------------------------------------------------------------------------------- /piptui/searchBox.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | import requests 4 | from npyscreen import BoxTitle, TextCommandBox 5 | 6 | from . import INSTALLED 7 | from .run_threaded import threaded 8 | 9 | 10 | class SearchBox(BoxTitle): 11 | _contained_widget = TextCommandBox 12 | 13 | 14 | PYPI = [] 15 | 16 | 17 | @threaded 18 | def get_pypi(): 19 | global PYPI 20 | request = requests.get('https://pypi.org/simple/') 21 | if request.status_code == 200: 22 | data = request.text 23 | pattern = re.compile(r' (.+)') 24 | PYPI = [pattern.match(line).group(1) 25 | for line in data.split("\n")[6:-2]] 26 | 27 | 28 | class QueryPypi: 29 | def __init__(self): 30 | get_pypi() 31 | 32 | def process_command_complete(self, value, *args, **kwargs): 33 | if value.value == '': 34 | value.parent.PkgBoxObj.values = INSTALLED 35 | value.parent.PkgBoxObj.update() 36 | @threaded 37 | def process_command_live(self, value, **kwargs): 38 | packages = [] 39 | if not value.value: 40 | value.parent.PkgBoxObj.values = INSTALLED 41 | value.parent.PkgBoxObj.update() 42 | else: 43 | query_pypi(mainbox=value.parent.PkgBoxObj, to_query=value.value) 44 | value.parent.PkgBoxObj.value = 0 45 | value.parent.PkgBoxObj.update() 46 | value.parent.InfoBoxObj.display_info() 47 | 48 | 49 | def query_pypi(mainbox, to_query): 50 | global PYPI 51 | packages = [] 52 | if not PYPI: 53 | request = requests.get('https://pypi.org/simple/') 54 | if request.status_code == 200: 55 | data = request.text 56 | pattern = re.compile(r' (.+)') 57 | PYPI = [pattern.match(line).group(1) 58 | for line in data.split("\n")[6:-2]] 59 | for pkg in PYPI: 60 | if to_query.lower() in pkg.lower(): 61 | packages.append(pkg) 62 | mainbox.values = packages 63 | mainbox.update() 64 | -------------------------------------------------------------------------------- /piptui/settingsForm.py: -------------------------------------------------------------------------------- 1 | from npyscreen import ActionForm, OptionList, TitleText, OptionListDisplay, OptionSingleChoice 2 | from .settings import set_config_value, get_config_value, THEME_FOLDER 3 | import os 4 | 5 | 6 | class ChangeUserOption(OptionSingleChoice): 7 | def when_set(self): 8 | if len(self.value[0]) > 1: 9 | set_config_value('use_user_arg', list(self.value)[0]) 10 | self.value = self.value[0] 11 | 12 | 13 | class ChangeUpdateOption(OptionSingleChoice): 14 | def when_set(self): 15 | if len(self.value[0]) > 1: 16 | set_config_value('check_for_update', list(self.value)[0]) 17 | self.value = self.value[0] 18 | 19 | 20 | class ChangeThemeOption(OptionSingleChoice): 21 | def when_set(self): 22 | if len(self.value[0]) > 1: 23 | set_config_value('theme', list(self.value)[0]) 24 | self.value = self.value[0] 25 | 26 | 27 | class SettingsForm(ActionForm): 28 | def create(self): 29 | self.name = 'Settings' 30 | option = OptionList() 31 | options = option.options 32 | options.append( 33 | ChangeUserOption( 34 | 'Install as user?', 35 | value=get_config_value('use_user_arg'), 36 | choices=[ 37 | 'true', 38 | 'false'], 39 | documentation=['Disable or enable pip\'s \'--user\' command argument'])) 40 | options.append( 41 | ChangeUpdateOption( 42 | 'Check For App Update?', 43 | value=get_config_value('check_for_update'), 44 | choices=[ 45 | 'true', 46 | 'false'], 47 | documentation=['Disable or enable application update check at startup'])) 48 | options.append( 49 | ChangeThemeOption( 50 | 'Theme:', 51 | value=get_config_value('theme'), 52 | choices=os.listdir(THEME_FOLDER), 53 | documentation=['Change app\'s theme'])) 54 | self.add(OptionListDisplay, values=options) 55 | 56 | def on_ok(self): 57 | self.parentApp.switchForm('MAIN') 58 | 59 | def on_cancel(self): 60 | self.parentApp.switchForm('MAIN') 61 | -------------------------------------------------------------------------------- /piptui/app.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | from npyscreen import setTheme 4 | 5 | from .actionForms import InstallForm, InstallVersionForm, UninstallForm, UpdateForm, UpdateAppForm 6 | from .custom.apNPSApplicationEvents import PipTuiApp 7 | from .custom.customTheme import PipTuiTheme 8 | from .mainForm import MainForm 9 | from .pkgInfoForm import PkgInfoForm 10 | from .settingsForm import SettingsForm 11 | from . import __version__ 12 | from .run_threaded import threaded 13 | from .settings import get_config_value 14 | 15 | 16 | class App(PipTuiApp): 17 | def onStart(self): 18 | setTheme(PipTuiTheme) 19 | self.release = __version__.__version__ 20 | self.MainForm = self.addForm("MAIN", MainForm) 21 | self.InstallForm = self.addForm( 22 | "INSTALL", InstallForm, lines=6, columns=26) 23 | self.InstallVersionForm = self.addForm( 24 | "INSTALL_VERSION", InstallVersionForm, lines=6, columns=26) 25 | self.UninstallForm = self.addForm( 26 | "UNINSTALL", UninstallForm, lines=6, columns=26) 27 | self.UpdateForm = self.addForm( 28 | "UPDATE", UpdateForm, lines=6, columns=26) 29 | self.PkgInfoForm = self.addForm('PKG_INFO', PkgInfoForm) 30 | 31 | self.UpdateAppForm = self.addForm( 32 | 'APP_UPDATE', UpdateAppForm, lines=6, columns=26) 33 | self.SettingsForm = self.addForm('SETTINGS', SettingsForm) 34 | if get_config_value('check_for_update') is True: 35 | self.check_for_update() 36 | 37 | @threaded 38 | def check_for_update(self): 39 | request = requests.get('https://pypi.org/pypi/PipTUI/json') 40 | if request.status_code == 200: 41 | json_data = request.json() 42 | self.release = max( 43 | list( 44 | json_data.get('releases')), 45 | key=self.major_minor_micro) 46 | if __version__.__version__ != self.release: 47 | self.switchForm("APP_UPDATE") 48 | 49 | def major_minor_micro(self, version): 50 | major, minor, micro = re.search( 51 | r'(\d+)\.(\d+)\.(\d+)', version).groups() 52 | return int(major), int(minor), int(micro) 53 | 54 | 55 | def main(): 56 | App().run() 57 | 58 | 59 | if __name__ == "__main__": 60 | main() 61 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import find_packages, _install_setup_requires 2 | from piptui import __version__ 3 | from pathlib import Path 4 | import distutils.core 5 | import os 6 | 7 | requirements = ['npyscreen @ https://github.com/npcole/npyscreen', 'requests', 'windows-curses; sys_platform == "win32"'] 8 | 9 | 10 | def setup(**attrs): 11 | HOME_DIR = str(Path.home()) 12 | PIPTUI_DIR = HOME_DIR + '/.piptui/' 13 | THEME_FOLDER = PIPTUI_DIR + 'themes/' 14 | if not os.path.isdir(PIPTUI_DIR): 15 | os.mkdir(PIPTUI_DIR) 16 | if not os.path.isdir(THEME_FOLDER): 17 | os.mkdir(THEME_FOLDER) 18 | 19 | with open('.piptui/config.ini') as original: 20 | with open(PIPTUI_DIR + 'config.ini', 'w') as new: 21 | new.write(original.read()) 22 | 23 | for file in os.listdir('.piptui/themes'): 24 | with open('.piptui/themes/' + file) as original: 25 | with open(THEME_FOLDER + file, 'w') as new: 26 | new.write(original.read()) 27 | _install_setup_requires(attrs) 28 | return distutils.core.setup(**attrs) 29 | def main(): 30 | setup( 31 | name='PipTUI', 32 | version=__version__.__version__, 33 | packages=find_packages(), 34 | data_files=None, 35 | description='A console PIP GUI!', 36 | license="MIT", 37 | author='Nitan Alexandru Marcel', 38 | author_email='nitan.marcel@gmail.com', 39 | classifiers=[ 40 | 'Environment :: Console', 41 | 'Development Status :: 4 - Beta', 42 | 'License :: OSI Approved :: MIT License', 43 | 'Operating System :: OS Independent', 44 | 'Programming Language :: Python', 45 | 'Programming Language :: Python :: 3.6', 46 | 'Programming Language :: Python :: 3.7' 47 | ], 48 | keywords='pip, npyscreen, pip-gui', 49 | install_requires=requirements, 50 | entry_points={'console_scripts': ['piptui = piptui.app:main']}, 51 | ) 52 | 53 | 54 | if __name__ == "__main__": 55 | main() 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | pip-wheel-metadata/ 26 | share/python-wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | MANIFEST 31 | 32 | # PyInstaller 33 | # Usually these files are written by a python script from a template 34 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 35 | *.manifest 36 | *.spec 37 | 38 | # Installer logs 39 | pip-log.txt 40 | pip-delete-this-directory.txt 41 | 42 | # Unit test / coverage reports 43 | htmlcov/ 44 | .tox/ 45 | .nox/ 46 | .coverage 47 | .coverage.* 48 | .cache 49 | nosetests.xml 50 | coverage.xml 51 | *.cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # celery beat schedule file 95 | celerybeat-schedule 96 | 97 | # SageMath parsed files 98 | *.sage.py 99 | 100 | # Environments 101 | .env 102 | .venv 103 | env/ 104 | venv/ 105 | ENV/ 106 | env.bak/ 107 | venv.bak/ 108 | 109 | # Spyder project settings 110 | .spyderproject 111 | .spyproject 112 | 113 | # Rope project settings 114 | .ropeproject 115 | 116 | # mkdocs documentation 117 | /site 118 | 119 | # mypy 120 | .mypy_cache/ 121 | .dmypy.json 122 | dmypy.json 123 | 124 | # Pyre type checker 125 | .pyre/ 126 | 127 | .idea/ 128 | 129 | -------------------------------------------------------------------------------- /piptui/mainForm.py: -------------------------------------------------------------------------------- 1 | import curses 2 | 3 | from .custom.customForm import FormBaseNewHinted 4 | from .infoBox import InfoBox 5 | from .logBox import LogBox 6 | from .pkgBox import PkgBox 7 | from .searchBox import QueryPypi, SearchBox 8 | 9 | 10 | class MainForm(FormBaseNewHinted): 11 | def create(self): 12 | exit_handlers = {'^Q': lambda x: exit(0), 13 | 155: lambda x: exit(0), 14 | curses.ascii.ESC: lambda x: exit(0)} 15 | action_handlers = { 16 | '^A': lambda x: self.parentApp.switchForm("INSTALL"), 17 | '^R': lambda x: self.parentApp.switchForm("UNINSTALL"), 18 | '^U': lambda x: self.parentApp.switchForm("UPDATE"), 19 | '^O': lambda x: self.PkgBoxObj.open_in_browser(), 20 | '^H': lambda x: self.PkgBoxObj.open_homepage(), 21 | '^D': lambda x: self.show_details_form(), 22 | '^S': lambda x: self.SearchBoxObj.edit(), 23 | curses.KEY_F1: lambda x: self.parentApp.switchForm("SETTINGS")} 24 | 25 | self.add_handlers(exit_handlers) 26 | self.add_handlers(action_handlers) 27 | self.add_event_hander( 28 | 'event_package_select', 29 | self.event_package_select) 30 | 31 | y, x = self.useable_space() 32 | 33 | self.PkgBoxObj = self.add(PkgBox, 34 | name="Packages", 35 | value=0, relx=1, max_width=x // 5, rely=0, 36 | max_height=-5) 37 | 38 | self.InfoBoxObj = self.add( 39 | InfoBox, name='Description', rely=0, relx=( 40 | x // 5) + 1, max_height=-5) 41 | self.SearchBoxObj = self.add( 42 | SearchBox, 43 | name="Search", 44 | value=0, 45 | relx=1, 46 | max_width=x // 5, 47 | max_height=-5, 48 | ) 49 | 50 | self.LogBoxObj = self.add(LogBox, name='Pip Log', relx=( 51 | x // 5) + 1, rely=-7, max_height=0) 52 | 53 | self.action_controller = QueryPypi 54 | 55 | self.display() 56 | self.PkgBoxObj.create() 57 | self.InfoBoxObj.create() 58 | 59 | def event_package_select(self, *args, **kwargs): 60 | self.InfoBoxObj.display_info() 61 | 62 | def show_details_form(self, *args, **kwargs): 63 | self.parentApp.getForm("PKG_INFO").update() 64 | self.parentApp.switchForm("PKG_INFO") 65 | -------------------------------------------------------------------------------- /piptui/custom/customTheme.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import json 3 | from piptui.settings import get_config_value, THEME_FOLDER 4 | 5 | from npyscreen import ThemeManager 6 | 7 | TRANSPARENT_KEYS = [ 8 | 'GREEN_ON_DEFAULT', 9 | 'BLUE_ON_DEFAULT', 10 | 'BLACK_ON_DEFAULT', 11 | 'CYAN_ON_DEFAULT', 12 | 'MAGENTA_ON_DEFAULT', 13 | 'YELLOW_ON_DEFAULT', 14 | 'WHITE_ON_DEFAULT', 15 | 'RED_ON_DEFAULT'] 16 | DEFAULT_COLORS = json.load(open(THEME_FOLDER + get_config_value('theme'))) 17 | HAS_TRANSPARENT = False 18 | 19 | 20 | HAS_TRANSPARENT = any(color in DEFAULT_COLORS.values() 21 | for color in TRANSPARENT_KEYS) 22 | 23 | 24 | class PipTuiTheme(ThemeManager): 25 | 26 | default_colors = { 27 | 'DEFAULT': 'WHITE_BLACK', 28 | 'FORMDEFAULT': 'WHITE_BLACK', 29 | 'NO_EDIT': 'BLUE_BLACK', 30 | 'STANDOUT': 'CYAN_BLACK', 31 | 'CURSOR': 'WHITE_BLACK', 32 | 'CURSOR_INVERSE': 'BLACK_WHITE', 33 | 'LABEL': 'GREEN_BLACK', 34 | 'LABELBOLD': 'WHITE_BLACK', 35 | 'CONTROL': 'YELLOW_BLACK', 36 | 'IMPORTANT': 'GREEN_BLACK', 37 | 'SAFE': 'GREEN_BLACK', 38 | 'WARNING': 'YELLOW_BLACK', 39 | 'DANGER': 'RED_BLACK', 40 | 'CRITICAL': 'BLACK_RED', 41 | 'GOOD': 'GREEN_BLACK', 42 | 'GOODHL': 'GREEN_BLACK', 43 | 'VERYGOOD': 'BLACK_GREEN', 44 | 'CAUTION': 'YELLOW_BLACK', 45 | 'CAUTIONHL': 'BLACK_YELLOW', 46 | } 47 | 48 | _colors_to_define = ( 49 | ('BLACK_WHITE', curses.COLOR_BLACK, curses.COLOR_WHITE), 50 | ('BLUE_BLACK', curses.COLOR_BLUE, curses.COLOR_BLACK), 51 | ('CYAN_BLACK', curses.COLOR_CYAN, curses.COLOR_BLACK), 52 | ('GREEN_BLACK', curses.COLOR_GREEN, curses.COLOR_BLACK), 53 | ('MAGENTA_BLACK', curses.COLOR_MAGENTA, curses.COLOR_BLACK), 54 | ('RED_BLACK', curses.COLOR_RED, curses.COLOR_BLACK), 55 | ('YELLOW_BLACK', curses.COLOR_YELLOW, curses.COLOR_BLACK), 56 | ('BLACK_RED', curses.COLOR_BLACK, curses.COLOR_RED), 57 | ('BLACK_GREEN', curses.COLOR_BLACK, curses.COLOR_GREEN), 58 | ('BLACK_YELLOW', curses.COLOR_BLACK, curses.COLOR_YELLOW), 59 | ('BLUE_WHITE', curses.COLOR_BLUE, curses.COLOR_WHITE), 60 | ('CYAN_WHITE', curses.COLOR_CYAN, curses.COLOR_WHITE), 61 | ('GREEN_WHITE', curses.COLOR_GREEN, curses.COLOR_WHITE), 62 | ('MAGENTA_WHITE', curses.COLOR_MAGENTA, curses.COLOR_WHITE), 63 | ('RED_WHITE', curses.COLOR_RED, curses.COLOR_WHITE), 64 | ('YELLOW_WHITE', curses.COLOR_YELLOW, curses.COLOR_WHITE) 65 | ) 66 | 67 | if HAS_TRANSPARENT is True: 68 | _colors_to_define = _colors_to_define + (('BLACK_ON_DEFAULT', curses.COLOR_BLACK, -1), 69 | ('WHITE_ON_DEFAULT', curses.COLOR_WHITE, -1), 70 | ('BLUE_ON_DEFAULT', curses.COLOR_BLUE, -1), 71 | ('CYAN_ON_DEFAULT', curses.COLOR_CYAN, -1), 72 | ('GREEN_ON_DEFAULT', curses.COLOR_GREEN, -1), 73 | ('MAGENTA_ON_DEFAULT', curses.COLOR_MAGENTA, -1), 74 | ('RED_ON_DEFAULT', curses.COLOR_RED, -1), 75 | ('YELLOW_ON_DEFAULT', curses.COLOR_YELLOW, -1)) 76 | default_colors = DEFAULT_COLORS or default_colors 77 | 78 | def __init__(self, *args, **kwargs): 79 | if HAS_TRANSPARENT is True: 80 | curses.use_default_colors() 81 | super(PipTuiTheme, self).__init__(*args, **kwargs) 82 | -------------------------------------------------------------------------------- /piptui/pkgInfoForm.py: -------------------------------------------------------------------------------- 1 | import curses 2 | import site 3 | 4 | import requests 5 | from . import INSTALLED 6 | from .run_threaded import threaded 7 | from npyscreen import TitleMultiLine, TitleText 8 | from .custom.customForm import ActionFormHinted 9 | 10 | 11 | class PkgInfoForm(ActionFormHinted): 12 | def create(self): 13 | self.name = 'Package Info!' 14 | exit_handlers = { 15 | '^Q': lambda x: self.parentApp.switchForm('MAIN'), 16 | 155: lambda x: self.parentApp.switchForm('MAIN'), 17 | curses.ascii.ESC: lambda x: self.parentApp.switchForm('MAIN')} 18 | 19 | self.add_handlers(exit_handlers) 20 | self.add_handlers( 21 | {'^A': lambda x: self.parentApp.switchForm('INSTALL_VERSION')}) 22 | 23 | self.pkg_name = self.add( 24 | TitleText, 25 | name='Package Name:', 26 | editable=False) 27 | self.version = self.add(TitleText, name='Version:', editable=False) 28 | self.size = self.add(TitleText, name='Size:', editable=False) 29 | self.summary = self.add(TitleText, name='Summary:', editable=False) 30 | self.home_page = self.add(TitleText, name='Home-Page:', editable=False) 31 | self.author = self.add(TitleText, name='Author:', editable=False) 32 | self.author_email = self.add( 33 | TitleText, name='Author Email:', editable=False) 34 | self.license = self.add(TitleText, name='License:', editable=False) 35 | self.location = self.add(TitleText, name='Location:', editable=False) 36 | self._new_line = self.add(TitleText, name='\n', editable=False) 37 | self.releases = self.add( 38 | TitleMultiLine, 39 | name='Releases:', 40 | max_height=5, 41 | scroll_exit=True) 42 | 43 | @threaded 44 | def update(self): 45 | current_selection = self.parentApp.MainForm.PkgBoxObj.value 46 | pkg = self.parentApp.MainForm.PkgBoxObj.values[current_selection].split()[ 47 | 0] 48 | request = requests.get('https://pypi.org/pypi/{}/json'.format(pkg)) 49 | if request.status_code == 200: 50 | js_data = request.json() 51 | info = js_data.get('info') 52 | releases = js_data.get('releases', '') 53 | 54 | self.pkg_name.value = pkg 55 | self.version.value = info.get('version', '') 56 | self.size.value = self.get_max_release_size(releases) 57 | self.summary.value = info.get('summary', '') 58 | self.home_page.value = info.get('home_page', '') 59 | self.author.value = info.get('author', '') 60 | self.author_email.value = info.get('author_email', '') 61 | 62 | self.license.value = info.get('license', '') 63 | for package in INSTALLED: 64 | if pkg == package.split()[0]: 65 | self.location.value = site.getusersitepackages() 66 | self.releases.values = [release for release in releases] 67 | self.releases.value = 0 68 | else: 69 | print('\a') 70 | self.display() 71 | 72 | def on_ok(self): 73 | self.parentApp.switchForm('MAIN') 74 | 75 | def on_cancel(self): 76 | self.parentApp.switchForm('MAIN') 77 | 78 | def get_max_release_size(self, data): 79 | latest = max(data.items()) 80 | lst = latest[1] 81 | size = lst[0].get('size') 82 | 83 | power = 2**10 84 | n = 0 85 | units = { 86 | 0: '', 87 | 1: 'kilobytes', 88 | 2: 'megabytes', 89 | 3: 'gigabytes', 90 | 4: 'terabytes'} 91 | while size > power: 92 | size /= power 93 | n += 1 94 | final_size = round(size, 2), units[n] 95 | return str(final_size[0]) + ' ' + str(final_size[1]) 96 | -------------------------------------------------------------------------------- /piptui/actionForms.py: -------------------------------------------------------------------------------- 1 | import curses 2 | 3 | from npyscreen import ActionForm 4 | 5 | 6 | class UninstallForm(ActionForm): 7 | CANCEL_BUTTON_BR_OFFSET = (2, 45) 8 | OK_BUTTON_BR_OFFSET = (2, 5) 9 | CANCEL_BUTTON_TEXT = 'Cancel' 10 | OK_BUTTON_TEXT = 'Uninstall' 11 | 12 | def create(self): 13 | y, x = self.parentApp.MainForm.useable_space() 14 | self.show_atx = x // 2 - 10 15 | self.show_aty = y // 2 - 5 16 | self.name = "Uninstall Package?" 17 | 18 | exit_handlers = {'^Q': lambda: exit(0), 19 | 155: lambda: exit(0), 20 | curses.ascii.ESC: lambda: exit(0)} 21 | self.add_handlers(exit_handlers) 22 | self.display() 23 | 24 | def on_ok(self): 25 | current_selection = self.parentApp.MainForm.PkgBoxObj.value 26 | pkg = self.parentApp.MainForm.PkgBoxObj.values[current_selection].split()[ 27 | 0] 28 | self.parentApp.MainForm.LogBoxObj.uninstall_pkg(pkg, current_selection) 29 | self.parentApp.switchForm('MAIN') 30 | 31 | def on_cancel(self): 32 | self.parentApp.switchForm('MAIN') 33 | 34 | 35 | class UpdateForm(ActionForm): 36 | CANCEL_BUTTON_BR_OFFSET = (2, 45) 37 | OK_BUTTON_BR_OFFSET = (2, 5) 38 | CANCEL_BUTTON_TEXT = 'Cancel' 39 | OK_BUTTON_TEXT = 'Update' 40 | 41 | def create(self): 42 | y, x = self.parentApp.MainForm.useable_space() 43 | self.show_atx = x // 2 - 10 44 | self.show_aty = y // 2 - 5 45 | self.name = "Update Package?" 46 | 47 | exit_handlers = {'^Q': lambda: exit(0), 48 | 155: lambda: exit(0), 49 | curses.ascii.ESC: lambda: exit(0)} 50 | self.add_handlers(exit_handlers) 51 | self.display() 52 | 53 | def on_ok(self): 54 | current_selection = self.parentApp.MainForm.PkgBoxObj.value 55 | pkg = self.parentApp.MainForm.PkgBoxObj.values[current_selection].split()[ 56 | 0] 57 | self.parentApp.MainForm.LogBoxObj.update_pkg(pkg, current_selection) 58 | self.parentApp.switchForm('MAIN') 59 | 60 | def on_cancel(self): 61 | self.parentApp.switchForm('MAIN') 62 | 63 | 64 | class InstallForm(ActionForm): 65 | CANCEL_BUTTON_BR_OFFSET = (2, 45) 66 | OK_BUTTON_BR_OFFSET = (2, 5) 67 | CANCEL_BUTTON_TEXT = 'Cancel' 68 | OK_BUTTON_TEXT = 'Install' 69 | 70 | def create(self): 71 | y, x = self.parentApp.MainForm.useable_space() 72 | self.show_atx = x // 2 - 10 73 | self.show_aty = y // 2 - 5 74 | self.name = "Install Release?" 75 | 76 | exit_handlers = {'^Q': lambda: exit(0), 77 | 155: lambda: exit(0), 78 | curses.ascii.ESC: lambda: exit(0)} 79 | self.add_handlers(exit_handlers) 80 | self.display() 81 | 82 | def on_ok(self): 83 | current_selection = self.parentApp.MainForm.PkgBoxObj.value 84 | pkg = self.parentApp.MainForm.PkgBoxObj.values[current_selection].split()[ 85 | 0] 86 | self.parentApp.MainForm.LogBoxObj.install_pkg(pkg, current_selection) 87 | self.parentApp.switchForm('MAIN') 88 | 89 | def on_cancel(self): 90 | self.parentApp.switchForm('MAIN') 91 | 92 | 93 | class InstallVersionForm(ActionForm): 94 | CANCEL_BUTTON_BR_OFFSET = (2, 45) 95 | OK_BUTTON_BR_OFFSET = (2, 5) 96 | CANCEL_BUTTON_TEXT = 'Cancel' 97 | OK_BUTTON_TEXT = 'Install' 98 | 99 | def create(self): 100 | y, x = self.parentApp.MainForm.useable_space() 101 | self.show_atx = x // 2 - 10 102 | self.show_aty = y // 2 - 5 103 | self.name = "Install Package?" 104 | 105 | exit_handlers = {'^Q': lambda: exit(0), 106 | 155: lambda: exit(0), 107 | curses.ascii.ESC: lambda: exit(0)} 108 | self.add_handlers(exit_handlers) 109 | self.display() 110 | 111 | def on_ok(self): 112 | current_selection = self.parentApp.PkgInfoForm.releases.value 113 | version = self.parentApp.PkgInfoForm.releases.values[current_selection].split()[ 114 | 0] 115 | pkg = self.parentApp.PkgInfoForm.pkg_name.value + '==' + version 116 | self.parentApp.MainForm.LogBoxObj.install_pkg(pkg, current_selection) 117 | self.parentApp.switchForm('MAIN') 118 | 119 | def on_cancel(self): 120 | self.parentApp.switchForm('PKG_INFO') 121 | 122 | 123 | class UpdateAppForm(ActionForm): 124 | CANCEL_BUTTON_BR_OFFSET = (2, 45) 125 | OK_BUTTON_BR_OFFSET = (2, 5) 126 | CANCEL_BUTTON_TEXT = 'Cancel' 127 | OK_BUTTON_TEXT = 'Update' 128 | 129 | def create(self): 130 | y, x = self.parentApp.MainForm.useable_space() 131 | self.show_atx = x // 2 - 10 132 | self.show_aty = y // 2 - 5 133 | self.name = "Update App?" 134 | 135 | exit_handlers = {'^Q': lambda: exit(0), 136 | 155: lambda: exit(0), 137 | curses.ascii.ESC: lambda: exit(0)} 138 | self.add_handlers(exit_handlers) 139 | self.display() 140 | 141 | def on_ok(self): 142 | self.parentApp.MainForm.LogBoxObj.update_app() 143 | self.parentApp.switchForm('MAIN') 144 | 145 | def on_cancel(self): 146 | self.parentApp.switchForm('MAIN') 147 | -------------------------------------------------------------------------------- /piptui/logBox.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import sys 3 | 4 | import requests 5 | from npyscreen import BoxTitle 6 | 7 | from .run_threaded import threaded 8 | from.settings import get_config_value 9 | 10 | 11 | class LogBox(BoxTitle): 12 | @threaded 13 | def uninstall_pkg(self, pkg, current_selection): 14 | uninstalled = False 15 | self.refresh() 16 | proc = subprocess.Popen([sys.executable, 17 | '-m', 18 | 'pip', 19 | 'uninstall', 20 | pkg, 21 | '-y'], 22 | stdout=subprocess.PIPE, 23 | stderr=subprocess.PIPE) 24 | while True: 25 | stdout = proc.stdout.readline() 26 | stderr = proc.stderr.readline() 27 | if stdout: 28 | self.values.append(stdout.decode().strip()) 29 | self.update() 30 | uninstalled = True 31 | if stderr: 32 | self.values.append(stderr.decode().strip()) 33 | self.update() 34 | uninstalled = False 35 | if not proc.poll() and not stdout and not stderr: 36 | if uninstalled is True: 37 | self.parent.parentApp.MainForm.PkgBoxObj.values.pop( 38 | current_selection) 39 | try: 40 | self.uninstall_pkg.__thread__.join() 41 | except BaseException: 42 | pass 43 | break 44 | 45 | @threaded 46 | def update_pkg(self, pkg, current_selection): 47 | updated = False 48 | self.refresh() 49 | cmd = [sys.executable, 50 | '-m', 51 | 'pip', 52 | 'install', 53 | pkg, 54 | '--upgrade'] 55 | if get_config_value('use_user_arg', as_bool=True) is True: 56 | cmd.append('--user') 57 | proc = subprocess.Popen(cmd, 58 | stdout=subprocess.PIPE, 59 | stderr=subprocess.PIPE) 60 | while True: 61 | stdout = proc.stdout.readline() 62 | stderr = proc.stderr.readline() 63 | if stdout: 64 | self.values.append(stdout.decode().strip()) 65 | self.update() 66 | updated = True 67 | if stderr: 68 | self.values.append(stderr.decode().strip()) 69 | self.update() 70 | updated = False 71 | if not proc.poll() and not stdout and not stderr: 72 | if updated is True: 73 | data = requests.get( 74 | 'https://pypi.org/pypi/{}/json'.format(pkg)) 75 | if data.status_code == 200: 76 | json_data = data.json() 77 | releases = json_data.get("releases") 78 | if releases: 79 | self.parent.parentApp.MainForm.PkgBoxObj.values[current_selection] = pkg + " " + max( 80 | releases) 81 | try: 82 | self.uninstall_pkg.__thread__.join() 83 | except BaseException: 84 | pass 85 | break 86 | 87 | @threaded 88 | def install_pkg(self, pkg, current_selection): 89 | global INSTALLED 90 | self.refresh() 91 | cmd = [sys.executable, 92 | '-m', 93 | 'pip', 94 | 'install', 95 | pkg, 96 | ] 97 | if get_config_value('use_user_arg', as_bool=True) is True: 98 | cmd.append('--user') 99 | proc = subprocess.Popen(cmd, 100 | stdout=subprocess.PIPE, 101 | stderr=subprocess.PIPE) 102 | while True: 103 | stdout = proc.stdout.readline() 104 | stderr = proc.stderr.readline() 105 | if stdout: 106 | self.values.append(stdout.decode().strip()) 107 | self.update() 108 | if stderr: 109 | self.values.append(stderr.decode().strip()) 110 | self.update() 111 | if not proc.poll() and not stdout and not stderr: 112 | try: 113 | self.uninstall_pkg.__thread__.join() 114 | except BaseException: 115 | pass 116 | break 117 | 118 | def update_app(self): 119 | self.refresh() 120 | release = self.parent.parentApp.release 121 | cmd = [sys.executable, 122 | '-m', 123 | 'pip', 124 | 'install', 125 | 'PipTUI==' + release] 126 | if get_config_value('use_user_arg', as_bool=True) is True: 127 | cmd.append('--user') 128 | proc = subprocess.Popen(cmd, 129 | stdout=subprocess.PIPE, 130 | stderr=subprocess.PIPE) 131 | while True: 132 | stdout = proc.stdout.readline() 133 | stderr = proc.stderr.readline() 134 | if stdout: 135 | self.values.append(stdout.decode().strip()) 136 | self.update() 137 | if stderr: 138 | self.values.append(stderr.decode().strip()) 139 | self.update() 140 | if not proc.poll() and not stdout and not stderr: 141 | try: 142 | self.uninstall_pkg.__thread__.join() 143 | except BaseException: 144 | pass 145 | break 146 | 147 | def refresh(self): 148 | self.values = [] 149 | self.update() 150 | --------------------------------------------------------------------------------