Fix output to be sorted using portage.versions.vercmp (bug 409449).
[gentoolkit.git] / bin / euse
1 #!/bin/bash
2 # Disable globbing because "-*" and such is valid as a use flag. 
3 set -f
4
5 # $Header$
6
7 # bash replacement for the original euse by Arun Bhanu
8 # Author: Marius Mauch <genone@gentoo.org>
9 #         Jared Hancock (Signigicant rewrite for package.use support)
10 # Licensed under the GPL v2
11
12 PROGRAM_NAME=euse
13 VERSION="svn"
14
15 EPREFIX=${EPREFIX:-$(portageq envvar EPREFIX)}
16 ETC="${EPREFIX}/etc"
17 USR_SHARE_PORTAGE="${EPREFIX}/usr/share/portage"
18
19 # define error functions so they can be used immediately
20 fatal() {
21         echo -e "ERROR: ${*}"
22         set +f
23         exit 1
24 }
25
26 error() {
27         echo -e "ERROR: ${*}"
28 }
29
30 warn() {
31         echo -e "WARNING: ${*}"
32 }
33
34 # /etc/make.conf can now exist in /etc/portage/make.conf, prefer it over
35 # /etc/make.conf for changes. Since this will only be used for modifying
36 # the USE variable, we need to make sure the one we pick is the one with
37 # the USE variable defined.
38 if [[ -n $(grep '^USE="' "${ETC}/portage/make.conf" 2>/dev/null) ]]; then
39         MAKE_CONF_PATH="${ETC}/portage/make.conf"
40 elif [[ -e "${ETC}/make.conf" ]]; then
41         MAKE_CONF_PATH="${ETC}/make.conf"
42 else
43         fatal "make.conf does not exist"
44 fi
45 MAKE_CONF_BACKUP_PATH="${MAKE_CONF_PATH}.euse_backup"
46
47 # /etc/make.globals has been moved to /usr/share/portage/config/make.globals
48 if [ -e "${USR_SHARE_PORTAGE}/config/make.globals" ]; then
49         MAKE_GLOBALS_PATH="${USR_SHARE_PORTAGE}/config/make.globals"
50 else
51         MAKE_GLOBALS_PATH="${ETC}/make.globals"
52 fi
53
54 # /etc/make.profile or /etc/portage/make.profile, if /etc/make.profile exists, it will be used
55 if [ -e "${ETC}/make.profile" ]; then
56         MAKE_PROFILE_PATH="${ETC}/make.profile"
57 elif [ -e "${ETC}/portage/make.profile" ]; then
58         MAKE_PROFILE_PATH="${ETC}/portage/make.profile"
59 else
60         fatal "make.profile does not exist"
61 fi
62 PACKAGE_USE_PATH=${ETC}/portage/package.use
63
64 [ -z "${MODE:-}" ] && MODE="showhelp"           # available operation modes: showhelp, showversion, showdesc, showflags, modify
65
66 parse_arguments() {
67         if [ -z "${1}" ]; then
68                 return
69         fi
70         while [ -n "${1}" ]; do
71                 case "${1}" in
72                         -h | --help)           MODE="showhelp";;
73                         -V | -v | --version)   MODE="showversion";;
74                         -i | --info)           MODE="showdesc";;
75                         -I | --info-installed) MODE="showinstdesc";;
76                         -l | --local)          SCOPE="local";;
77                         -g | --global)         SCOPE="global";;
78                         -a | --active)         MODE="showflags";;
79                         -E | --enable)         MODE="modify"; ACTION="add";;
80                         -D | --disable)        MODE="modify"; ACTION="remove";;
81                         -P | --prune | -R | --remove)
82                                                MODE="modify"; ACTION="prune";;
83                         -p | --package)        MODE="modify"; shift; PACKAGE=${1}; SCOPE="local";;
84                         -*)
85                                 echo "ERROR: unknown option ${1} specified."
86                                 echo
87                                 MODE="showhelp"
88                                 ;;
89                         "%active")
90                                 get_portageuseflags
91                                 ARGUMENTS="${ARGUMENTS:-} ${ACTIVE_FLAGS[9]}"
92                                 ;;
93                         *)
94                                 ARGUMENTS="${ARGUMENTS:-} ${1}"
95                                 ;;
96                 esac
97                 shift
98         done
99 }
100
101 get_real_path() {
102         set -P
103         cd "$1"
104         pwd
105         cd "$OLDPWD"
106         set +P
107 }
108
109 # Function: check_sanity {{{
110 # Performs some basic system sanity checks
111 check_sanity() {
112         # file permission tests
113         local descdir
114         local make_defaults
115         local make_conf
116
117         [[ ! -d "${MAKE_PROFILE_PATH}" || ! -r "${MAKE_PROFILE_PATH}" ]] && error "${MAKE_PROFILE_PATH} is not readable"
118         # 
119         for make_conf in $(get_all_make_conf); do
120                 [ ! -r "${make_conf}" ] && fatal "${make_conf} is not readable"
121         done
122
123         descdir="$(get_portdir)/profiles"
124         
125         [ ! -r "${MAKE_GLOBALS_PATH}" ] && fatal "${MAKE_GLOBALS_PATH} is not readable"
126         [ -z "$(get_portdir)" ] && fatal "\$PORTDIR couldn't be determined"
127         [ ! -d "${descdir}" ] && fatal "${descdir} does not exist or is not a directory"
128         [ ! -r "${descdir}/use.desc" ] && fatal "${descdir}/use.desc is not readable"
129         [ ! -r "${descdir}/use.local.desc" ] && fatal "${descdir}/use.local.desc is not readable"
130
131         for make_defaults in $(get_all_make_defaults); do
132                 [ ! -r "$make_defaults" ]  && fatal "$_make_defaults is not readable"
133         done
134         [ "${MODE}" == "modify" -a ! -w "${MAKE_CONF_PATH}" ] && fatal ""${MAKE_CONF_PATH}" is not writable"
135         [ "${MODE}" == "modify" -a -s "${PACKAGE_USE_PATH}" -a ! -w "${PACKAGE_USE_PATH}" ] && fatal ""${PACKAGE_USE_PATH}" is not writable"
136         return 0
137 } # }}}
138
139 showhelp() {
140 cat << HELP
141 ${PROGRAM_NAME} (${VERSION})
142
143 Syntax: ${PROGRAM_NAME} <option> [suboptions] [useflaglist]
144
145 Options: -h, --help           - show this message
146          -V, --version        - show version information
147          -i, --info           - show descriptions for the given useflags
148          -I, --info-installed - show descriptions for the given useflags and
149                                 their current impact on the installed system
150          -g, --global         - show only global use flags (suboption)
151          -l, --local          - show only local use flags (suboption)
152          -a, --active         - show currently active useflags and their origin
153          -E, --enable         - enable the given useflags
154          -D, --disable        - disable the given useflags
155          -R, --remove         - remove all references to the given flags from
156                                 make.conf and package.use to revert to default 
157                                 settings
158          -P, --prune          - alias for --remove
159          -p, --package        - used with -E, -D, and -R to apply to a
160                                 specific package only
161
162 Notes: ${PROGRAM_NAME} currently works for global flags defined
163        in make.globals, make.defaults, make.conf, use.force, and use.mask 
164        and local flags defined in package.use and individual package ebuilds.
165        It might have issues with cascaded profiles. If multiple options are 
166        specified only the last one will be used.
167 HELP
168 }
169
170 showversion() {
171 cat << VER
172 ${PROGRAM_NAME} (${VERSION})
173 Written by Marius Mauch
174
175 Copyright (C) 2004-2009 Gentoo Foundation, Inc.
176 This is free software; see the source for copying conditions.
177 VER
178 }
179
180 # Function: reduce_incrementals {{{
181 # remove duplicate flags from the given list in both positive and negative forms
182 # (but unlike portage always keep the last value even if it's negative)
183 # Otherwise the status flags could be incorrect if a flag appers multiple times in
184 # one location (like make.conf).
185 # Using python here as bash sucks for list handling.
186 # NOTE: bash isn't actually that bad at handling lists -- sh is. This may be
187 #       worth another look to avoid calling python unnecessariy. Or we could
188 #       just write the whole thing in python. ;)
189 reduce_incrementals() {
190         echo $@ | python -c "from __future__ import print_function;import sys
191 r=[]
192 for x in sys.stdin.read().split():
193         if x[0] == '-' and x[1:] in r:
194                 r.remove(x[1:])
195                 r.append(x)
196         elif x[0] != '-' and '-'+x in r:
197                 r.remove('-'+x)
198                 r.append(x)
199         elif x == '-*': r = ['-*']
200         elif x not in r: r.append(x)
201 print(' '.join(r))" 
202 } # }}}
203
204 # Function: reduce_incrementals_trump {{{
205 # Similar to reduce_incrementals but negative flags trump positive
206 # flags, regardless of which follows which
207 reduce_incrementals_trump() {
208         echo $@ | python -c "from __future__ import print_function;import sys
209 r=[]
210 for x in sys.stdin.read().split():
211         if x[0] == '-' and x[1:] in r:
212                 r.remove(x[1:])
213                 r.append(x)
214         elif x == '-*': r = ['-*']
215         elif x not in r and not '-'+x in r: r.append(x)
216 print(' '.join(r))" 
217 } # }}}
218
219 # Function: reduce_package_use {{{
220 # Similar to reduce_incrementals except converts lines from package atoms
221 # in /etc/portage/package.use files to lines of "pkg {[-]flag}*"
222 #
223 # Arguments:
224 # * - Lines of package atom followed by flags
225 #     (app-editors/vim flag1 flag2 -flag3)
226 reduce_package_use() {
227         echo "${@}" | python -c "from __future__ import print_function;import sys,re
228 h={}; getflags=re.compile(r'(-?[\w*-]+)')
229 for x in sys.stdin.read().split('\n'):
230         if not x: continue
231         parts = x.lstrip().split(' ',1)
232         if len(parts)==1: continue
233         pkg=parts[0]
234         flags = getflags.findall(parts[1])
235         if not pkg in h: h[pkg]=[]
236         r=h[pkg]
237         for x in flags:
238                 if x[0] == '-' and x[1:] in r:
239                         r.remove(x[1:])
240                         r.append(x)
241                 elif x[0] != '-' and '-'+x in r:
242                         r.remove('-'+x)
243                         r.append(x)
244                 elif x == '-*': r = h[pkg] = ['-*']
245                 elif x not in r:
246                         r.append(x)
247 print('\n'.join(['%s %s' % (pkg,' '.join(flgs)) for pkg,flgs in h.items() if len(flgs)]))"
248 } # }}}
249
250 # Function: get_useflags {{{
251 # Creates a bash array ACTIVE_FLAGS that contains the global use flags, 
252 # indexed by origin: 0: environment, 1: make.conf, 2: make.defaults, 
253 # 3: make.globals, and local use flags, indexed by origin: 4: package.use, 
254 # 5: ebuild IUSE, 6: use.mask, 7: use.force, 
255 # 9: flags indicated active by emerge --info (get_portageuseflags)
256 get_useflags() {
257         set +f
258         if [[ -z ${ACTIVE_FLAGS[4]} && ( $SCOPE == "local" || -z $SCOPE ) ]]; then
259                 # Parse through /etc/portage/package.use
260                 if [[ -d ${PACKAGE_USE_PATH} ]]; then
261                         ACTIVE_FLAGS[4]="$( cat ${PACKAGE_USE_PATH}/* \
262                                 | sed -re "s/ *#.*$//g" -e "s/^ *$//g" )"
263                 elif [[ -e ${PACKAGE_USE_PATH} ]]; then
264                         # JWM, 23/12/2009: I edited this following line but I'm not sure if it's 100% correct.
265                         ACTIVE_FLAGS[4]="$( sed -re "s/ *#.*$//g" -e "s/^ *$//g" \
266                                 ${PACKAGE_USE_PATH})"
267                 fi
268                 # Simplify ACTIVE_FLAGS[4] to be lines of pkg {[-]flag}*
269                 ACTIVE_FLAGS[4]="$(reduce_package_use "${ACTIVE_FLAGS[4]}")"
270                 #
271                 # ACTIVE_FLAGS[5] reserved for USE flags defined in ebuilds and
272                 # is generated/maintained in the get_useflaglist_ebuild() function
273         fi
274
275         # only calculate once as calling emerge is painfully slow
276         [ -n "${USE_FLAGS_CALCULATED}" ] && return
277
278         # backup portdir so get_portdir() doesn't give false results later
279         portdir_backup="${PORTDIR}"
280         
281         ACTIVE_FLAGS[0]="$(reduce_incrementals "${USE}")"
282         USE=""
283         for x in $(get_all_make_conf); do
284                 source "${x}"
285                 ACTIVE_FLAGS[1]="$(reduce_incrementals "${ACTIVE_FLAGS[1]}" "${USE}")"
286         done
287         USE=""
288         for x in $(get_all_make_defaults); do
289                 source "${x}"
290                 ACTIVE_FLAGS[2]="${ACTIVE_FLAGS[2]} ${USE}"
291         done
292         ACTIVE_FLAGS[2]="$(reduce_incrementals "${ACTIVE_FLAGS[2]}")"
293         USE=""
294         source "${MAKE_GLOBALS_PATH}"
295         ACTIVE_FLAGS[3]="$(reduce_incrementals "${USE}")"
296
297         # restore saved env variables
298         USE="${ACTIVE_FLAGS[0]}"
299         PORTDIR="${portdir_backup}"
300
301         #
302         # Traverse through use.mask and use.force (0.5s)
303         # Flip signs of use.mask (it's interpreted oppositely), 
304         ACTIVE_FLAGS[6]=$(reduce_incrementals_trump \
305                 $(cat $(traverse_profile "use.mask") | sed -re "/^#.*$/{d}") \
306                         | sed -re "s/(^| )-[^ ]*//g" -e "s/(^| )([a-z0-9])/ -\2/g")
307         ACTIVE_FLAGS[7]=$(reduce_incrementals \
308                 $(cat $(traverse_profile "use.force") \
309                         | sed -re "/^#.*$/ {d}"))
310
311         USE_FLAGS_CALCULATED=1
312         set -f
313 } # }}}
314
315 # Function: get_portageuseflags # {{{
316 # Fetch USE flags reported active by Portage
317 get_portageuseflags() {
318         # only calculate once as calling emerge is painfully slow
319         [ -n "${_PORTAGE_USE_FLAGS_CALCULATED}" ] && return
320         # get the currently active USE flags as seen by portage, this has to be after
321         # restoring USE or portage won't see the original environment
322         # Bug 181309, emerge may complain if EMERGE_DEFAULT_OPTS="--ask" is set
323         ACTIVE_FLAGS[9]="$(portageq envvar USE)" #'
324         _PORTAGE_USE_FLAGS_CALCULATED=1
325 } # }}}
326
327 # Function: get_useflaglist {{{
328 # Get the list of all known USE flags by reading use.desc and/or 
329 # use.local.desc (depending on the value of $SCOPE). Also searches any 
330 # registered overlays after searching the main portage tree first.
331 # Use flags visible in both the main tree and overlays are trumped by
332 # the main tree. Overlays are indicated by brackets [xxx] at the 
333 # beginning of the description. 
334 #
335 # Returns:
336 # (written to stdout) Sorted, unique list of system-wide USE flags and
337 # descriptions. Flags defined in overlays have the overlay in brackets
338 # prepended to the descriptions.
339 #
340 # Environment:
341 # SCOPE - [local|global] constrain search to local (use.local.desc) or
342 #                 global (use.desc)
343 get_useflaglist() {
344         local descdir
345         local overlay
346         for profiledir in ${ALL_PORTDIRS[@]}; do
347                 descdir="${profiledir}/profiles"
348                 if [[ -z ${SCOPE} || ${SCOPE} == "global" ]]; then
349                         [[ ! -s "${descdir}/use.desc" ]] && continue
350                         egrep "^[^# ]+ +-" "${descdir}/use.desc" 
351                 fi
352                 if [[ -z ${SCOPE} || ${SCOPE} == "local" ]]; then
353                         [[ ! -s "${descdir}/use.local.desc" ]] && continue
354                         egrep "^[^# :]+:[^ ]+ +-" "${descdir}/use.local.desc" \
355                                 | cut -d: -f 2 
356                 fi
357         done | cut -d " "  -f1 | sort --field=":" --key=1,1 --unique
358 } # }}}
359
360 # Function: get_useflaglist_ebuild {{{
361 # Builds USE flag information for specified package atom into 
362 # ACTIVE_FLAGS[5]. For the atom, the versions available are found, and 
363 # for each, the corresponding SLOT, IUSE are stored along with which 
364 # overlay the ebuild lives in. Considering that the pieces of information 
365 # may be required in any order or any subsets, it is intended for the 
366 # function to cache the information and it be retrieved from 
367 # ACTIVE_FLAGS[5]. So the format of ACTIVE_FLAGS[5] is newline-separated 
368 # list of:
369 #
370 # category/packge;version;SLOT;IUSE;overlay
371 #
372 # Arguments:
373 # $1 - Package atom to lookup (app-editor/vim)
374 #
375 # Returns:
376 # Nothing significant
377 #
378 # Environment:
379 # PORTDIR - Root of portage tree
380 # ACTIVE_FLAGS - Array of current use flag info
381 #
382 get_useflaglist_ebuild() {
383         local known=$(echo "${ACTIVE_FLAGS[5]}" | egrep "^${1}")
384         if [[ -n $known ]]; then
385                 # No need to recache
386                 return
387         fi
388         local pkg=$(echo ${1} | cut -d/ -f2)
389         declare append
390         for portdir in ${ALL_PORTDIRS[@]}; do
391                 if [[ -s "${portdir}/profiles/repo_name" ]]; then
392                         overlay="$(cat "${portdir}/profiles/repo_name")"
393                 else
394                         # XXX: Portage uses "x-<basename of the overlay>
395                         overlay="x-$(basename "${portdir}")"
396                 fi
397                 # Open the ebuild file and retrieve defined USE flags
398                 [[ ! -d "$portdir/${1}" ]] && continue
399                 if [[ ! -d "$portdir/metadata/cache" ]]; then
400                         echo "!!! Metadata cache not found. You need to run " >&2
401                         echo "!!! 'egencache --repo=$overlay --update'" >&2
402                         echo "!!! to generate metadata for your overlays" >&2
403                         return 1
404                 fi
405                 append=$(set +f; ls $portdir/metadata/cache/${1}-* \
406                         | egrep "${1}-[0-9.]+" \
407                         | sed -e "s:$portdir/metadata/cache/${1}-::g" \
408                         | while read -d $'\n' version; do
409                                 IFS=$'\n'
410                                 if [[ ! -e "$portdir/metadata/cache/${1}-$version" ]]; then
411                                         # Repo does not have this particular package
412                                         continue
413                                 fi
414                                 iuse=$(head -11 "$portdir/metadata/cache/${1}-$version"|tail -1)
415                                 slot=$(head -3 "$portdir/metadata/cache/${1}-$version"|tail -1)
416                                 echo "${1};${version};${slot};${iuse};${overlay}"
417                         done
418                 )
419                 if [[ -z ${ACTIVE_FLAGS[5]} ]]; then ACTIVE_FLAGS[5]="$append"
420                 else ACTIVE_FLAGS[5]="${ACTIVE_FLAGS[5]}"$'\n'"$append"
421                 fi
422         done
423 } # }}}
424
425 # get all make.conf files that exist on the system
426 get_all_make_conf() {
427         # At least one of the files exists or we would not have made it this far
428         for x in ${ETC}/make.conf ${ETC}/portage/make.conf; do
429                 [ -e "${x}" ] && echo "${x}"
430         done
431 }
432 # Function: traverse_profile {{{
433 # General method of collecting the contents of a profile
434 # component by traversing through the cascading profile
435 #
436 # Arguments:
437 # $1 - Filename (make.profile)
438 # [$2] - Current directory (unspecified means to start at the top)
439 traverse_profile() {
440         local curdir
441         local parent
442         local rvalue=""
443         
444         curdir="${2:-$(get_real_path ${MAKE_PROFILE_PATH})}"
445         
446         if [[ -f "${curdir}/parent" ]]; then
447                 for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do
448                         # Bug 231394, handle parent path being absolute
449                         if [[ ${parent:0:1} == "/" ]]; then
450                                 pdir="$(get_real_path ${parent})"
451                         else
452                                 pdir="$(get_real_path ${curdir}/${parent})"
453                         fi
454                         rvalue="${rvalue} $(traverse_profile ${1} ${pdir})"
455                 done
456         fi
457         [[ -f "${curdir}/${1}" ]] && rvalue="${rvalue} ${curdir}/${1}"
458
459         echo "${rvalue}"
460 } # }}}
461
462 # Function: get_all_make_defaults {{{
463 # Det all make.defaults by traversing the cascaded profile directories
464 get_all_make_defaults() {
465         if [[ -z ${MAKE_DEFAULTS:-} ]]; then
466                 MAKE_DEFAULTS=$(traverse_profile "make.defaults")
467         fi
468         echo $MAKE_DEFAULTS
469 } # }}}
470 MAKE_DEFAULTS=$(get_all_make_defaults)
471
472 # Function: get_flagstatus_helper # {{{
473 # Little helper function to get the status of a given flag in one of the 
474 # ACTIVE_FLAGS elements. 
475 #
476 # Returns:
477 # (Written to STDOUT) Flag active status (+/-) or default string given
478 # in argument 4 or an empty space
479
480 # Arguments:
481 # 1 - flag to test
482 # 2 - index of ACTIVE_FLAGS
483 # 3 - echo value for positive (and as lowercase for negative) test result
484 # 4 - (optional) echo value for "missing" test result, defaults to blank
485 get_flagstatus_helper() {
486         if [[ -z ${flags} ]]; then 
487                 local flags=${ACTIVE_FLAGS[${2}]}
488         fi
489         local flag=$(echo " $flags " | grep -Eo " [+-]?${1} ")
490         if [[ ${flag:1:1} == "-" ]]; then
491                 echo -e -n "${3}" | tr [:upper:]+ [:lower:]-
492         elif [[ -n ${flag} ]]; then
493                 echo -e -n "${3}"
494         else
495                 echo -n "${4:- }"
496         fi
497 } # }}}
498
499 # Function: get_flagstatus_helper_pkg # {{{
500 # Entry to get_flagstatus_helper for packages which will fetch use
501 # flags set in package.use for the package and pass them on to
502 # get_flagstatus_helper. Also correcly handles lines in package.use
503 # specified for individual package versions
504 get_flagstatus_helper_pkg() {
505         if [[ -z ${2} ]]; then
506                 echo -ne "${4:- }"
507                 return
508         elif [[ -z ${flags} ]]; then
509                 # If no atoms are matchers (start with >,<,=, then they all match
510                 atoms=($2)
511                 if [[ -z "${atoms[@]/[<>=]*/}" ]]; then
512                         atoms=($(
513                                 echo "${atoms[@]}" | python -c "
514 from __future__ import print_function;import portage.dep as dep, sys
515 print(' '.join(dep.match_to_list('$5-$6',sys.stdin.read().split())))"))
516                 fi
517                 flags=$(for atom in ${atoms[@]}; do
518                         [[ -z $atom ]] && continue
519                         echo "${ACTIVE_FLAGS[4]}" | \
520                                 grep "^ *$atom" | cut -d\  -f2-
521                 done)
522         fi
523         if [[ -z ${5} || -z ${flags} ]]; then
524                 echo -e -n "${4:- }"
525                 return
526         fi
527         get_flagstatus_helper "$@"
528 } # }}}
529
530 # Function: get_flagstatus_helper_ebuild {{{
531 # get_flagstatus_helper replacement for packages to fetch ebuild USE flag
532 # activation status.
533 #
534 # Returns:
535 # (Written to STDOUT) Flag active status (+/-) or default string given
536 # in argument 4 or an empty space. If USE flag is not defined in the list
537 # of flags (2), an '!' is written
538 #
539 # Arguments:
540 # 1 - flag to test
541 # 2 - IUSE line from ebuild file
542 # 3 - echo value for positive (and as lowercase for negative) test result
543 # 4 - (optional) echo value for "missing" test result, defaults to blank space
544 get_flagstatus_helper_ebuild() {
545         local flags=$(echo $2 | cut -d\" -f2) 
546         local flag=$(echo " $flags " | grep -Eo " [+-]?$1 ")
547         if [[ ${flag:1:1} == "+" ]]; then
548                 echo -en "${3}"
549         elif [[ ${flag:1:1} == "-" ]]; then
550                 echo -en "${3}" | tr [:upper:]+ [:lower:]-
551         elif [[ -z $flag ]]; then
552                 echo -en "!"
553         else
554                 echo -en "${4:- }"
555         fi
556 } # }}}
557
558 # Function: get_flagstatus {{{
559 # Prints a status string for the given flag, each column indicating the presence
560 # for portage, in the environment, in make.conf, in make.defaults, in 
561 # make.globals, and in use.force and flipped in use.mask.
562 #
563 # Arguments:
564 # 1 - use flag for which to retrieve status
565 #
566 # Returns:
567 # 0 (True) if flag is active, 1 (False) if not active
568 #
569 # Outputs:
570 # Full positive value would be "[+ECDGFm] ", full negative value would be [-ecdgfM],
571 # full missing value would be "[-      ] " (portage only sees present or not present)
572 get_flagstatus() {
573         get_useflags
574
575         local E=$(get_flagstatus_helper "${1}" 0 "E")
576         local C=$(get_flagstatus_helper "${1}" 1 "C")
577         local D=$(get_flagstatus_helper "${1}" 2 "D")
578         local G=$(get_flagstatus_helper "${1}" 3 "G")
579         local M=$(get_flagstatus_helper "${1}" 6 "M")
580         local F=$(get_flagstatus_helper "${1}" 7 "F")
581         # Use flags are disabled by default
582         ACTIVE="-"
583         #
584         # Use flag precedence is defined (at least) at:
585         # http://www.gentoo.org/doc/en/handbook/handbook-x86.xml?part=2&chap=2
586         for location in "E" "C" "D" "G" "F" "M"; do
587                 if [[ ${!location} == $location ]]; then
588                         ACTIVE="+"
589                         break
590                 elif [[ ${!location} != " " ]]; then 
591                         ACTIVE="-"
592                         break
593                 fi
594         done
595         echo -n "[${ACTIVE:--}$E$C$D$G$F$M] "
596         [[ $ACTIVE == "+" ]]; return $?
597 } # }}}
598
599 # Function: get_flagstatus_pkg {{{
600 # Outputs local flag status information for a specific package and perhaps
601 # specific package version.
602 #
603 # Arguments:
604 # 1 - flag (gtk)
605 # 2 - package atom (www-client/elinks)
606 # 3 - +/- whether flag is enabled by global configuration files
607 # 4 - (Optional) version of package to evaluate (empty means all versions)
608 #
609 # Outputs:
610 # Flag status for package.use and ebuild, slot and version, and overlay
611 # the version lives is if not PORTDIR
612 #
613 # Full positive would be "[+PB]", full negative would be "[-pb]", and full 
614 # missing would be "[?  ]", question because the sign will default to the 
615 # sign of the global status of the flag
616 get_flagstatus_pkg() {
617         #
618         # Pre-cache the use flags declared in ebuilds first.
619         # This is required as calling it inside a $() seems to
620         # prevent caching of results into $ACTIVE_FLAGS array
621         get_useflaglist_ebuild ${2} 
622         #
623         # Get list of ebuilds available for this package. The 
624         # get_useflaglist_ebuild() function stores them in $ACTIVE_FLAGS[5]
625         # with the package name leading the lines. The other information
626         # is in the same line, semi-colon (;) separated. The fields are
627         # package;version;SLOT;IUSE;overlay
628         #
629         # Fetch package atoms and flags from package.use only once
630         local atoms=$(echo "${ACTIVE_FLAGS[4]}" | \
631                         grep -Eo "^ *[<>]?=?${2}(-[0-9rp._*-]*)?")
632         local IFS=$'\n'; for pkgline in $(echo "${ACTIVE_FLAGS[5]}" | grep "^$2" \
633                         | sort -t \; -k2,2 -V); do
634                 OIFS=$IFS; IFS=";"; INFO=($pkgline); IFS=$OIFS;
635                 local version=${INFO[1]}
636                 [[ -n $4 && $4 != $version ]] && continue
637                 local slot=${INFO[2]}
638                 local iuse=${INFO[3]}
639                 if [[ $slot == '0' || $slot == "" ]]; then 
640                         slot=""; 
641                 else
642                         slot="($(echo ${slot} | cut -d\" -f2)) "
643                 fi
644                 local overlay=${INFO[4]}
645                 [[ -n $overlay ]] && overlay="[$overlay]"
646                 # 
647                 # Fetch enabled status for this version
648                 local P=$(get_flagstatus_helper_pkg "${1}" "${atoms}" "P" "" "${2}" "${version}")
649                 local B=$(get_flagstatus_helper_ebuild "${1}" "${iuse}" "B" "" "${2}" "${version}")
650                 UNUSED=0
651                 ACTIVE=${3}
652                 for location in "P" "B"; do
653                         if [[ ${!location} == $location ]]; then
654                                 ACTIVE="+"
655                         elif [[ ${!location} == "!" ]]; then 
656                                 UNUSED=1
657                                 break
658                         elif [[ ${!location} != " " ]]; then 
659                                 ACTIVE="-"
660                                 break
661                         fi
662                 done
663                 if [[ $UNUSED == 1 ]]; then
664                         echo "              ${slot}${version} ${overlay}"
665                 else
666                         echo "        [${ACTIVE:-${3:- }}$P$B] ${slot}${version} ${overlay}"
667                 fi
668         done
669         echo
670 } # }}}
671
672 # Function: get_portdir {{{
673 # faster replacement to `portageq portdir`
674 #
675 # Outputs:
676 # Location of portage tree root
677 get_portdir() {
678         # Use a subshell so we don't have to protect the variables in
679         # the current scope
680         (
681         if [ -z "${PORTDIR:-}" ]; then
682                 source "${MAKE_GLOBALS_PATH}"
683                 for x in $(get_all_make_defaults); do
684                         source "${x}"
685                 done
686                 for x in $(get_all_make_conf); do
687                         source "${x}"
688                 done
689         fi
690         echo "${PORTDIR}"
691         )
692 } # }}}
693 # This won't change while the script is running, so cache it
694 PORTDIR="$(get_portdir)"
695
696 # Function: get_all_overlays {{{
697 # Outputs list of portage overlays as defined in the PORTDIR_OVERLAY
698 # variable defined in make.conf 
699 get_all_overlays() {
700         # Use a subshell so we don't have to protect the variables in
701         # the current scope
702         ( 
703                 for x in $(get_all_make_conf); do
704                         [[ -r "${x}" ]] && source "${x}"
705                 done
706                 echo ${PORTDIR_OVERLAY}
707         )
708 } # }}}
709 ALL_PORTDIRS=( "$PORTDIR" $(get_all_overlays) )
710
711 # Function: array_contains {{{
712 # PHP-style array_contains function.
713 #
714 # Arguments:
715 # 1 - haystack
716 # 2 - needle
717 #
718 # Returns:
719 # 0 (True) if needle in haystack, null (False) otherwise
720 array_contains() {
721         for i in $1; do [[ $i == $2 ]] && return 0; done
722         return
723 } # }}}
724
725 # Function: showdesc {{{
726 # This function takes a list of use flags and shows the status and 
727 # the description for each one, honoring $SCOPE
728 #
729 # Arguments:
730 # * - USE flags for which to display descriptions. Undefined means to
731 #         display descriptions for all known USE flags
732 #
733 # Environment:
734 # SCOPE - defines whether to output local or global USE flag descriptions
735 #                 Empty means to display both
736 #
737 # Outputs:
738 # (STDOUT) Flag description(s) for given USE flags
739 #
740 showdesc() {
741         local descdir
742         local current_desc
743         local found_one
744         local args
745         
746         args="${*:-*}"
747
748         if [ -z "${SCOPE}" ]; then
749                 SCOPE="global" showdesc ${args}
750                 echo
751                 SCOPE="local" showdesc ${args}
752                 return
753         fi
754         
755         local useflags=( $(echo "$(get_useflaglist)") )
756         
757         [ "${SCOPE}" == "global" ] && echo "global use flags (searching: ${args})"
758         [ "${SCOPE}" == "local" ] && echo "local use flags (searching: ${args})"
759         echo "************************************************************"
760         if [ "${args}" == "*" ]; then
761                 args="${useflags[*]}"
762         fi
763
764         set ${args}
765
766         foundone=0
767         while [[ -n "${1}" ]]; do
768                 if [[ "${SCOPE}" == "global" ]]; then
769                         if array_contains "${useflags[*]}" "$1"; then
770                                 get_flagstatus "${1}"
771                                 # XXX: Handle overlay
772                                 grep -h "^${1}  *-" ${ALL_PORTDIRS[@]/%//profiles/use.desc} 2> /dev/null
773                                 foundone=1
774                         fi
775                 fi
776                 # local flags are a bit more complicated as there can be multiple
777                 # entries per flag and we can't pipe into printf
778                 if [[ "${SCOPE}" == "local" ]]; then
779                         if array_contains "${useflags[*]}" "$1"; then
780                                 foundone=1
781                         fi
782                         # Fetch all the packages data using this flag
783                         infos=$( grep -h ":${1}  *-" ${ALL_PORTDIRS[@]/%//profiles/use.local.desc} 2> /dev/null \
784                                 | sed -re "s/^([^:]+):(${1}) *- *(.+)/\1|\2|\3/g")
785                         OIFS=$IFS; IFS=$'\n'; infos=($infos); IFS=$OIFS;
786                         for line in "${infos[@]}"; do
787                                 OIFS=$IFS; IFS="|"; line=($line); IFS=$OIFS
788                                 pkg=${line[0]} 
789                                 flag=${line[1]}
790                                 desc=${line[2]}
791                                 if get_flagstatus "${flag}"; then
792                                         ACTIVE="+"
793                                 else
794                                         ACTIVE="-"
795                                 fi
796                                 printf "%s\n" "${flag}"
797                                 printf "%s: %s\n" "${pkg}" "${desc}" \
798                                         | fold --width=$((${COLUMNS:-80}-10)) -s | sed -e "s/^/    /g"
799                                 get_flagstatus_pkg "${flag}" "${pkg}" "${ACTIVE}"
800                         done
801                 fi
802                 shift
803         done
804         
805         if [[ ${foundone} == 0 ]]; then
806                 echo "no matching entries found"
807         fi
808 } # }}}
809
810 # Function: showinstdesc {{{
811 # Works like showdesc() but displays only descriptions of which the appropriate
812 # ebuild is installed and prints the name of those packages.
813 #
814 # Arguments:
815 # * - USE flags for which to display descriptions. Undefined means to
816 #         display descriptions for all known USE flags
817 #
818 # Environment:
819 # SCOPE - defines whether to output local or global USE flag descriptions
820 #                 Empty means to display both
821 #
822 # Outputs:
823 # (STDOUT) Flag description(s) for given USE flags along with installed 
824 # packages
825 #
826 showinstdesc() {
827         local descdir
828         local current_desc
829         local args
830         local -i foundone=0
831         local OIFS="$IFS"
832
833         args=("${@:-*}")
834
835         case "${SCOPE}" in
836                 "global") echo "global use flags (searching: ${args[@]})";;
837                  "local") echo "local use flags (searching: ${args[@]})";;
838                        *) SCOPE="global" showinstdesc "${args[@]}"
839                           echo
840                           SCOPE="local" showinstdesc "${args[@]}"
841                           return;;
842         esac
843
844         descdir="$(get_portdir)/profiles"
845         echo "************************************************************"
846
847         if [ "${args}" = "*" ]; then
848                 args="$(get_useflaglist | sort -u)"
849         fi
850
851         set "${args[@]}"
852
853         while [ -n "${1}" ]; do
854                 case "${SCOPE}" in
855                         "global")
856                                 if desc=$(grep "^${1}  *-" "${descdir}/use.desc"); then
857                                         get_flagstatus "${1}"
858                                         echo "$desc"
859                                         # get list of installed packages matching this USE flag.
860                                         IFS=$'\n'
861                                         packages=($(equery -q -C hasuse -i "${1}" | awk '{ print $(NF-1) }' | sort))
862                                         foundone+=${#packages[@]}
863                                         printf "\nInstalled packages matching this USE flag: "
864                                         if [ ${foundone} -gt 0 ]; then
865                                                 echo $'\n'"${packages[*]}"
866                                         else
867                                                 echo "none"
868                                         fi
869                                 fi
870                         ;;
871                         "local")
872                                 # local flags are a bit more complicated as there can be multiple
873                                 # entries per flag and we can't pipe into printf
874                                 IFS=': ' # Use a space instead of a dash because dashes occur in cat/pkg
875                                 while read pkg flag desc; do
876                                         # print name only if package is installed
877                                         # NOTE: If we implement bug #114086 's enhancement we can just use the
878                                         #       exit status of equery instead of a subshell and pipe to wc -l
879                                         # Bug 274472, -e is the default
880                                         if [ $(equery -q -C list -i "${pkg}" | wc -l) -gt 0 ]; then
881                                                 foundone=1
882                                                 IFS="$OIFS"
883                                                 get_flagstatus "${flag}" "${pkg}"
884                                                 IFS=': '
885                                                 printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc#- }"
886                                         fi
887                                 done < <(grep ":${1}  *-" "${descdir}/use.local.desc")
888                         ;;
889                 esac
890                 shift
891         done
892
893         if [ ${foundone} -lt 1 ]; then
894                 echo "no matching entries found"
895         fi
896         IFS="$OIFS"
897 } # }}}
898
899 # Function: showflags {{{
900 # show a list of all currently active flags and where they are activated
901 showflags() {
902         local args
903
904         get_useflags
905
906         args="${*:-*}"
907
908         if [ "${args}" == "*" ]; then
909                 args="$(get_useflaglist | sort -u)"
910         fi
911
912         set ${args}
913         get_portageuseflags
914         
915         while [ -n "${1}" ]; do
916                 if echo " ${ACTIVE_FLAGS[9]} " | grep " ${1} " > /dev/null; then
917                         printf "%-20s" ${1}
918                         get_flagstatus ${1}
919                         echo
920                 fi
921                 if echo " ${ACTIVE_FLAGS[4]} " | egrep -e " -?${1} " > /dev/null; then
922                         for pkg in $( echo "${ACTIVE_FLAGS[4]}" | \
923                                         egrep " -?${1} " | cut -d " " -f 2); do
924                                 printf "%-20s" ${1}
925                                 SCOPE="local" get_flagstatus ${1} "${pkg}"
926                                 printf "(%s)\n" ${pkg}
927                         done;
928                 fi
929                 shift
930         done
931 } # }}}
932
933 # two small helpers to add or remove a flag from a USE string
934 add_flag() {
935         # Remove leading '-' from flag if found
936         local flag=$1
937         [[ ${flag:0:1} == "-" ]] && flag=${1:1}
938
939         if [[ -n $(grep " -${flag} " <<< " ${ACTIVE_FLAGS[6]} ") ]]; then
940                 error "Use flag \"${flag}\" is masked and should not be added" \
941                           "to make.conf."
942                 return 1
943         # Bug #104396 -- Only add use flags defined in use.desc and use.local.desc
944         elif [[ -z $(grep "^${flag}$" <<< "$(get_useflaglist)") ]]; then
945                 error "Use flag \"${flag}\" is not defined in use.desc and should" \
946                           "not be added\nto make.conf."
947                 return 1
948         else
949                 NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE} ${1}"
950         echo "Adding flag \"${1}\" to make.conf" >&2
951         fi
952 }
953
954 remove_flag() {
955         NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE// ${1} / }"
956         echo "Removing flag \"${1}\" from make.conf" >&2
957 }
958
959 # Function: clean_package_use {{{
960 # Simple utility to remove empty files from package.use
961 clean_package_use() {
962 if [[ -d ${PACKAGE_USE_PATH} ]]; then
963         for f in $(find ${PACKAGE_USE_PATH} -size 0); do
964                 echo "Removing empty file ""${f}"""
965                 rm ${f}
966         done;
967 fi
968 } # }}}
969
970 # Function: scrub_use_flag {{{
971 # Utility to remove a use flag from a file in package.use[/]
972
973 # Arguments:
974 # 1 - File
975 # 2 - Use flag
976 #
977 # Environment:
978 # PACKAGE - Package atom for which to remove flag
979 scrub_use_flag() {
980         local atom_re="^[<>]?=?([a-z][\da-z/-]+[a-z])(-[0-9pr._*-]+)?"
981         local filename=${1}
982         # Ignore leading - on flag
983         local flag=${2#*-}
984         local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/")
985         local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?"
986
987         while read line; do
988                 # Skip (preserve) comments on their own lines
989                 if [[ -z $(echo "${line}" | sed -re "s/^ *#.*$//") ]]; then
990                         echo "${line}"
991                 # Detect if requested package is defined on this line
992                 elif [[ -n "${PACKAGE}" ]]; then
993                         if [[ -n $(echo "${line}" | grep -Ee "${pkg_re}") ]]; then      
994                                 # If this is the only (remaining) use flag defined
995                 # for this package, then remove the whole line
996                 if [[ -z $(echo "${line}" | grep -Ee "${pkg_re} *-?${flag} *$") ]]; then
997                                         # Remove flag from this line
998                                         echo "${line}" | sed -re "s/ *-?\b${flag}\b//"
999                                 fi
1000                         else
1001                                 # Passthru
1002                                 echo "${line}"
1003                         fi
1004                 # If line only has this use flag, let it be removed 
1005                 # (used if PACKAGE is not defined -- from pruning)
1006                 elif [[ -n $(echo "${line}" | \
1007                                 egrep "^[^#]*${atom_re}.*-?${flag}") ]]; then
1008                         echo "Removing use flag from ${line}" >&2
1009                         if [[ -z $(echo "${line}" | \
1010                                         grep -Ee "${atom_re} *-?${flag} *$") ]]; then
1011                                 # Remove flag from this line
1012                                 echo "${line}" | sed -re "s/-?\b${flag}\b//"
1013                         fi
1014                 else
1015                         # Passthru
1016                         echo "${line}"
1017                 fi
1018         done > "${filename}.new" < "${filename}" 
1019         mv "${filename}.new" "${filename}"
1020 } # }}}
1021
1022 # Function: modify_package {{{
1023 # Adds and removes USE flags from individual packages by modifying
1024 # files in package.use. It supports package.use both as a file and
1025 # and as a folder. Also handles "enabling" as removing a disabled flag from
1026 # a file, and "disabling" a globally enabled flag by adding a negative to 
1027 # a file. Also warns about unused and unknown flags, and about flags
1028 # already enabled, disabled, or masked.
1029 #
1030 # Arguments:
1031 # * - USE flag(s) to add or remove
1032 #
1033 # Environment:
1034 # PACKAGE - Package for which to add (-E) or remove (-D) the USE
1035 #           flag(s)
1036 modify_package() {
1037         get_useflags
1038
1039         local atom_re="^[<>]?=?([a-z][0-9a-z/-]+[a-z])(-[0-9pr._*-]+)?"
1040         local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/")
1041         local V=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\2/")
1042         local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?"
1043
1044         local all_flags=$(SCOPE= get_useflaglist)
1045         # Shift at top rather than bottom to support 'continue'
1046         set " " ${*}
1047         while [[ -n ${2} ]]; do
1048         shift
1049         local flag=${1}
1050         local method="add"
1051         ACTIVE="-"
1052         #
1053         # Fetch flag ACTIVE status (+,-,null)
1054         get_flagstatus "${flag}" "${pkg}" > /dev/null
1055         GLOBAL_ACTIVE="$ACTIVE"
1056         # XXX: If V is not given, is this necessary? Should it use the version
1057         #      that would be installed by emerge?
1058         get_flagstatus_pkg "${flag}" "${pkg}" "${ACTIVE}" "${V}" > /dev/null
1059         #
1060         # --- Sanity checks
1061         # (1) make sure ${pkg} exists in portdir or an overlay
1062         local exists=1
1063         for portdir in ${ALL_PORTDIRS[@]}; do
1064                 if [[ -d "${portdir}/${pkg}" ]]; then
1065                         exists=0
1066                         break
1067                 fi
1068         done
1069         if [[ $exists == 1 ]]; then
1070                 fatal "Package \"${pkg}\" does not exist"
1071         #
1072         # (2) make sure ${flag} is defined in get_useflaglist
1073         elif ! array_contains "$all_flags" ${flag}; then
1074                 error "USE flag \"${flag}\" does not exist"
1075                 continue
1076                 # Don't bail just because of this, just warn
1077         # (3) make sure use flag is valid for the package
1078         elif [[ -z $(echo "${ACTIVE_FLAGS[5]} " | grep -Ee "^${pkg_re}" \
1079                         | grep -Ee "[; ][+-]?${flag}") ]]; then
1080                 # XXX: Handle version or version wildcard?
1081                 warn "USE flag \"${flag}\" is not used by $PACKAGE"
1082                 # Don't necessarily bail for this, just warn
1083         elif [[ -n "${V}" && -z "$(egrep "<|>|=" <<< "${PACKAGE:0:1}")" ]]; then
1084                 error "Invalid package atom. Did you forget the leading '='?"
1085                 continue
1086         elif [[ -z "${V}" && -n "$(egrep "<|>|=" <<< "${PACKAGE:0:1}")" ]]; then
1087                 error "Invalid package atom. Did you forget the version?"
1088                 continue
1089         fi
1090         # If removing a disabled flag, or adding an enabled one, emit a warning,
1091         # indicating a likely misunderstanding
1092         if [[ "${ACTION}" == "remove" ]]; then
1093                 if [[ "${ACTIVE:-${GLOBAL_ACTIVE}}" == "-" ]]; then
1094                         warn "USE flag \"$flag\" is already disabled for $PACKAGE"
1095                 fi
1096                 flag="-${flag}"
1097         elif [[ "${ACTION}" == "prune" ]]; then
1098                 # Just remove the flag below
1099                 [[ "${ACTIVE}" == "-" ]] && flag="-${flag}"
1100                 method="remove"
1101         elif [[ "${ACTION}" == "add" ]]; then
1102                 if [[ "${ACTIVE:-${GLOBAL_ACTIVE:--}}" == "+" ]]; then
1103                         # XXX: Perhaps look at indicating where it is enabled
1104                         warn "USE flag \"$flag\" is already enabled for $PACKAGE"
1105                 fi
1106         fi
1107         case "${method}" in
1108                 "add")
1109                         local filename
1110                         if [[ -d ${PACKAGE_USE_PATH} ]]; then
1111                                 # Use naming convention of package.use/package
1112                                 filename="${PACKAGE_USE_PATH}/${pkg#*/}"
1113                                 if [[ ! -s "${filename}" ]]; then
1114                                         # Create new file to contain flag
1115                                         echo "${PACKAGE} ${flag}" > "${filename}"
1116                                         echo "Adding \"${PACKAGE}[${flag}]\" use flag to new file ""${filename}"""
1117                                         continue
1118                                 fi
1119                         else    
1120                                 # Add to package.use file instead
1121                                 filename="${PACKAGE_USE_PATH}"
1122                                 # Create as necessary
1123                                 touch "${filename}"
1124                         fi
1125                         # Walk through the file and add the flag manually
1126                         echo "Adding \"${PACKAGE}[${flag}]\" use flag in \"${filename}\""
1127                         local added=0
1128                         while read line; do
1129                                 if [[ -n $(echo "${line}" | egrep -e "^[^#]*${PACKAGE} ") ]]; then
1130                                         echo $(reduce_package_use "${line} ${flag}")
1131                                         added=1
1132                                 else
1133                                         # Passthru
1134                                         echo "${line}"
1135                                 fi
1136                         done < "${filename}" > "${filename}.new"
1137                         mv "${filename}.new" "${filename}"
1138                         if [[ ${added} -eq 0 ]]; then
1139                                 echo "${PACKAGE} ${flag}" >> "${filename}"
1140                         fi
1141                         ;;
1142                 "remove")
1143                         local filename
1144                         if [[ -d ${PACKAGE_USE_PATH} ]]; then
1145                                 # Scan for file containing named package and use flag
1146                                 filename=$(egrep -rle "${pkg_re}.*[^-]${flag}( |$)" "${PACKAGE_USE_PATH}")
1147                                 if [[ -z "${filename}" ]]; then
1148                                         error ""${flag}" is not defined for package "${PACKAGE}""
1149                                         continue
1150                                 fi
1151                         else
1152                                 # Remove from package.use instead
1153                                 filename=${PACKAGE_USE_PATH}
1154                                 # Create as necessary
1155                                 touch "${filename}"
1156                         fi
1157                         # Scrub use flag from matched files
1158                         for f in ${filename}; do
1159                                 # Remove current flags in file
1160                                 echo "Removing \"${PACKAGE}[${flag}]\" use flag in \"${f}\""
1161                                 scrub_use_flag ${f} ${flag}
1162                         done
1163                         # Remove empty files
1164                         clean_package_use
1165                         ;;
1166         esac
1167         done
1168 } # }}}
1169
1170 # Function: modify {{{
1171 # USE flag modification function. Mainly a loop with calls to add_flag and 
1172 # remove_flag to create a new USE string which is then inserted into make.conf.
1173 modify() {
1174         if [[ -n "${PACKAGE}" ]]; then
1175                 modify_package "${*}"
1176                 return;
1177         fi;
1178
1179         local make_conf_modified=0
1180
1181         if [ -z "${*}" ]; then
1182                 if [ "${ACTION}" != "prune" ]; then
1183                         echo "WARNING: no USE flags listed for modification, do you really"
1184                         echo "         want to ${ACTION} *all* known USE flags?"
1185                         echo "         If you don't please press Ctrl-C NOW!!!"
1186                         sleep 5
1187                         set $(get_useflaglist | sort -u)
1188                 fi
1189         fi
1190
1191         get_useflags
1192
1193         NEW_MAKE_CONF_USE=" ${ACTIVE_FLAGS[1]} "
1194
1195         while [[ -n "${1}" ]]; do
1196                 if [[ "${ACTION}" == "add" ]]; then
1197                         if  [[ -n $(grep " ${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1198                                 warn "Use flag \"${1}\" is already enabled globally"
1199                                 shift
1200                         elif [[ -n $(grep " -${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1201                                 remove_flag "-${1}" || exit
1202                                 make_conf_modified=1
1203                         else
1204                                 add_flag "${1}" || exit
1205                                 make_conf_modified=1
1206                                 shift
1207                         fi
1208                 elif [[ "${ACTION}" == "remove" ]]; then
1209                         if [[ -n $(grep " -${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1210                                 warn "Use flag \"${1}\" is already disabled globally"
1211                                 shift
1212                         elif [[ -n $(grep " ${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1213                                 remove_flag "${1}" || exit
1214                                 make_conf_modified=1
1215                         else
1216                                 add_flag "-${1}" || exit
1217                                 make_conf_modified=1
1218                                 shift
1219                         fi
1220                 elif [[ "${ACTION}" == "prune" ]]; then
1221                         if [[ -n $(grep " ${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1222                                 remove_flag "${1}" || exit
1223                                 make_conf_modified=1
1224                         elif [[ -n $(grep " -${1} " <<< " ${NEW_MAKE_CONF_USE} ") ]]; then
1225                                 remove_flag "-${1}" || exit
1226                                 make_conf_modified=1
1227                         else
1228                                 warn "Use flag \"${1}\" is not set globally"
1229                         fi
1230                         shift
1231                 fi
1232         done
1233
1234         # a little loop to add linebreaks so we don't end with one ultra-long line
1235         NEW_MAKE_CONF_USE_2=""
1236         for x in ${NEW_MAKE_CONF_USE}; do
1237                 if [ $(((${#NEW_MAKE_CONF_USE_2}%70)+${#x}+2)) -gt 70 ]; then
1238                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}\\ \\n     $x "
1239                 else
1240                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}${x} "
1241                 fi
1242         done
1243
1244         # Bail if there is no need to modify make.conf
1245         [[ ${make_conf_modified} == 1 ]] || return
1246         # make a backup just in case the user doesn't like the new make.conf
1247         cp -p "${MAKE_CONF_PATH}" "${MAKE_CONF_BACKUP_PATH}"
1248
1249         # as sed doesn't really work with multi-line patterns we have to replace USE
1250         # on our own here. Basically just skip everything between USE=" and the
1251         # closing ", printing our new USE line there instead.
1252         inuse=0
1253         had_use=0
1254         x=0
1255         (while [ "$x" -eq "0" ]; do
1256                 read -r line || break
1257                 x="$?"
1258                 # Bug 275362 - Handle the case where make.conf includes:
1259                 # USE="
1260                 # a b
1261                 # "
1262                 # Consume USE=" when detected so the quote won't be detected
1263                 # as the ending quote
1264                 if [ "${line:0:4}" == "USE=" ]; then inuse=1; line=${line:5}; fi
1265                 [ "${inuse}" == "0" ] && echo -E "${line}"
1266                 if [ "${inuse}" == "1" ] && echo "${line}" | egrep '" *(#.*)?$' > /dev/null; then
1267                         echo -n 'USE="'
1268                         echo -ne "${NEW_MAKE_CONF_USE_2%% }"
1269                         echo '"'
1270                         inuse=0
1271                         had_use=1
1272                 fi
1273         done
1274         if [ ${had_use} -eq 0 ]; then
1275                 echo -n 'USE="'
1276                 echo -ne "${NEW_MAKE_CONF_USE_2%% }"
1277                 echo '"'
1278         fi ) < "${MAKE_CONF_BACKUP_PATH}" | sed -e 's:\\ $:\\:' > "${MAKE_CONF_PATH}"
1279
1280         echo "${MAKE_CONF_PATH} was modified, a backup copy has been placed at ${MAKE_CONF_BACKUP_PATH}"
1281 } # }}}
1282
1283 ##### main program comes now #####
1284
1285 parse_arguments "$@"
1286 check_sanity
1287
1288 eval ${MODE} ${ARGUMENTS}
1289
1290 # vim: set tabstop=4 shiftwidth=4: