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