1#!@SHELL@ 2 3: ${RCM_LIB:=$(dirname "$0")/../share/rcm} 4. "$RCM_LIB/rcm.sh" 5 6pushdir() { 7 DIR_STACK="$DIR_STACK:$PWD/$1" 8 $DEBUG "cd'ing to $1 from $PWD with stack $DIR_STACK" 9 cd -- "$1" 10} 11 12popdir() { 13 current="$(echo "$DIR_STACK" | sed -e 's/.*://g')" 14 prior="$(echo "$DIR_STACK" | sed -e "s|:$current$||" | sed -e 's/.*://g')" 15 DIR_STACK="$(echo "$DIR_STACK" | sed -e 's/:[^:]*$//')" 16 $DEBUG "cd'ing to $prior from $PWD with stack $DIR_STACK" 17 cd -- "$prior" 18} 19 20build_path() { 21 local dest="$1" 22 local file="$2" 23 local dotted=$3 24 25 if [ $dotted -eq 1 ]; then 26 echo "$dest/$file" 27 else 28 echo "$dest/.$file" 29 fi 30} 31 32file_join() { 33 local result= 34 35 for file; do 36 if [ "x$file" != "x." ]; then 37 if [ "x$result" = "x" ]; then 38 result="$file" 39 else 40 result="$result/$file" 41 fi 42 fi 43 done 44 45 echo "$result" 46} 47 48show_dir() { 49 local dir="$1" 50 local dest_dir="$2" 51 local dotfiles_dir="$3" 52 local dotfiles_subdir="$4" 53 local dotted=$5 54 local exclude_file_globs="$6" 55 local include_file_globs="$7" 56 local symlink_dirs_file_globs="$8" 57 local mk_dirs_file_globs="$9" 58 local dest_path="$(build_path "$dest_dir" "$dir" $dotted)" 59 60 $DEBUG "show_dir $1 $2 $3 $4 $5 $6 $7 $8 $9" 61 62 $VERBOSE "recurring on $dest_path" 63 64 pushdir "$dir" 65 for f in *; do 66 $DEBUG "handling the file $f" 67 next_dir="$(file_join "$dotfiles_subdir" "$dir")" 68 handle_file "$f" "$dest_path" "$dotfiles_dir" "$next_dir" 1 "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" 69 done 70 popdir 71} 72 73sigil_for() { 74 local file="$1" 75 local symlink_dirs_file_globs="$2" 76 local copy_always=0 77 local symlink_dirs=0 78 79 for copy_file in $COPY_ALWAYS; do 80 $DEBUG "copy_file: $copy_file" 81 $DEBUG "file: $file" 82 83 case "$file" in 84 $copy_file) 85 copy_always=1 86 break 87 ;; 88 esac 89 done 90 91 if [ -n "$symlink_dirs_file_globs" ]; then 92 symlink_dirs=1 93 fi 94 95 if [ $copy_always -eq 1 ]; then 96 echo 'X' 97 elif [ $symlink_dirs -eq 1 ]; then 98 echo '$' 99 else 100 echo '@' 101 fi 102} 103 104show_file() { 105 local file="$1" 106 local dest_dir="$2" 107 local dotfiles_dir="$3" 108 local dotfiles_subdir="$4" 109 local dotted=$5 110 local symlink_dirs_file_globs="$6" 111 local dest_file="$(build_path "$dest_dir" "$file" $dotted)" 112 local src_file="$(file_join "$dotfiles_subdir" "$file")" 113 local abs_src_file="$(file_join "$dotfiles_dir" "$src_file")" 114 local output="$dest_file:$abs_src_file" 115 116 if [ $SHOW_SIGILS -eq 1 ]; then 117 sigil="$(sigil_for "$src_file" "$symlink_dirs_file_globs")" 118 output="$output:$sigil" 119 fi 120 121 if echo "$DEST_STACK:" | grep -v ":$dest_file:" >/dev/null; then 122 DEST_STACK="$DEST_STACK:$dest_file" 123 $PRINT "$output" 124 else 125 $VERBOSE "skipping hidden file $output" 126 fi 127} 128 129handle_file() { 130 local file="$1" 131 local dest_dir="$2" 132 local dotfiles_dir="$3" 133 local dotfiles_subdir="$4" 134 local dotted=$5 135 local exclude_file_globs="$6" 136 local include_file_globs="$7" 137 local symlink_dirs_file_globs="$8" 138 local mk_dirs_file_globs="$9" 139 140 $DEBUG "handle_file $1 $2 $3 $4 $5 $6 $7 $8" "$9" 141 142 if [ ! -e "$file" ]; then 143 $VERBOSE "skipping non-existent file $file" 144 elif is_excluded "$dotfiles_subdir/$file" "$exclude_file_globs" "$include_file_globs"; then 145 $VERBOSE "skipping excluded file $file" 146 elif [ -d "$file" ] && is_excluded "$dotfiles_subdir/$file" "$symlink_dirs_file_globs" "$mk_dirs_file_globs"; then 147 show_file "$file" "$dest_dir" "$dotfiles_dir" "$dotfiles_subdir" $dotted "$symlink_dirs_file_globs" 148 elif [ -d "$file" ]; then 149 show_dir "$file" "$dest_dir" "$dotfiles_dir" "$dotfiles_subdir" $dotted "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" 150 else 151 show_file "$file" "$dest_dir" "$dotfiles_dir" "$dotfiles_subdir" $dotted 152 fi 153} 154 155is_metafile() { 156 host_portion="$(echo "$1" | sed -e 's/host-.*/host-/')" 157 tag_portion="$(echo "$1" | sed -e 's/tag-.*/tag-/')" 158 159 [ "x$host_portion" = 'xhost-' -o "x$tag_portion" = 'xtag-' -o "x$1" = "xhooks" ] 160} 161 162dotfiles_dir_excludes() { 163 local dotfiles_dir="$1" 164 local excludes="$2" 165 166 $DEBUG "dotfiles_dir_excludes $dotfiles_dir" 167 $DEBUG " with excludes: $excludes" 168 169 for exclude in "$excludes"; do 170 if echo "$exclude" | grep ':' >/dev/null; then 171 dotfiles_dir_pat="$(echo "$exclude" | sed 's/:.*//')" 172 file_glob="$(echo "$exclude" | sed 's/.*://')" 173 174 if [ "x$dotfiles_dir_pat" != "x*" ] && is_relative "$dotfiles_dir_pat"; then 175 dotfiles_dir_pat="$PWD/$dotfiles_dir_pat" 176 fi 177 178 if [ "x$dotfiles_dir_pat" = "x*" -o "x$dotfiles_dir_pat" = "x$dotfiles_dir" ]; then 179 echo "$file_glob" 180 fi 181 else 182 echo "$exclude" 183 fi 184 done 185} 186 187is_excluded() { 188 local file="$1" 189 local exclude_file_globs="$2" 190 local include_file_globs="$3" 191 local base_file="$(basename "$file")" 192 193 $DEBUG "is_excluded $file $exclude_file_globs $include_file_globs" 194 195 for exclude_file_glob in $exclude_file_globs; do 196 $DEBUG "file: $file" 197 $DEBUG "exclude_file_glob: $exclude_file_glob" 198 199 is_single_excluded "$file" "$exclude_file_glob" "$include_file_globs" 200 ret=$? 201 if [ $ret -eq 0 -o $ret -eq 1 ]; then 202 return $ret 203 fi 204 205 is_single_excluded "$base_file" "$exclude_file_glob" "$include_file_globs" 206 ret=$? 207 if [ $ret -eq 0 -o $ret -eq 1 ]; then 208 return $ret 209 fi 210 done 211 212 return 1 213} 214 215is_single_excluded() { 216 local file="$1" 217 local exclude_file_glob="$2" 218 local include_file_globs="$3" 219 220 case "$file" in 221 $exclude_file_glob) 222 for include_file_glob in $include_file_globs; do 223 case "$file" in 224 $include_file_glob) return 1;; 225 esac 226 done 227 228 return 0 229 ;; 230 esac 231 232 return 2 233} 234 235show_help() { 236 local exit_code=${1:-0} 237 238 $PRINT "Usage: lsrc [-FhqVv] [-B HOSTNAME] [-d DOT_DIR] [-I EXCL_PAT] [-S EXCL_PAT] [-s EXCL_PAT] [-t TAG] [-U EXCL_PAT] [-u EXCL_PAT] [-x EXCL_PAT]" 239 $PRINT "see lsrc(1) and rcm(7) for more details" 240 241 exit $exit_code 242} 243 244handle_command_line() { 245 local arg_tags= 246 local verbosity=0 247 local version=0 248 local show_sigils=0 249 local dotfiles_dirs= 250 local excludes= 251 local includes= 252 local symlink_dirs= 253 local never_symlink_dirs= 254 local hostname= 255 local undotted= 256 local never_undotted= 257 258 while getopts :FVqvhI:x:B:S:s:U:u:t:d: opt; do 259 case "$opt" in 260 F) show_sigils=1;; 261 h) show_help ;; 262 I) includes="$(append_variable "$includes" "$OPTARG")" ;; 263 t) arg_tags="$(append_variable "$arg_tags" "$OPTARG")" ;; 264 v) verbosity=$(($verbosity + 1));; 265 q) verbosity=$(($verbosity - 1));; 266 d) dotfiles_dirs="$(append_variable "$dotfiles_dirs" "$OPTARG")" ;; 267 V) version=1;; 268 x) excludes="$(append_variable "$excludes" "$OPTARG")" ;; 269 S) symlink_dirs="$(append_variable "$symlink_dirs" "$OPTARG")" ;; 270 s) never_symlink_dirs="$(append_variable "$never_symlink_dirs" "$OPTARG")" ;; 271 U) undotted="$(append_variable "$undotted" "$OPTARG")" ;; 272 u) never_undotted="$(append_variable "$never_undotted" "$OPTARG")" ;; 273 B) hostname="$OPTARG";; 274 ?) show_help 64 ;; 275 esac 276 done 277 shift $(($OPTIND-1)) 278 279 handle_common_flags lsrc $version $verbosity 280 HOSTNAME="$(determine_hostname "$hostname")" 281 SHOW_SIGILS=$show_sigils 282 TAGS="${arg_tags:-$TAGS}" 283 DOTFILES_DIRS="${dotfiles_dirs:-$DOTFILES_DIRS}" 284 EXCLUDES="${excludes:-$EXCLUDES}" 285 INCLUDES="${includes:-$INCLUDES}" 286 SYMLINK_DIRS="${symlink_dirs:-$SYMLINK_DIRS}" 287 MK_DIRS="${never_symlink_dirs:-$MK_DIRS}" 288 UNDOTTED="${undotted:-$UNDOTTED}" 289 NEVER_UNDOTTED="${never_undotted:-$NEVER_UNDOTTED}" 290 FILES="$@" 291 292 $DEBUG "TAGS: $TAGS" 293 $DEBUG "DOTFILES_DIRS: $DOTFILES_DIRS" 294} 295 296DEST_STACK= 297 298handle_command_line "$@" 299 300: ${DOTFILES_DIRS:=$DOTFILES_DIRS $DEFAULT_DOTFILES_DIR} 301$DEBUG "DOTFILES_DIRS: $DOTFILES_DIRS" 302 303: ${COPY_ALWAYS:=""} 304$DEBUG "COPY_ALWAYS: $COPY_ALWAYS" 305 306: ${SYMLINK_DIRS:=""} 307$DEBUG "SYMLINK_DIRS: $SYMLINK_DIRS" 308 309: ${MK_DIRS:=""} 310$DEBUG "MK_DIRS: $MK_DIRS" 311 312: ${UNDOTTED:=""} 313$DEBUG "UNDOTTED: $UNDOTTED" 314 315: ${NEVER_UNDOTTED:=""} 316$DEBUG "NEVER_UNDOTTED: $NEVER_UNDOTTED" 317 318relative_root_dir="$PWD" 319 320for DOTFILES_DIR in $DOTFILES_DIRS; do 321 cd -- "$relative_root_dir" 322 323 DOTFILES_DIR="$(eval echo "$DOTFILES_DIR")" 324 325 if is_relative $DOTFILES_DIR; then 326 DOTFILES_DIR="$PWD/$DOTFILES_DIR" 327 fi 328 329 if [ ! -d "$DOTFILES_DIR" ]; then 330 $VERBOSE "skipping non-existent directory: $DOTFILES_DIR" 331 continue 332 fi 333 334 exclude_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$EXCLUDES")" 335 $DEBUG "exclude_file_globs: $exclude_file_globs" 336 include_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$INCLUDES")" 337 symlink_dirs_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$SYMLINK_DIRS")" 338 $DEBUG "symlink_dirs_file_globs: $symlink_dirs_file_globs" 339 mk_dirs_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$MK_DIRS")" 340 $DEBUG "mk_dirs_file_globs: $mk_dirs_file_globs" 341 undotted_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$UNDOTTED")" 342 $DEBUG "undotted_file_globs: $undotted_file_globs" 343 never_undotted_file_globs="$(dotfiles_dir_excludes "$DOTFILES_DIR" "$NEVER_UNDOTTED")" 344 $DEBUG "never_undotted_file_globs: $never_undotted_file_globs" 345 346 cd -- "$DOTFILES_DIR" 347 DIR_STACK=":$DOTFILES_DIR" 348 349 host_files="$DOTFILES_DIR/host-$HOSTNAME" 350 if [ -d "$host_files" ]; then 351 pushdir "$(basename "$host_files")" 352 for escaped_file in ${FILES:-*}; do 353 file="$(decode "$escaped_file")" 354 dotted=0 355 if is_excluded "$file" "$undotted_file_globs" "$never_undotted_file_globs"; then 356 dotted=1 357 fi 358 359 handle_file "$file" "$DEST_DIR" "$host_files" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" 360 done 361 popdir 362 fi 363 364 cd -- "$DOTFILES_DIR" 365 366 for tag in $TAGS; do 367 if [ -d "tag-$tag" ]; then 368 pushdir "$(basename "tag-$tag")" 369 for escaped_file in ${FILES:-*}; do 370 file="$(decode "$escaped_file")" 371 $DEBUG "TAG: $tag, exclude_file_globs: $exclude_file_globs" 372 dotted=0 373 if is_excluded "$file" "$undotted_file_globs" "$never_undotted_file_globs"; then 374 dotted=1 375 fi 376 handle_file "$file" "$DEST_DIR" "$DOTFILES_DIR/tag-$tag" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" 377 done 378 popdir 379 fi 380 done 381 382 cd -- "$DOTFILES_DIR" 383 384 for escaped_file in ${FILES:-*}; do 385 file="$(decode "$escaped_file")" 386 dotted=0 387 if is_metafile "$file"; then 388 continue 389 fi 390 391 if is_excluded "$file" "$undotted_file_globs" "$never_undotted_file_globs"; then 392 dotted=1 393 fi 394 395 handle_file "$file" "$DEST_DIR" "$DOTFILES_DIR" . "$dotted" "$exclude_file_globs" "$include_file_globs" "$symlink_dirs_file_globs" "$mk_dirs_file_globs" 396 done 397done 398