Sanity check path rules with embedded variables.
[profile.git] / .profile.d / PATH.bashrc
1 # Path information is stored on separate lines in XXXdirs.
2 # We check each directory exists and add it to the appropriate PATH.
3 # Prepend paths with XXXdirs.pre.  They are guaranteed to precede other paths.
4 # Append paths with XXXdirs.post.  They are not guaranteed to follow others.
5 # Set per-OS files in $SYSTEM[/$ARCHITECTURE] subdirectories of $DIR.
6 #
7
8 # Location of the XXXdirs files.
9 DIR=${PROFILE_HOME:-~}/.PATH
10
11 # Paths to set and the file to get them from @variable to copy from.
12 PATHS="
13 PATH:bindirs
14 C_INCLUDE_PATH:incdirs
15 CPLUS_INCLUDE_PATH:@C_INCLUDE_PATH
16 LD_LIBRARY_PATH:libdirs
17 LD_RUN_PATH:@PATH
18 MANPATH:mandirs
19 PKG_CONFIG_PATH:pkgdirs
20 "
21
22 function sanitisepath() {
23   local paths="${@//:/ }"
24   local sane
25
26   for path in $paths; do
27     paths="${paths## }"
28     paths="${paths%% }"
29     paths="${paths#$path} "
30     if [ "${sane/:$path:/}" = "$sane" ]; then
31       sane="$sane:$path:"
32     fi
33   done
34
35   sane="${sane//::/:}"
36   sane="${sane#:}"
37   sane="${sane%:}"
38
39   echo "$sane"
40 }
41
42 # Set one path to be the same as another.
43 function copypath() {
44   local newpath="$1"; shift
45   local oldpath="$1"; shift
46
47   # Sanitise and export.
48   local path="$(eval echo \$$oldpath)"
49   [ -z "$path" ] || eval "export $newpath='$path'"
50 }
51
52 # Try to expand variables in a path definition, discarding it if expansion fails.
53 function expandpath() {
54   local path="$1"; shift
55
56   while [ -n "$path" ]; do
57     [ "${path/\$/}" = "$path" ] && return 0
58     path="${path#*\$}"
59
60     local var="${path%%/*}"
61     eval "[ -n \"\$$var\" ] || return 1"
62   done
63
64   return 0
65 }
66
67 # Helper.
68 function addpath() {
69   local path="$1"; shift
70   local dir="$1"; shift
71
72   if ! expandpath "$dir"; then
73     echo "$path"
74   else
75     dir=$(eval echo "$dir")
76     if [ -d "$dir" ]; then
77       echo "$path:$dir"
78     else
79       echo "$path"
80     fi
81   fi
82 }
83
84 # Set a path from directories.
85 function makepath() {
86   local newpath="$1"; shift
87   local dirs="$1"; shift
88
89   # Set IFS to newline only so that we can read $(embedded shell commands).
90   local JGD=$IFS
91   IFS='
92 '
93   # Read them.
94   local path=
95   while read dir; do
96     path=$(addpath "$path" "$dir")
97     if [ -n "$PROFILE_HOME" -a ! "${dir#\$HOME}" = "$dir" ]; then
98       dir="$PROFILE_HOME${dir#\$HOME}"
99       path=$(addpath "$path" "$dir")
100     fi
101   done < "$dirs"
102
103   # Restore IFS.
104   IFS=$JGD
105
106   # Are we setting, prepending or appending?
107   local existing="$(eval echo \$$newpath)"
108   dirs="${dirs##*/}"
109   local where="${dirs##*.}"
110   case "$where" in
111     "$dirs") ;;
112     pre) path="$path:$existing";;
113     post) path="$existing:$path";;
114     *) return;;
115   esac
116
117   # Sanitise path.
118   path=$(sanitisepath "$path")
119   [ -z "$path" ] && return
120
121   # Export.
122   eval "export $newpath='$path'"
123 }
124
125 # Construct directory list, omitting nonexistent and undefined ones.
126 dirs=
127 for dir in "${SYSTEM:-@}/${ARCHITECTURE:-@}" "${SYSTEM:-@}" ""; do
128   [ "${dir/@/}" = "$dir" ] || continue
129   [ -d "$DIR/$dir" ] || continue
130   dirs="$dirs,$DIR/$dir"
131   dirs="${dirs%%/}"
132 done
133 dirs="${dirs##,}"
134 [ "${dirs/,/}" = "$dirs" ] || dirs="{$dirs}"
135
136 for path in $PATHS; do
137   var="${path%:*}"
138   source="${path#*:}"
139
140   if [ "${source#@}" = "$source" ]; then
141     for dir in $(eval echo $dirs/$source{,.pre,.post}); do
142       [ -e "$dir" ] || continue
143       makepath "$var" "$dir"
144     done
145   else
146     copypath "$var" "${source#@}"
147   fi
148 done
149
150
151 unset DIR PATHS dir dirs path var source expandpath sanitisepath copypath makepath newpath addpath