Fix whitespace
[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 /etc/make.conf for changes
33 if [ -e "${ETC}/portage/make.conf" ]; then
34         MAKE_CONF_PATH="${ETC}/portage/make.conf"
35 elif [ -e "${ETC}/make.conf" ]; then
36         MAKE_CONF_PATH="${ETC}/make.conf"
37 else
38         fatal "make.conf does not exist"
39 fi
40 MAKE_CONF_BACKUP_PATH="${MAKE_CONF_PATH}.euse_backup"
41
42 # /etc/make.globals has been moved to /usr/share/portage/config/make.globals
43 if [ -e "${USR_SHARE_PORTAGE}/config/make.globals" ]; then
44         MAKE_GLOBALS_PATH="${USR_SHARE_PORTAGE}/config/make.globals"
45 else
46         MAKE_GLOBALS_PATH="${ETC}/make.globals"
47 fi
48
49 # /etc/make.profile or /etc/portage/make.profile, if /etc/make.profile exists, it will be used
50 if [ -e "${ETC}/make.profile" ]; then
51         MAKE_PROFILE_PATH="${ETC}/make.profile"
52 elif [ -e "${ETC}/portage/make.profile" ]; then
53         MAKE_PROFILE_PATH="${ETC}/portage/make.profile"
54 else
55         fatal "make.profile does not exist"
56 fi
57 PACKAGE_USE_PATH=${ETC}/portage/package.use
58
59 [ -z "${MODE}" ] && MODE="showhelp"             # available operation modes: showhelp, showversion, showdesc, showflags, modify
60
61 parse_arguments() {
62         if [ -z "${1}" ]; then
63                 return
64         fi
65         while [ -n "${1}" ]; do
66                 case "${1}" in
67                         -h | --help)           MODE="showhelp";;
68                         -V | -v | --version)   MODE="showversion";;
69                         -i | --info)           MODE="showdesc";;
70                         -I | --info-installed) MODE="showinstdesc";;
71                         -l | --local)          SCOPE="local";;
72                         -g | --global)         SCOPE="global";;
73                         -a | --active)         MODE="showflags";;
74                         -E | --enable)         MODE="modify"; ACTION="add";;
75                         -D | --disable)        MODE="modify"; ACTION="remove";;
76                         -P | --prune | -R | --remove)
77                                                MODE="modify"; ACTION="prune";;
78                         -p | --package)        MODE="modify"; shift; PACKAGE=${1}; SCOPE="local";;
79                         -*)
80                                 echo "ERROR: unknown option ${1} specified."
81                                 echo
82                                 MODE="showhelp"
83                                 ;;
84                         "%active")
85                                 get_portageuseflags
86                                 ARGUMENTS="${ARGUMENTS} ${ACTIVE_FLAGS[9]}"
87                                 ;;
88                         *)
89                                 ARGUMENTS="${ARGUMENTS} ${1}"
90                                 ;;
91                 esac
92                 shift
93         done
94 }
95
96 get_real_path() {
97         set -P
98         cd "$1"
99         pwd
100         cd "$OLDPWD"
101         set +P
102 }
103
104 # Function: check_sanity {{{
105 # Performs some basic system sanity checks
106 check_sanity() {
107         # file permission tests
108         local descdir
109         local make_defaults
110         local make_conf
111
112         [[ ! -d "${MAKE_PROFILE_PATH}" || ! -r "${MAKE_PROFILE_PATH}" ]] && error "${MAKE_PROFILE_PATH} is not readable"
113         # 
114         for make_conf in $(get_all_make_conf); do
115                 [ ! -r "${make_conf}" ] && fatal "${make_conf} is not readable"
116         done
117
118         descdir="$(get_portdir)/profiles"
119         
120         [ ! -r "${MAKE_GLOBALS_PATH}" ] && fatal "${MAKE_GLOBALS_PATH} is not readable"
121         [ -z "$(get_portdir)" ] && fatal "\$PORTDIR couldn't be determined"
122         [ ! -d "${descdir}" ] && fatal "${descdir} does not exist or is not a directory"
123         [ ! -r "${descdir}/use.desc" ] && fatal "${descdir}/use.desc is not readable"
124         [ ! -r "${descdir}/use.local.desc" ] && fatal "${descdir}/use.local.desc is not readable"
125
126         for make_defaults in $(get_all_make_defaults); do
127                 [ ! -r "$make_defaults" ]  && fatal "$_make_defaults is not readable"
128         done
129         [ "${MODE}" == "modify" -a ! -w "${MAKE_CONF_PATH}" ] && fatal ""${MAKE_CONF_PATH}" is not writable"
130         [ "${MODE}" == "modify" -a -s "${PACKAGE_USE_PATH}" -a ! -w "${PACKAGE_USE_PATH}" ] && fatal ""${PACKAGE_USE_PATH}" is not writable"
131 } # }}}
132
133 showhelp() {
134 cat << HELP
135 ${PROGRAM_NAME} (${VERSION})
136
137 Syntax: ${PROGRAM_NAME} <option> [suboptions] [useflaglist]
138
139 Options: -h, --help           - show this message
140          -V, --version        - show version information
141          -i, --info           - show descriptions for the given useflags
142          -I, --info-installed - show descriptions for the given useflags and
143                                 their current impact on the installed system
144          -g, --global         - show only global use flags (suboption)
145          -l, --local          - show only local use flags (suboption)
146          -a, --active         - show currently active useflags and their origin
147          -E, --enable         - enable the given useflags
148          -D, --disable        - disable the given useflags
149          -R, --remove         - remove all references to the given flags from
150                                 make.conf and package.use to revert to default 
151                                 settings
152          -P, --prune          - alias for --remove
153          -p, --package        - used with -E, -D, and -R to apply to a
154                                 speciic package only
155
156 Notes: ${PROGRAM_NAME} currently works for global flags defined
157        in make.globals, make.defaults, make.conf, use.force, and use.mask 
158        and local flags defined in package.use and individual package ebuilds.
159        It might have issues with cascaded profiles. If multiple options are 
160        specified only the last one will be used.
161 HELP
162 }
163
164 showversion() {
165 cat << VER
166 ${PROGRAM_NAME} (${VERSION})
167 Written by Marius Mauch
168
169 Copyright (C) 2004-2009 Gentoo Foundation, Inc.
170 This is free software; see the source for copying conditions.
171 VER
172 }
173
174 # Function: reduce_incrementals {{{
175 # remove duplicate flags from the given list in both positive and negative forms
176 # (but unlike portage always keep the last value even if it's negative)
177 # Otherwise the status flags could be incorrect if a flag appers multiple times in
178 # one location (like make.conf).
179 # Using python here as bash sucks for list handling.
180 # NOTE: bash isn't actually that bad at handling lists -- sh is. This may be
181 #       worth another look to avoid calling python unnecessariy. Or we could
182 #       just write the whole thing in python. ;)
183 reduce_incrementals() {
184         echo $@ | python -c "import sys
185 r=[]
186 for x in sys.stdin.read().split():
187         if x[0] == '-' and x[1:] in r:
188                 r.remove(x[1:])
189                 r.append(x)
190         elif x[0] != '-' and '-'+x in r:
191                 r.remove('-'+x)
192                 r.append(x)
193         elif x == '-*': r = ['-*']
194         elif x not in r: r.append(x)
195 print ' '.join(r)" 
196 } # }}}
197
198 # Function: reduce_incrementals_trump {{{
199 # Similar to reduce_incrementals but negative flags trump positive
200 # flags, regardless of which follows which
201 reduce_incrementals_trump() {
202         echo $@ | python -c "import sys
203 r=[]
204 for x in sys.stdin.read().split():
205         if x[0] == '-' and x[1:] in r:
206                 r.remove(x[1:])
207                 r.append(x)
208         elif x == '-*': r = ['-*']
209         elif x not in r and not '-'+x in r: r.append(x)
210 print ' '.join(r)" 
211 } # }}}
212
213 # Function: reduce_package_use {{{
214 # Similar to reduce_incrementals except converts lines from package atoms
215 # in /etc/portage/package.use files to lines of "pkg {[-]flag}*"
216 #
217 # Arguments:
218 # * - Lines of package atom followed by flags
219 #     (app-editors/vim flag1 flag2 -flag3)
220 reduce_package_use() {
221         echo "${@}" | python -c "import sys,re
222 h={}; getflags=re.compile(r'(-?[\w*-]+)')
223 for x in sys.stdin.read().split('\n'):
224         if not x: continue
225         parts = x.lstrip().split(' ',1)
226         if len(parts)==1: continue
227         pkg=parts[0]
228         flags = getflags.findall(parts[1])
229         if not pkg in h: h[pkg]=[]
230         r=h[pkg]
231         for x in flags:
232                 if x[0] == '-' and x[1:] in r:
233                         r.remove(x[1:])
234                         r.append(x)
235                 elif x[0] != '-' and '-'+x in r:
236                         r.remove('-'+x)
237                         r.append(x)
238                 elif x == '-*': r = h[pkg] = ['-*']
239                 elif x not in r:
240                         r.append(x)
241 print '\n'.join(['%s %s' % (pkg,' '.join(flgs)) for pkg,flgs in h.iteritems() if len(flgs)])"
242 } # }}}
243
244 # Function: get_useflags {{{
245 # Creates a bash array ACTIVE_FLAGS that contains the global use flags, 
246 # indexed by origin: 0: environment, 1: make.conf, 2: make.defaults, 
247 # 3: make.globals, and local use flags, indexed by origin: 4: package.use, 
248 # 5: ebuild IUSE, 6: use.mask, 7: use.force, 
249 # 9: flags indicated active by emerge --info (get_portageuseflags)
250 get_useflags() {
251         if [[ -z ${ACTIVE_FLAGS[4]} && ( $SCOPE == "local" || -z $SCOPE ) ]]; then
252                 # Parse through /etc/portage/package.use
253                 if [[ -d ${PACKAGE_USE_PATH} ]]; then
254                         ACTIVE_FLAGS[4]="$( cat ${PACKAGE_USE_PATH}/* \
255                                 | sed -re "s/ *#.*$//g" -e "s/^ *$//g" )"
256                 elif [[ -e ${PACKAGE_USE_PATH} ]]; then
257                         # JWM, 23/12/2009: I edited this following line but I'm not sure if it's 100% correct.
258                         ACTIVE_FLAGS[4]="$( sed -re "s/ *#.*$//g" -e "s/^ *$//g" \
259                                 ${PACKAGE_USE_PATH})"
260                 fi
261                 # Simplify ACTIVE_FLAGS[4] to be lines of pkg {[-]flag}*
262                 ACTIVE_FLAGS[4]="$(reduce_package_use "${ACTIVE_FLAGS[4]}")"
263                 #
264                 # ACTIVE_FLAGS[5] reserved for USE flags defined in ebuilds and
265                 # is generated/maintained in the get_useflaglist_ebuild() function
266         fi
267
268         # only calculate once as calling emerge is painfully slow
269         [ -n "${USE_FLAGS_CALCULATED}" ] && return
270
271         # backup portdir so get_portdir() doesn't give false results later
272         portdir_backup="${PORTDIR}"
273         
274         ACTIVE_FLAGS[0]="$(reduce_incrementals ${USE})"
275         USE=""
276         for x in $(get_all_make_conf); do
277                 source "${x}"
278                 ACTIVE_FLAGS[1]="$(reduce_incrementals ${ACTIVE_FLAGS[1]} ${USE})"
279         done
280         USE=""
281         for x in $(get_all_make_defaults); do
282                 source "${x}"
283                 ACTIVE_FLAGS[2]="${ACTIVE_FLAGS[2]} ${USE}"
284         done
285         ACTIVE_FLAGS[2]="$(reduce_incrementals ${ACTIVE_FLAGS[2]})"
286         USE=""
287         source "${MAKE_GLOBALS_PATH}"
288         ACTIVE_FLAGS[3]="$(reduce_incrementals ${USE})"
289
290         # restore saved env variables
291         USE="${ACTIVE_FLAGS[0]}"
292         PORTDIR="${portdir_backup}"
293
294         #
295         # Traverse through use.mask and use.force (0.5s)
296         # Flip signs of use.mask (it's interpreted oppositely), 
297         ACTIVE_FLAGS[6]=$(reduce_incrementals_trump \
298                 $(cat $(traverse_profile "use.mask") | sed -re "/^#.*$/{d}") \
299                         | sed -re "s/(^| )-[^ ]*//g" -e "s/(^| )([a-z0-9])/ -\2/g")
300         ACTIVE_FLAGS[7]=$(reduce_incrementals \
301                 $(cat $(traverse_profile "use.force") \
302                         | sed -re "/^#.*$/ {d}"))
303
304         USE_FLAGS_CALCULATED=1
305 } # }}}
306
307 # Function: get_portageuseflags # {{{
308 # Fetch USE flags reported active by Portage
309 get_portageuseflags() {
310         # only calculate once as calling emerge is painfully slow
311         [ -n "${_PORTAGE_USE_FLAGS_CALCULATED}" ] && return
312         # get the currently active USE flags as seen by portage, this has to be after
313         # restoring USE or portage won't see the original environment
314         # Bug 181309, emerge may complain if EMERGE_DEFAULT_OPTS="--ask" is set
315         ACTIVE_FLAGS[9]="$(portageq envvar USE)" #'
316         _PORTAGE_USE_FLAGS_CALCULATED=1
317 } # }}}
318
319 # Function: get_useflaglist {{{
320 # Get the list of all known USE flags by reading use.desc and/or 
321 # use.local.desc (depending on the value of $SCOPE). Also searches any 
322 # registered overlays after searching the main portage tree first.
323 # Use flags visible in both the main tree and overlays are trumped by
324 # the main tree. Overlays are indicated by brackets [xxx] at the 
325 # beginning of the description. 
326 #
327 # Returns:
328 # (written to stdout) Sorted, unique list of system-wide USE flags and
329 # descriptions. Flags defined in overlays have the overlay in brackets
330 # prepended to the descriptions.
331 #
332 # Environment:
333 # SCOPE - [local|global] constrain search to local (use.local.desc) or
334 #                 global (use.desc)
335 get_useflaglist() {
336         local descdir
337         local overlay
338         for profiledir in ${ALL_PORTDIRS[@]}; do
339                 descdir="${profiledir}/profiles"
340                 if [[ -z ${SCOPE} || ${SCOPE} == "global" ]]; then
341                         [[ ! -s "${descdir}/use.desc" ]] && continue
342                         egrep "^[^# ]+ +-" "${descdir}/use.desc" 
343                 fi
344                 if [[ -z ${SCOPE} || ${SCOPE} == "local" ]]; then
345                         [[ ! -s "${descdir}/use.local.desc" ]] && continue
346                         egrep "^[^# :]+:[^ ]+ +-" "${descdir}/use.local.desc" \
347                                 | cut -d: -f 2 
348                 fi
349         done | cut -d " "  -f1 | sort --field=":" --key=1,1 --unique
350 } # }}}
351
352 # Function: get_useflaglist_ebuild {{{
353 # Builds USE flag information for specified package atom into 
354 # ACTIVE_FLAGS[5]. For the atom, the versions available are found, and 
355 # for each, the corresponding SLOT, IUSE are stored along with which 
356 # overlay the ebuild lives in. Considering that the pieces of information 
357 # may be required in any order or any subsets, it is intended for the 
358 # function to cache the information and it be retrieved from 
359 # ACTIVE_FLAGS[5]. So the format of ACTIVE_FLAGS[5] is newline-separated 
360 # list of:
361 #
362 # category/packge;version;SLOT;IUSE;overlay
363 #
364 # Arguments:
365 # $1 - Package atom to lookup (app-editor/vim)
366 #
367 # Returns:
368 # Nothing significant
369 #
370 # Environment:
371 # PORTDIR - Root of portage tree
372 # ACTIVE_FLAGS - Array of current use flag info
373 #
374 get_useflaglist_ebuild() {
375         local known=$(echo "${ACTIVE_FLAGS[5]}" | egrep "^${1}")
376         if [[ -n $known ]]; then
377                 # No need to recache
378                 return
379         fi
380         local pkg=$(echo ${1} | cut -d/ -f2)
381         declare append
382         for portdir in ${ALL_PORTDIRS[@]}; do
383                 # Open the ebuild file and retrieve defined USE flags
384                 [[ ! -d "$portdir/${1}" ]] && continue
385                 if [[ ! -d "$portdir/metadata/cache" ]]; then
386                         echo "!!! Metadata cache not found. You need to run " >&2
387                         echo "!!! 'egencache --repo=$overlay --update'" >&2
388                         echo "!!! to generate metadata for your overlays" >&2
389                         return 1
390                 fi
391                 append=$(ls $portdir/metadata/cache/${1}-* \
392                         | egrep "${1}-[0-9.]+" \
393                         | sed -e "s:$portdir/metadata/cache/${1}-::g" \
394                         | while read -d $'\n' version; do
395                                 IFS=$'\n'
396                                 if [[ $portdir == $PORTDIR ]]; then 
397                                         overlay=""
398                                 else
399                                         if [[ -s $(dirname ${portdir}/repo_name) ]]; then
400                                                 overlay="$(cat "${portdir}/profiles/repo_name")"
401                                         else
402                                                 # XXX: May be better to use full path
403                                                 overlay="$(basename "${portdir}")"
404                                         fi
405                                 fi
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         [[ -f "${curdir}/${1}" ]] && rvalue="${curdir}/${1} ${rvalue}"
443         if [[ -f "${curdir}/parent" ]]; then
444                 for parent in $(egrep -v '(^#|^ *$)' ${curdir}/parent); do
445                         # Bug 231394, handle parent path being absolute
446                         if [[ ${parent:0:1} == "/" ]]; then
447                                 pdir="$(get_real_path ${parent})"
448                         else
449                                 pdir="$(get_real_path ${curdir}/${parent})"
450                         fi
451                         rvalue="$(traverse_profile ${1} ${pdir}) ${rvalue}"
452                 done
453         fi
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 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         if [ -z "${PORTDIR}" ]; then
675                 use_backup="${USE}"
676                 source "${MAKE_GLOBALS_PATH}"
677                 for x in $(get_all_make_defaults); do
678                         source "${x}"
679                 done
680                 for x in $(get_all_make_conf); do
681                         source "${x}"
682                 done
683                 USE="${use_backup}"
684         fi
685         echo "${PORTDIR}"
686 } # }}}
687 # This won't change while the script is running, so cache it
688 PORTDIR="$(get_portdir)"
689
690 # Function: get_all_overlays {{{
691 # Outputs list of portage overlays as defined in the PORTDIR_OVERLAY
692 # variable defined in make.conf 
693 get_all_overlays() {
694         use_backup="${USE}"
695         source "${MAKE_CONF_PATH}"
696         USE="${use_backup}"
697         echo ${PORTDIR_OVERLAY}
698 } # }}}
699 ALL_PORTDIRS=( "$PORTDIR" $(get_all_overlays) )
700
701 # Function: array_contains {{{
702 # PHP-style array_contains function.
703 #
704 # Arguments:
705 # 1 - haystack
706 # 2 - needle
707 #
708 # Returns:
709 # 0 (True) if needle in haystack, null (False) otherwise
710 array_contains() {
711         for i in $1; do [[ $i == $2 ]] && return 0; done
712         return
713 } # }}}
714
715 # Function: showdesc {{{
716 # This function takes a list of use flags and shows the status and 
717 # the description for each one, honoring $SCOPE
718 #
719 # Arguments:
720 # * - USE flags for which to display descriptions. Undefined means to
721 #         display descriptions for all known USE flags
722 #
723 # Environment:
724 # SCOPE - defines whether to output local or global USE flag descriptions
725 #                 Empty means to display both
726 #
727 # Outputs:
728 # (STDOUT) Flag description(s) for given USE flags
729 #
730 showdesc() {
731         local descdir
732         local current_desc
733         local found_one
734         local args
735         
736         set -f
737         args="${*:-*}"
738
739         if [ -z "${SCOPE}" ]; then
740                 SCOPE="global" showdesc ${args}
741                 echo
742                 SCOPE="local" showdesc ${args}
743                 return
744         fi
745         
746         local useflags=( $(echo "$(get_useflaglist)") )
747         
748         [ "${SCOPE}" == "global" ] && echo "global use flags (searching: ${args})"
749         [ "${SCOPE}" == "local" ] && echo "local use flags (searching: ${args})"
750         echo "************************************************************"
751         set +f
752         if [ "${args}" == "*" ]; then
753                 args="${useflags[*]}"
754         fi
755
756         set ${args}
757
758         foundone=0
759         while [[ -n "${1}" ]]; do
760                 if [[ "${SCOPE}" == "global" ]]; then
761                         if array_contains "${useflags[*]}" "$1"; then
762                                 get_flagstatus "${1}"
763                                 # XXX: Handle overlay
764                                 grep "^${1}  *-" ${ALL_PORTDIRS[@]/%//profiles/use.desc} 2> /dev/null \
765                                         | sed -re "s/^([^:]+)://"
766                                 foundone=1
767                         fi
768                 fi
769                 # local flags are a bit more complicated as there can be multiple
770                 # entries per flag and we can't pipe into printf
771                 if [[ "${SCOPE}" == "local" ]]; then
772                         if array_contains "${useflags[*]}" "$1"; then
773                                 foundone=1
774                         fi
775                         # Fetch all the packages data using this flag
776                         infos=$( grep ":${1}  *-" ${ALL_PORTDIRS[@]/%//profiles/use.local.desc} 2> /dev/null \
777                                 | sed -re "s/^([^:]+):([^:]+):(${1}) *- *(.+)/\1|\2|\3|\4/g")
778                         OIFS=$IFS; IFS=$'\n'; infos=($infos); IFS=$OIFS;
779                         for line in "${infos[@]}"; do
780                                 OIFS=$IFS; IFS="|"; line=($line); IFS=$OIFS
781                                 pkg=${line[1]} 
782                                 flag=${line[2]}
783                                 desc=${line[3]}
784                                 if get_flagstatus "${flag}"; then
785                                         ACTIVE="+"
786                                 else
787                                         ACTIVE="-"
788                                 fi
789                                 printf "%s\n" "${flag}"
790                                 printf "%s: %s\n" "${pkg}" "${desc}" \
791                                         | fold --width=$((${COLUMNS:-80}-10)) -s | sed -e "s/^/    /g"
792                                 get_flagstatus_pkg "${flag}" "${pkg}" "${ACTIVE}"
793                         done
794                 fi
795                 shift
796         done
797         
798         if [[ ${foundone} == 0 ]]; then
799                 echo "no matching entries found"
800         fi
801 } # }}}
802
803 # Function: showinstdesc {{{
804 # Works like showdesc() but displays only descriptions of which the appropriate
805 # ebuild is installed and prints the name of those packages.
806 #
807 # Arguments:
808 # * - USE flags for which to display descriptions. Undefined means to
809 #         display descriptions for all known USE flags
810 #
811 # Environment:
812 # SCOPE - defines whether to output local or global USE flag descriptions
813 #                 Empty means to display both
814 #
815 # Outputs:
816 # (STDOUT) Flag description(s) for given USE flags along with installed 
817 # packages
818 #
819 showinstdesc() {
820         local descdir
821         local current_desc
822         local args
823         local -i foundone=0
824         local OIFS="$IFS"
825
826         args=("${@:-*}")
827
828         case "${SCOPE}" in
829                 "global") echo "global use flags (searching: ${args[@]})";;
830                  "local") echo "local use flags (searching: ${args[@]})";;
831                        *) SCOPE="global" showinstdesc "${args[@]}"
832                           echo
833                           SCOPE="local" showinstdesc "${args[@]}"
834                           return;;
835         esac
836
837         descdir="$(get_portdir)/profiles"
838         echo "************************************************************"
839
840         if [ "${args}" = "*" ]; then
841                 args="$(get_useflaglist | sort -u)"
842         fi
843
844         set "${args[@]}"
845
846         while [ -n "${1}" ]; do
847                 case "${SCOPE}" in
848                         "global")
849                                 if desc=$(grep "^${1}  *-" "${descdir}/use.desc"); then
850                                         get_flagstatus "${1}"
851                                         echo "$desc"
852                                         # get list of installed packages matching this USE flag.
853                                         IFS=$'\n'
854                                         packages=($(equery -q -C hasuse -i "${1}" | awk '{ print $(NF-1) }' | sort))
855                                         foundone+=${#packages[@]}
856                                         printf "\nInstalled packages matching this USE flag: "
857                                         if [ ${foundone} -gt 0 ]; then
858                                                 echo $'\n'"${packages[*]}"
859                                         else
860                                                 echo "none"
861                                         fi
862                                 fi
863                         ;;
864                         "local")
865                                 # local flags are a bit more complicated as there can be multiple
866                                 # entries per flag and we can't pipe into printf
867                                 IFS=': ' # Use a space instead of a dash because dashes occur in cat/pkg
868                                 while read pkg flag desc; do
869                                         # print name only if package is installed
870                                         # NOTE: If we implement bug #114086 's enhancement we can just use the
871                                         #       exit status of equery instead of a subshell and pipe to wc -l
872                                         # Bug 274472, -e is the default
873                                         if [ $(equery -q -C list -i "${pkg}" | wc -l) -gt 0 ]; then
874                                                 foundone=1
875                                                 IFS="$OIFS"
876                                                 get_flagstatus "${flag}" "${pkg}"
877                                                 IFS=': '
878                                                 printf "%s (%s):\n%s\n\n" "${flag}" "${pkg}" "${desc#- }"
879                                         fi
880                                 done < <(grep ":${1}  *-" "${descdir}/use.local.desc")
881                         ;;
882                 esac
883                 shift
884         done
885
886         if [ ${foundone} -lt 1 ]; then
887                 echo "no matching entries found"
888         fi
889         IFS="$OIFS"
890 } # }}}
891
892 # Function: showflags {{{
893 # show a list of all currently active flags and where they are activated
894 showflags() {
895         local args
896
897         get_useflags
898
899         args="${*:-*}"
900
901         if [ "${args}" == "*" ]; then
902                 args="$(get_useflaglist | sort -u)"
903         fi
904
905         set ${args}
906         get_portageuseflags
907         
908         while [ -n "${1}" ]; do
909                 if echo " ${ACTIVE_FLAGS[9]} " | grep " ${1} " > /dev/null; then
910                         printf "%-20s" ${1}
911                         get_flagstatus ${1}
912                         echo
913                 fi
914                 if echo " ${ACTIVE_FLAGS[4]} " | egrep -e " -?${1} " > /dev/null; then
915                         for pkg in $( echo "${ACTIVE_FLAGS[4]}" | \
916                                         egrep " -?${1} " | cut -d " " -f 2); do
917                                 printf "%-20s" ${1}
918                                 SCOPE="local" get_flagstatus ${1} "${pkg}"
919                                 printf "(%s)\n" ${pkg}
920                         done;
921                 fi
922                 shift
923         done
924 } # }}}
925
926 # two small helpers to add or remove a flag from a USE string
927 add_flag() {
928         if [[ -n $(grep " -${1//-/} " <<< " ${ACTIVE_FLAGS[6]} ") ]]; then
929                 error "Use flag \"${1//-/}\" is masked and should not be added" \
930                           "to make.conf."
931                 return 1
932         # Bug #104396 -- Only add use flags defined in use.desc and use.local.desc
933         elif [[ -z $(grep "^${1//-/}$" <<< "$(get_useflaglist)") ]]; then
934                 error "Use flag \"${1//-/}\" is not defined in use.desc and should" \
935                           "not be added\nto make.conf."
936                 return 1
937         else
938                 NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE} ${1}"
939         fi
940 }
941
942 remove_flag() {
943         NEW_MAKE_CONF_USE="${NEW_MAKE_CONF_USE// ${1} / }"
944 }
945
946 # Function: clean_package_use {{{
947 # Simple utility to remove empty files from package.use
948 clean_package_use() {
949 if [[ -d ${PACKAGE_USE_PATH} ]]; then
950         for f in $(find ${PACKAGE_USE_PATH} -size 0); do
951                 echo "Removing empty file ""${f}"""
952                 rm ${f}
953         done;
954 fi
955 } # }}}
956
957 # Function: scrub_use_flag {{{
958 # Utility to remove a use flag from a file in package.use[/]
959
960 # Arguments:
961 # 1 - File
962 # 2 - Use flag
963 #
964 # Environment:
965 # PACKAGE - Package atom for which to remove flag
966 scrub_use_flag() {
967         local atom_re="^[<>]?=?([a-z][\da-z/-]+[a-z])(-[0-9pr._*-]+)?"
968         local filename=${1}
969         # Ignore leading - on flag
970         local flag=${2#*-}
971         local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/")
972         local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?"
973
974         while read line; do
975                 # Skip (preserve) comments on their own lines
976                 if [[ -z $(echo "${line}" | sed -re "s/^ *#.*$//") ]]; then
977                         echo "${line}"
978                 # Detect if requested package is defined on this line
979                 elif [[ -n "${PACKAGE}" ]]; then
980                         if [[ -n $(echo "${line}" | grep -Ee "${pkg_re}") ]]; then      
981                                 # If this is the only (remaining) use flag defined
982                                 # for this package, then remove the whole line
983                                 if [[ -z $(echo "${line}" | \
984                                                 grep -Ee "${pkg_re} *-?${flag} *$") ]]; then
985                                         # Remove flag from this line
986                                         echo "${line}" | sed -re "s/ *-?\b${flag}\b//"
987                                 fi
988                         else
989                                 # Passthru
990                                 echo "${line}"
991                         fi
992                 # If line only has this use flag, let it be removed 
993                 # (used if PACKAGE is not defined -- from pruning)
994                 elif [[ -n $(echo "${line}" | \
995                                 egrep "^[^#]*${atom_re}.*-?${flag}") ]]; then
996                         echo "Removing use flag from ${line}" >&2
997                         if [[ -z $(echo "${line}" | \
998                                         grep -Ee "${atom_re} *-?${flag} *$") ]]; then
999                                 # Remove flag from this line
1000                                 echo "${line}" | sed -re "s/-?\b${flag}\b//"
1001                         fi
1002                 else
1003                         # Passthru
1004                         echo "${line}"
1005                 fi
1006         done > "${filename}.new" < "${filename}" 
1007         mv "${filename}.new" "${filename}"
1008 } # }}}
1009
1010 # Function: modify_package {{{
1011 # Adds and removes USE flags from individual packages by modifying
1012 # files in package.use. It supports package.use both as a file and
1013 # and as a folder. Also handles "enabling" as removing a disabled flag from
1014 # a file, and "disabling" a globally enabled flag by adding a negative to 
1015 # a file. Also warns about unused and unknown flags, and about flags
1016 # already enabled, disabled, or masked.
1017 #
1018 # Arguments:
1019 # * - USE flag(s) to add or remove
1020 #
1021 # Environment:
1022 # PACKAGE - Package for which to add (-E) or remove (-D) the USE
1023 #           flag(s)
1024 modify_package() {
1025         get_useflags
1026
1027         local atom_re="^[<>]?=?([a-z][\da-z/-]+[a-z])(-[0-9pr._*-]+)?"
1028         local pkg=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\1/")
1029         local V=$(echo "${PACKAGE}" | sed -re "s/${atom_re}/\2/")
1030         local pkg_re="[<>]?=?${pkg}(-[\dpr._*-]+)?"
1031
1032         local all_flags=$(SCOPE= get_useflaglist)
1033         # Shift at top rather than bottom to support 'continue'
1034         set " " ${*}
1035         while [[ -n ${2} ]]; do
1036         shift
1037         local flag=${1}
1038         ACTIVE="-"
1039         #
1040         # Fetch flag ACTIVE status (+,-,null)
1041         get_flagstatus "${flag}" "${pkg}" > /dev/null
1042         GLOBAL_ACTIVE="$ACTIVE"
1043         # XXX: If V is not given, is this necessary? Should it use the version
1044         #      that would be installed by emerge?
1045         get_flagstatus_pkg "${flag}" "${pkg}" "${ACTIVE}" "${V}" > /dev/null
1046         #
1047         # --- Sanity checks
1048         # (1) make sure ${pkg} exists in portdir
1049         if [[ ! -d "$(get_portdir)/${pkg}" ]]; then
1050                 fatal "Package \"${pkg}\" does not exist"
1051         #
1052         # (2) make sure ${flag} is defined in get_useflaglist
1053         elif ! array_contains "$all_flags" ${flag}; then
1054                 error "USE flag \"${flag}\" does not exist"
1055                 continue
1056                 # Don't bail just because of this, just warn
1057         # (3) make sure use flag is valid for the package
1058         elif [[ -z $(echo "${ACTIVE_FLAGS[5]} " | grep -Ee "^${pkg_re}" \
1059                         | grep -Ee "[; ][+-]?${flag}") ]]; then
1060                 # XXX: Handle version or version wildcard?
1061                 warn "USE flag \"${flag}\" is not used by $PACKAGE"
1062                 # Don't necessarily bail for this, just warn
1063         elif [[ -n "${V}" && -z "$(egrep "<|>|=" <<< "${PACKAGE:0:1}")" ]]; then
1064                 error "Invalid package atom. Did you forget the leading '='?"
1065                 continue
1066         elif [[ -z "${V}" && -n "$(egrep "<|>|=" <<< "${PACKAGE:0:1}")" ]]; then
1067                 error "Invalid package atom. Did you forget the version?"
1068                 continue
1069         fi
1070         # If flag is enabled in portage USE flags (emerge --info), 
1071         # then "remove"ing the flag should be replaced with adding
1072         # the negative flag instead
1073         if [[ "${ACTION}" == "remove" ]]; then
1074                 if [[ "${ACTIVE:-${GLOBAL_ACTIVE}}" == "+" ]]; then
1075                         if [[ -n $(echo "${ACTIVE_FLAGS[4]}" | grep "^$PACKAGE" \
1076                                         | grep " $flag") ]]; then
1077                                 iuse=$(echo "${ACTIVE_FLAGS[5]} " | grep -Ee "^${pkg_re}" \
1078                                                 | cut -d ";" -f4 | egrep -o "[+-]?${flag}")
1079                                 # Ensure the flag is enabled in the ebuild _and_ in package.use,
1080                                 # if so, enable it in package.use
1081                                 if [[ "${iuse}" =~ "+" ]]; then
1082                                         # The flag is currently enabled in the ebuild, so add a
1083                                         # disablement
1084                                         flag="-${flag}"
1085                                         ACTION="add"
1086                                 fi
1087                         else
1088                                 # Not enabled in package.use, so disable it there
1089                                 flag="-${flag}"
1090                                 ACTION="add"
1091                         fi
1092                 else
1093                         error "USE flag \"$flag\" is already disabled for $PACKAGE"
1094                         continue
1095                 fi
1096         elif [[ "${ACTION}" == "prune" ]]; then
1097                 # Just remove the flag below
1098                 [[ "${ACTIVE}" == "-" ]] && flag="-${flag}"
1099                 ACTION="remove"
1100         # If flag is currently disabled for the package requested 
1101         # to be enabled in, then "remove" the negative
1102         elif [[ "${ACTION}" == "add" ]]; then
1103                 if [[ "${ACTIVE}" == "-" ]]; then
1104                         # If flag is masked, it should be added to package.mask, instead
1105                         # of package.use. For now, yield a warning and quit
1106                         if [[ -n $(echo " ${ACTIVE_FLAGS[6]}" | grep " -${flag} ") ]]; then
1107                                 error "USE flag \"$flag\" is masked. Enabling in package.use will" \
1108                                           "\nbe ineffective. You may have an incorrect profile selected."
1109                                 continue
1110                         elif [[ -n $(echo "${ACTIVE_FLAGS[4]}" | grep "^$PACKAGE" \
1111                                         | grep " -$flag") ]]; then
1112                                 iuse=$(echo "${ACTIVE_FLAGS[5]} " | grep -Ee "^${pkg_re}" \
1113                                                 | cut -d ";" -f4 | egrep -o "[+-]?${flag}")
1114                                 # Ensure the flag is disabled in the ebuild _and_ in package.use,
1115                                 # if so, enable it in package.use
1116                                 if [[ "${iuse}" =~ "+" ]]; then
1117                                         # The flag is currently disabled by package.use only, so remove the
1118                                         # disablement
1119                                         flag="-${flag}"
1120                                         ACTION="remove"
1121                                 fi
1122                         fi
1123                 elif [[ "${ACTIVE:-${GLOBAL_ACTIVE:--}}" == "+" ]]; then
1124                         # XXX: Perhaps look at indicating where it is enabled
1125                         error "USE flag \"$flag\" is already enabled for $PACKAGE"
1126                         continue
1127                 fi
1128         fi
1129         case "${ACTION}" in
1130                 "add")
1131                         local filename
1132                         if [[ -d ${PACKAGE_USE_PATH} ]]; then
1133                                 # Use naming convention of package.use/package
1134                                 filename="${PACKAGE_USE_PATH}/${pkg#*/}"
1135                                 if [[ ! -s "${filename}" ]]; then
1136                                         # Create new file to contain flag
1137                                         echo "${PACKAGE} ${flag}" > "${filename}"
1138                                         echo "Adding \"${PACKAGE}[${flag}]\" use flag to new file ""${filename}"""
1139                                         continue
1140                                 fi
1141                         else    
1142                                 # Add to package.use file instead
1143                                 filename="${PACKAGE_USE_PATH}"
1144                                 # Create as necessary
1145                                 touch "${filename}"
1146                         fi
1147                         # Walk through the file and add the flag manually
1148                         echo "Adding \"${PACKAGE}[${flag}]\" use flag in \"${filename}\""
1149                         local added=0
1150                         while read line; do
1151                                 if [[ -n $(echo "${line}" | egrep -re "^[^#]*${PACKAGE} ") ]]; then
1152                                         echo $(reduce_package_use "${line} ${flag}")
1153                                         added=1
1154                                 else
1155                                         # Passthru
1156                                         echo "${line}"
1157                                 fi
1158                         done < "${filename}" > "${filename}.new"
1159                         mv "${filename}.new" "${filename}"
1160                         if [[ ${added} -eq 0 ]]; then
1161                                 echo "${PACKAGE} ${flag}" >> "${filename}"
1162                         fi
1163                         ;;
1164                 "remove")
1165                         local filename
1166                         if [[ -d ${PACKAGE_USE_PATH} ]]; then
1167                                 # Scan for file containing named package and use flag
1168                                 filename=$(egrep -rle "${pkg_re}.*[^-]${flag}( |$)" "${PACKAGE_USE_PATH}")
1169                                 if [[ -z "${filename}" ]]; then
1170                                         error ""${flag}" is not defined for package "${PACKAGE}""
1171                                         continue
1172                                 fi
1173                         else
1174                                 # Remove from package.use instead
1175                                 filename=${PACKAGE_USE_PATH}
1176                                 # Create as necessary
1177                                 touch "${filename}"
1178                         fi
1179                         # Scrub use flag from matched files
1180                         for f in ${filename}; do
1181                                 # Remove current flags in file
1182                                 echo "Removing \"${PACKAGE}[${flag}]\" use flag in \"${f}\""
1183                                 scrub_use_flag ${f} ${flag}
1184                         done
1185                         # Remove empty files
1186                         clean_package_use
1187                         ;;
1188         esac
1189         done
1190 } # }}}
1191
1192 # Function: modify {{{
1193 # USE flag modification function. Mainly a loop with calls to add_flag and 
1194 # remove_flag to create a new USE string which is then inserted into make.conf.
1195 modify() {
1196         if [[ -n "${PACKAGE}" ]]; then
1197                 modify_package "${*}"
1198                 return;
1199         fi;
1200
1201         if [ -z "${*}" ]; then
1202                 if [ "${ACTION}" != "prune" ]; then
1203                         echo "WARNING: no USE flags listed for modification, do you really"
1204                         echo "         want to ${ACTION} *all* known USE flags?"
1205                         echo "         If you don't please press Ctrl-C NOW!!!"
1206                         sleep 5
1207                         set $(get_useflaglist | sort -u)
1208                 fi
1209         fi
1210
1211         get_useflags
1212
1213         NEW_MAKE_CONF_USE=" ${ACTIVE_FLAGS[1]} "
1214
1215         while [ -n "${1}" ]; do
1216                 if [ "${ACTION}" == "add" ]; then
1217                         if echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then
1218                                 shift
1219                         elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then
1220                                 remove_flag "-${1}"
1221                         else
1222                                 add_flag "${1}" || exit
1223                                 shift
1224                         fi
1225                 elif [ "${ACTION}" == "remove" ]; then
1226                         if echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then
1227                                 shift
1228                         elif echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then
1229                                 remove_flag "${1}"
1230                         else
1231                                 add_flag "-${1}" || exit
1232                                 shift
1233                         fi
1234                 elif [ "${ACTION}" == "prune" ]; then
1235                         if echo " ${NEW_MAKE_CONF_USE} " | grep " ${1} " > /dev/null; then
1236                                 remove_flag "${1}"
1237                         elif echo " ${NEW_MAKE_CONF_USE} " | grep " -${1} " > /dev/null; then
1238                                 remove_flag "-${1}"
1239                         fi
1240                         shift
1241                 fi
1242         done
1243
1244         #echo "old flags:"
1245         #echo ${ACTIVE_FLAGS[1]}
1246         #echo
1247         #echo "new flags:"
1248         #echo ${NEW_MAKE_CONF_USE}
1249
1250         # a little loop to add linebreaks so we don't end with one ultra-long line
1251         NEW_MAKE_CONF_USE_2=""
1252         for x in ${NEW_MAKE_CONF_USE}; do
1253                 if [ $(((${#NEW_MAKE_CONF_USE_2}%70)+${#x}+2)) -gt 70 ]; then
1254                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}\\ \\n     $x "
1255                 else
1256                         NEW_MAKE_CONF_USE_2="${NEW_MAKE_CONF_USE_2}${x} "
1257                 fi
1258         done
1259
1260         # make a backup just in case the user doesn't like the new make.conf
1261         cp -p "${MAKE_CONF_PATH}" "${MAKE_CONF_BACKUP_PATH}"
1262
1263         # as sed doesn't really work with multi-line patterns we have to replace USE
1264         # on our own here. Basically just skip everything between USE=" and the
1265         # closing ", printing our new USE line there instead.
1266         inuse=0
1267         had_use=0
1268         x=0
1269         (while [ "$x" -eq "0" ]; do
1270                 read -r line || break
1271                 x="$?"
1272                 # Bug 275362 - Handle the case where make.conf includes:
1273                 # USE="
1274                 # a b
1275                 # "
1276                 # Consume USE=" when detected so the quote won't be detected
1277                 # as the ending quote
1278                 if [ "${line:0:4}" == "USE=" ]; then inuse=1; line=${line:5}; fi
1279                 [ "${inuse}" == "0" ] && echo -E "${line}"
1280                 if [ "${inuse}" == "1" ] && echo "${line}" | egrep '" *(#.*)?$' > /dev/null; then
1281                         echo -n 'USE="'
1282                         echo -ne "${NEW_MAKE_CONF_USE_2%% }"
1283                         echo '"'
1284                         inuse=0
1285                         had_use=1
1286                 fi
1287         done
1288         if [ ${had_use} -eq 0 ]; then
1289                 echo -n 'USE="'
1290                 echo -ne "${NEW_MAKE_CONF_USE_2%% }"
1291                 echo '"'
1292         fi ) < "${MAKE_CONF_BACKUP_PATH}" | sed -e 's:\\ $:\\:' > "${MAKE_CONF_PATH}"
1293
1294         echo "${MAKE_CONF_PATH} was modified, a backup copy has been placed at ${MAKE_CONF_BACKUP_PATH}"
1295 } # }}}
1296
1297 ##### main program comes now #####
1298
1299 # disable globbing as it fucks up with args=*
1300 set -f
1301 parse_arguments "$@"
1302 check_sanity
1303 set +f
1304
1305 eval ${MODE} ${ARGUMENTS}
1306
1307 # vim: set tabstop=4 shiftwidth=4: