summaryrefslogtreecommitdiff
blob: 9424ad670733ace37f654ba7ee45925955751741 (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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
# Gentoo Linux Bash Shell Command Completion
# Common functions to use in other completions
#
# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License, v2 or later

# Retrieve PORTDIR/PORTDIR_OVERLAY location.
#
# In order of highest to lowest priority:
# /etc/portage/repos.conf{,/*.conf}
# /usr/share/portage/config/repos.conf
# /etc/portage/make.conf
# /etc/make.conf
# /usr/share/portage/config/make.globals
#
# The first two files are in repos.conf format and must be parsed for the
# variable "location".  The rest are make.conf style and are simply sourced
# for PORTDIR and PORTDIR_OVERLAY.  While repos.conf overrides any value of
# PORTDIR set in make.conf, PORTDIR_OVERLAY is incremental (combined across
# available sources).
# 
# This would be a hell of a lot simpler if we used portageq, but also about
# 500 times slower.
_portdir() {
    local mainreponame mainrepopath overlayname overlaypath

    if [[ -e @GENTOO_PORTAGE_EPREFIX@/usr/share/portage/config/repos.conf ]]; then
        if [[ ${1} == -o ]]; then
            for overlayname in $(_parsereposconf -l); do
                overlaypath+=($(_parsereposconf ${overlayname} location))
            done

            source @GENTOO_PORTAGE_EPREFIX@/etc/make.conf 2>/dev/null
            source @GENTOO_PORTAGE_EPREFIX@/etc/portage/make.conf 2>/dev/null
        
            overlaypath+=(${PORTDIR_OVERLAY})

            # strip out duplicates
            overlaypath=($(printf "%s\n" "${overlaypath[@]}" | sort -u))

            echo "${overlaypath[@]}"
        else
            mainreponame=$(_parsereposconf DEFAULT main-repo)
            mainrepopath=$(_parsereposconf ${mainreponame} location)

            echo "${mainrepopath}"
        fi
    else
        source @GENTOO_PORTAGE_EPREFIX@/usr/share/portage/config/make.globals 2>/dev/null
        source @GENTOO_PORTAGE_EPREFIX@/etc/make.conf 2>/dev/null
        source @GENTOO_PORTAGE_EPREFIX@/etc/portage/make.conf 2>/dev/null

        echo "${PORTDIR}"

        if [[ ${1} == -o ]]; then 
            echo "${PORTDIR_OVERLAY}"
        fi   
    fi
}

# _parsereposconf [-l] <repo> <variable>
#   -l lists available repos
_parsereposconf() {
    local f insection line section v value var

    for f in @GENTOO_PORTAGE_EPREFIX@/usr/share/portage/config/repos.conf \
        @GENTOO_PORTAGE_EPREFIX@/etc/portage/repos.conf \
        @GENTOO_PORTAGE_EPREFIX@/etc/portage/repos.conf/*.conf; do

        [[ -f ${f} ]] || continue
        insection=0
            
        while read -r line; do
            # skip comments and blank lines
            [[ -z ${line} || ${line} == '#'* ]] && continue

            if [[ ${insection} == 1 && ${line} == '['*']' ]]; then
                # End of the section we were interested in so stop
                secname+=(${line//[(\[|\])]/}) # record name for -l
                break
            elif [[ ${line} == '['*']' ]]; then
                # Entering a new section, check if it's the one we want
                section=${line//[(\[|\])]/}
                [[ ${section} == "${1}" ]] && insection=1
                secname+=(${section}) # record name for -l
            elif [[ ${insection} == 1 ]]; then
                # We're in the section we want, grab the values
                var=${line%%=*}
                var=${var// /}
                value=${line#*=}
                value=${value# }
                [[ ${var} == ${2} ]] && v=${value}
            fi
            continue
        done < "${f}"
    done

    if [[ ${1} == -l ]]; then
        echo "${secname[@]}"
    else
        echo "${v}"
    fi
}

# like _pkgname but completes on package names only (no category)
_pkgname_only()
{
    local i pd
    local cur="$1"
    shift
    local dir="$@"

    COMPREPLY=($(compgen -W "$(\
        for pd in $dir ; do \
            builtin cd ${pd}; \
            for i in *-*/${cur}*; do \
                [[ -d ${i} ]] && { local x=${i##*/} ; echo ${x%-[0-9]*}; } \
            done ; \
        done)" -- ${cur}))
}

#
# This function completes package names.
#
# usage: pkgname <mode> <current-directory>
#
# Where mode is one of:
#   -A  Search all available packages (except for those in the overlays)
#   -I  Only search the installed packages
#
# TODO: Look at breaking this function out and making it a "universal"
#       category/package name completion function.
#
_pkgname()
{
    local mode cur portdir only
    mode="$1"
    cur="$2"
    portdir=$(_portdir -o)
    # Ignore '=' at the beginning of the current completion
    [[ ${cur:1:1} == "=" ]] && cur=${cur:2}
    [[ ${cur:0:1} == "=" ]] && cur=${cur:1}
    case $mode in
    -I)
        # Complete either the category or the complete package name
        if [[ $cur == */* ]]; then
        COMPREPLY=($(builtin cd @GENTOO_PORTAGE_EPREFIX@/var/db/pkg; compgen -W "$(compgen -G "$cur*" )" -- $cur))
        else
        COMPREPLY=($(builtin cd @GENTOO_PORTAGE_EPREFIX@/var/db/pkg; compgen -W "$(compgen -G "$cur*" -S /)" -- $cur))
        fi
        # We may just have finished completing the category.
        # Make sure there isn't anything more to complete now.
        if [[ ${#COMPREPLY[@]} == 1 ]]; then
        COMPREPLY=($(builtin cd @GENTOO_PORTAGE_EPREFIX@/var/db/pkg; compgen -W "$(compgen -G "$COMPREPLY*")" -- $cur))
        fi

            if [[ -z "${COMPREPLY}" ]] ; then
                only=1
                _pkgname_only ${cur} @GENTOO_PORTAGE_EPREFIX@/var/db/pkg
            fi
        ;;
    -A)
        # Complete either the category or the complete package name
        if [[ $cur == */* ]]; then
            # Once the category has been completed, it's safe to use ${portdir}
            # to continue completion.
                local ww=$(\
                    for pd in ${portdir} ; do
                        builtin cd ${pd};
                        compgen -W "$(compgen -G "${cur}*")" -- "${cur}" ;
                    done)
                COMPREPLY=($(\
                    for x in ${ww}; do echo $x; done|sort -u
                        ))
            # When we've completed most of the name, also display the version for
            # possible completion.
            if [[ ${#COMPREPLY[@]} -le 1 || ${cur:${#cur}-1:1} == "-" ]] \
                && [[ ${cur} != */ ]]; then
                    # Use the portage cache to complete specific versions from
                    COMPREPLY=(${COMPREPLY[@]} $(
                        for pd in ${portdir}; do
                            if [[ -d ${pd}/metadata/md5-cache ]]; then
                                builtin cd ${pd}/metadata/md5-cache
                                compgen -W "$(compgen -G "${cur}*")" -- "${cur}"
                            elif [[ -d ${pd}/metadata/cache ]]; then
                                builtin cd ${pd}/metadata/cache
                                compgen -W "$(compgen -G "${cur}*")" -- "${cur}"
                            fi
                        done
                    ))
            fi
        else
            # 1. Collect all the categories among ${portdir}
            local ww=$(\
                for pd in ${portdir}; do
                    builtin cd ${pd};
                    compgen -X "!@(*-*|virtual)" -S '/' -G "$cur*";
                done)

            # 2. Now ugly hack to delete duplicate categories
            local w x
            for x in ${ww} ; do w="${x}\n${w}"; done
            local words=$(echo -e ${w} | sort -u)

            COMPREPLY=($(compgen -W "$words" -- $cur))

            if [[ ${#COMPREPLY[@]} == 1 ]]; then
                COMPREPLY=($(compgen -W "$(
                    for pd in ${portdir}; do
                        if [[ -d ${pd}/metadata/md5-cache ]]; then
                            builtin cd ${pd}/metadata/md5-cache
                            compgen -G "$COMPREPLY*"
                        elif [[ -d ${pd}/metadata/cache ]]; then
                            builtin cd ${pd}/metadata/cache
                            compgen -G "$COMPREPLY*"
                        fi
                    done
                )" -- $cur))
            fi
        fi

            if [[ -z "${COMPREPLY}" ]] ; then
                only=1
                _pkgname_only ${cur} ${portdir}
            fi
        ;;
    *)
        # Somebody screwed up! :-)
        ;;
    esac
    # 'equery' wants an '=' in front of specific package versions.
    # Add it if there is only one selected package and it isn't there already.
    if [[ ${#COMPREPLY[@]} == 1 && ${COMP_WORDS[COMP_CWORD]:0:1} != "=" ]]
    then
        [[ -z "${only}" ]] && COMPREPLY=("="$COMPREPLY)
    fi
}

#
# This is an helper function for completion of  "-o <list>" / "--option=<list>"
# kind of command lines options.
#
# Usage: _list_compgen <current> <sep> <item1>[<sep><item2> ...]
# - <current>: what we have so far on the command line
# - <sep>: the separator character used in lists
# - <itemN>: a valid item
# Returns: the function outputs each possible completion (one per line),
# and returns 0. Typical usage is COMPREPLY=($(_list_compgen ...)).
#
# Note: items must not contain the <sep> character (no backslash escaping has
# been implemented).
#
_list_compgen()
{
    # Read the three parameters.
    local current="${1}" ; shift
    local sep="${1}" ; shift
    local items="${*}"

    # This is the maximum number of "<current><sep><other_item>" possible
    # completions that should be listed in case <current> is a valid list.
    # Setting it to a negative value means "no bound" (always list everything).
    # Setting it to 0 means "never list anything" (only suggest <sep>).
    # Setting it to a positive value N means "list up to N possible items, and
    # only suggest <sep> if there are more".
    # It is probably not worth a parameter, thus it will defaults to my
    # prefered setting (1) if not already defined in the environment.
    local max_others_number=${max_others_number:-1}

    # Save IFS. The <sep> character will be used instead in the following.
    local saved_IFS="${IFS}"
    IFS="${sep}"

    # Split the current items list in two parts:
    # - current_item is the last one (maybe partial or even empty)
    # - prefix_item are items are the previous ones
    local current_item="${current##*${sep}}"
    local prefix_items="${current%${current_item}}"

    # Iterate through valid items to recognize those that are:
    # - partial matches of the <current_item>
    # - already used in the list prefix
    # - not used in the list prefix, and not an exact match of <current_item>
    # Also check whether the <current_item> is exactly a valid one.
    local matching_items
    local other_items
    local exact_match
    local my_item
    for my_item in ${items} ; do
        if [[ "${sep}${prefix_items}${sep}" == *"${sep}${my_item}${sep}"* ]] ; then
            # The item has already been used in the list prefix: ignore it.
            continue
        elif [[ "${my_item}" == "${current_item}" ]] ; then
            # The item _exactly_ matches the <current_item>: that means that we
            # will have to suggest some more items to add behind.
            exact_match=1
        elif [[ "${my_item}" == "${current_item}"* ]] ; then
            # The item matches the <current_item>: it will be a possible
            # completion. It will also be a possible additional item in case of
            # exact match.
            matching_items="${matching_items}${sep}${my_item}"
            other_items="${other_items}${sep}${my_item}"
        else
            # The item neither matches the <current_item> nor has been already
            # used: it will only be a possible additional item in case of exact
            # match.
            other_items="${other_items}${sep}${my_item}"
        fi
    done
    matching_items="${matching_items#${sep}}"
    other_items="${other_items#${sep}}"

    # Takes care of the case where <current_item> is not exactly valid but
    # there is only one matching item: force this completion, and handle it
    # just as an exact match.
    if [[ -z "${exact_match}" ]] \
    && [[ "${matching_items}" != *"${sep}"* ]] ; then
        exact_match=1
        current="${current%${current_item}}${matching_items}"
        current_item="${matching_items}"
        matching_items=""
        other_items="${sep}${other_items}${sep}"
        other_items="${other_items/${sep}${current_item}${sep}/${sep}}"
        other_items="${other_items#${sep}}"
        other_items="${other_items%${sep}}"
    fi

    # List all possible completions. They are stored in an array.
    # XXX: maybe if should be COMPREPLY directly? (with no output at the end)
    local my_compreply=()
    local i=0
    if [[ -n "${exact_match}" ]] ; then
        # Found an exact match? Then add "<current>".
        my_compreply[${i}]="${current}"
        let i++
    fi
    if [[ -n "${matching_items}" ]] ; then
        # Found some matching items?
        # Then add "<prefix_items><matching_item>".
        for my_item in ${matching_items} ; do
            my_compreply[${i}]="${prefix_items}${my_item}"
            let i++
        done
    fi
    if [[ -n "${exact_match}" ]] \
    && [[ -n "${other_items}" ]] ; then
        # Found an exact match and some other possible items remain?
        # First, count them:
        local count_others=0
        for my_item in ${other_items} ; do
            let count_others++
        done
        # Then decide how to behave depending on the max_others_number setting:
        if (( max_others_number < 0 )) \
        || (( count_others <= max_others_number )) ; then
            # List the possible "<current><sep><other_item>" completions.
            for my_item in ${other_items} ; do
                my_compreply[${i}]="${current}${sep}${my_item}"
                let i++
            done
        else # Only suggest adding the <sep> character.
            my_compreply[${i}]="${current}${sep}"
            let i++
        fi
    fi

    # Restore IFS.
    IFS="${saved_IFS}"

    # Output the array of possible completions and returns.
    local j=0
    while (( i > j )) ; do
        echo ${my_compreply[$j]}
        let j++
    done
    return 0
}

#
# Helper routine for the subcommand 'meta' of 'equery'
# (Used two times, by _equery and _epkginfo, therefore in an extra function)
#
_equery_meta()
{
    local cur="$1"

    case $cur in
        -*)
            COMPREPLY=($(compgen -W "--help -h --description -d --herd -H --keywords -k --maintainer -m --useflags -u --upstream -U --xml -x" -- $cur))
            ;;
        *)
            _pkgname -A $cur
            ;;
    esac
}