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