├── 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 | 
5 | 
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 |
--------------------------------------------------------------------------------