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