aboutsummarylogtreecommitdiffstats
path: root/pac
blob: 39d4b9b0a5e712decc7ff962d18371618ea332fb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python3
# coding: utf-8

"""
pac - wrapper around pacaur to mimic yaourts search feature

Usage:
    pac <searchpattern>
"""

__author__ = 'Ricardo Band'
__copyright__ = 'Copyright 2017, Ricardo band'
__credits__ = ['Ricardo Band']
__license__ = 'MIT'
__version__ = '1.1.0'
__maintainer__ = 'Ricardo Band'
__email__ = 'email@ricardo.band'

import sys
from subprocess import call, run, PIPE


def search(search_term: str) -> list:
    """
    Search for the given terms using pacaur and return the results. The output of pacaur looks like this:

    $ pacaur -Ss android
    extra/gvfs-mtp 1.30.3-1 (gnome) [installed]
        Virtual filesystem implementation for GIO (MTP backend; Android, media player)
    community/android-file-transfer 3.0-2
        Android MTP client with minimalistic UI
    aur/android-studio 2.2.3.0-1 (626, 22.50) [installed]
        The official Android IDE (Stable branch)
    aur/android-ndk r13b-1 (252, 3.70)
        Android C/C++ developer kit

    A result consists of 2 lines. The second one is the package description. The first one goes like this (paranthesis
    means optional):
    repo/package_name version [package_group] [installed_state] [votes]

    - repo is the name of the repository and can be any string configured in /etc/pacman.conf like 'core', 'extra', 'aur',
    'myrepo'
    - package name is the identifiing string of the package
    - version can be any string but will most likely be something like 1.2.3-2
    - package group is a string in brackets
    - if the package is already installed the line has the string '[installed]' in it
    - if the repo is the aur the line will have the votes which look like '(252, 3.70)'

    the only parts tha is interesting here is the package name because we need that to install the packages afterwards.
    We still put everything in a dict to make the output more colorful.

    """
    result: List[dict] = []
    out: str = run(['pacaur', '-Ss', search_term], stdout=PIPE).stdout
    entry: dict = {}

    for line in out.decode().split('\n'):
        if line.startswith(' '):
            entry['description'] = line.strip()
            result.append(entry)
            entry = {}
        elif line != '':
            l = line.split('/')
            entry['repo'] = l[0]
            l = l[1].split(' ')
            entry['package'] = l[0]
            entry['version'] = l[1]
            entry['votes'] = None
            entry['group'] = None
            entry['installed'] = None
            if len(l) > 2 and l[2].startswith('('):
                if l[2].endswith(')'):
                    entry['group'] = l[2]
                else:
                    entry['votes'] = '%s %s' % (l[2], l[3])
            if len(l) > 3 and l[3].startswith('('):
                if l[3].endswith(')'):
                    entry['group'] = l[3]
                else:
                    entry['votes'] = '%s %s' % (l[3], l[4])
            if '[installed]' in l:
                entry['installed'] = '[installed]'
    return result


def present(entries: list):
    """
    Present the list of entries with numbers in front of it. For each package it displays 2 lines like this:

    1   extra/gvfs-mtp 1.30.3-1 (gnome) [installed]
        Virtual filesystem implementation for GIO (MTP backend; Android, media player)
    2   community/android-file-transfer 3.0-2
        Android MTP client with minimalistic UI
    3   aur/android-studio 2.2.3.0-1 (626, 22.50) [installed]
        The official Android IDE (Stable branch)
    4   aur/android-ndk r13b-1 (252, 3.70)
        Android C/C++ developer kit

    After that, a prompt will be printed but this is the task for another function.
    """
    CEND: str = '\33[0m'
    CBOLD: str = '\33[1m'
    CBLACK: str = '\33[30m'
    CVIOLET: str = '\33[35m'
    CGREEN2: str = '\33[92m'
    CYELLOW2: str = '\33[93m'
    CVIOLET2: str = '\33[95m'
    CYELLOWBG: str = '\33[43m'
    CYELLOWBG2: str = '\33[103m'

    for index, entry in enumerate(entries):
        print(f"{CBLACK}{CYELLOWBG}{index + 1}{CEND} {CVIOLET2}{entry['repo']}/{CEND}{CBOLD}{entry['package']}{CEND} {CGREEN2}{entry['version']}{CEND}", end='')
        if entry['group']:
            print(f" {entry['group']}", end='')
        if entry['installed']:
            print(f" {CBLACK}{CYELLOWBG2}{entry['installed']}{CEND}", end='')
        if entry['votes']:
            print(f" {CBLACK}{CYELLOWBG2}{entry['votes']}{CEND}", end='')
        print(f"\n    {entry['description']}")
    print(f"{CYELLOW2}==>{CEND} {CBOLD}Enter n° of packages to be installed (ex: 1 2 3 or 1-3){CEND}")
    print(f"{CYELLOW2}==>{CEND} {CBOLD}-------------------------------------------------------{CEND}")


def parse_num(numbers: str):
    """
    Takes a string like '1 2 3 6-8' and finds out which numbers the user wants. In this case 1,2,3,6,7,8.
    It can detect single digits or ranges seperated by space. A range must be given as from-to, where 'from' is always
    smaller then 'to'.
    """
    result = []
    for n in numbers.split(' '):
        if '-' in n:
            start, end = n.split('-')
            if not (start.isdecimal() and end.isdecimal()):
                sys.exit(f'{start} or {end} is not a number')
            # TODO: I'm pretty sure this can be optimized
            for i in list(range(int(start) - 1, int(end))):
                result.append(i)
        elif n.isdecimal():
            result.append(int(n) - 1)
        else:
            sys.exit(f'{n} is not a number')
    return result


def install(numbers: list, packages: list):
    """
    Gets the chosen packages and concatinates them. Then executes the pacaur command with the packages to install them.
    """
    names = [packages[i]['package'] for i in numbers]
    call(f"pacaur -S {' '.join(names)}", shell=True)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        if sys.argv[1][:2] in ['-S', '-Q', '-R']:
            call(f"pacaur {' '.join(sys.argv[1:])}", shell=True)
        else:
            entries = search(' '.join(sys.argv[1:]))
            present(entries)
            numbers = input('\33[93m==>\33[0m ')
            numbers = parse_num(numbers)
            install(numbers, entries)
    else:
        call('pacaur -Syu', shell=True)