|
#!/usr/bin/env bash |
|
|
|
set -euo pipefail |
|
|
|
absolute-path-of() { |
|
echo $(cd "$(dirname "$1")" &>/dev/null && pwd)/$(basename "$1") |
|
} |
|
|
|
PROJECT_DIR=$(dirname $(dirname $(absolute-path-of $0))) |
|
|
|
source "$PROJECT_DIR/bin/helpers.sh" |
|
|
|
GIT_NAME= |
|
GIT_EMAIL= |
|
GITHUB_USER= |
|
GITHUB_TOKEN= |
|
DRY_RUN=0 |
|
FORCE_ALL=0 |
|
FORCE_TEMPLATES=0 |
|
VERBOSE=0 |
|
|
|
announce() { |
|
local subaction="$1" |
|
local action="$2" |
|
local source_path= destination_path= |
|
local color= |
|
|
|
if [[ $# -eq 4 ]]; then |
|
source_path="${3/$PROJECT_DIR/\$DOTFILES}" |
|
destination_path="${4/$HOME/~}" |
|
else |
|
destination_path="${3/$HOME/~}" |
|
fi |
|
|
|
if [[ -d $source_path ]]; then |
|
source_path="${source_path}/" |
|
fi |
|
|
|
if [[ -d $destination_path ]]; then |
|
destination_path="${destination_path}/" |
|
fi |
|
|
|
case $action in |
|
create) |
|
color=green |
|
;; |
|
overwrite) |
|
color=red |
|
;; |
|
exists | same | unknown) |
|
color=blue |
|
;; |
|
esac |
|
|
|
local colorized_action=$(colorize $color "$(printf "%8s" "$action")") |
|
local colorized_subaction=$(colorize yellow "$(printf "%5s" "$subaction")") |
|
|
|
local prefix="${colorized_action} ${colorized_subaction}" |
|
|
|
if [[ $subaction == "gen" ]]; then |
|
echo "${prefix} ${source_path} ==> ${destination_path}" |
|
elif [[ $source_path ]]; then |
|
echo "${prefix} ${destination_path} --> ${source_path}" |
|
else |
|
echo "${prefix} ${destination_path}" |
|
fi |
|
} |
|
|
|
print-help() { |
|
cat <<TEXT |
|
$(colorize bold "## DESCRIPTION") |
|
|
|
This script will create symlinks in your home folder based on the contents of |
|
the src/ directory. This directory is iterated over, and one of a few things |
|
happens depending on what it is: |
|
|
|
* For any file in src/, the script will create a symlink in your home folder |
|
that points to this file. |
|
EXAMPLE: for src/tmux.conf, ~/.tmux.conf is created that points to this file. |
|
* For any directory in src/, the script will recurse the directory and create |
|
symlinks inside of it according to the previous rule. |
|
EXAMPLE: for src/rbenv/default-gems, ~/.rbenv/default-gems is created that |
|
points to this file. |
|
|
|
There are a couple of exceptions to this: |
|
|
|
* For a file anywhere in src/ that ends in .erb, the script will evaluate |
|
that file as an ERB template, producing another file instead of creating a |
|
symlink. (\`args\` within a template refers to the OPTIONS below.) |
|
EXAMPLE: for src/gitconfig.erb, it is run through ERB to produce ~/.gitconfig. |
|
* For any directory in src/ that contains a .no-recurse file, the script will |
|
NOT recurse the directory; instead, it will create a symlink for the |
|
directory. |
|
EXAMPLE: for src/zsh, because it contains a .no-recurse file, ~/.zsh is |
|
created that points to this directory. |
|
|
|
The script takes care not to overwrite any existing files, unless you specify |
|
--force or --force-templates. |
|
|
|
Finally, if you want to know what the script will do before running it for real, |
|
and especially if this is the first time you're running this script, use the |
|
--dry-run option. For further output, use the --verbose option. |
|
|
|
$(colorize bold "## USAGE") |
|
|
|
$0 [FIRST_TIME_OPTIONS] [OTHER_OPTIONS] |
|
|
|
FIRST_TIME_OPTIONS are one or more of: |
|
|
|
--git-email EMAIL |
|
The email that you'll use to author Git commits. |
|
--git-name NAME |
|
The name that you'll use to author Git commits. |
|
--github-user |
|
Your username on GitHub. Used by tools like GitX. |
|
--github-token |
|
Your API token on GitHub. Used by tools like GitX. |
|
|
|
OTHER_OPTIONS are one or more of: |
|
|
|
--dry-run, --noop, -n |
|
Don't actually create any symlinks or write any files. |
|
--force-templates |
|
Usually dotfiles generated from templates that already exist are not |
|
overwritten. This bypasses that. |
|
--force, -f |
|
Usually dotfiles that already exist are not overwritten. This bypasses that. |
|
(Implies --force-templates.) |
|
--verbose, -V |
|
Show every command that is run when it is run. |
|
--help, -h |
|
You're looking at it ;) |
|
TEXT |
|
} |
|
|
|
parse-args() { |
|
if [[ $# -eq 0 ]]; then |
|
error "No arguments given." |
|
echo "\ |
|
Please run --help for usage. (If this is the first time you're running this, |
|
take special note of FIRST_TIME_OPTIONS!)" |
|
exit 1 |
|
fi |
|
|
|
while [[ ${1:-} ]]; do |
|
local arg="${1:-}" |
|
case "$arg" in |
|
--git-email) |
|
GIT_EMAIL="$2" |
|
shift 2 |
|
;; |
|
--git-name) |
|
GIT_NAME="$2" |
|
shift 2 |
|
;; |
|
--github-user) |
|
GITHUB_USER="$2" |
|
shift 2 |
|
;; |
|
--github-token) |
|
GITHUB_TOKEN="$2" |
|
shift 2 |
|
;; |
|
--dry-run | --noop | -n) |
|
DRY_RUN=1 |
|
shift |
|
;; |
|
--force | -f) |
|
FORCE_ALL=1 |
|
shift |
|
;; |
|
--force-templates) |
|
FORCE_TEMPLATES=1 |
|
shift |
|
;; |
|
--verbose | -V) |
|
VERBOSE=1 |
|
shift |
|
;; |
|
--help | -h | -?) |
|
print-help | more -R |
|
exit |
|
;; |
|
*) |
|
error "Unknown argument '$arg' given." |
|
echo "Please run --help for usage." |
|
exit 1 |
|
esac |
|
done |
|
} |
|
|
|
generate-from-template() { |
|
local full_source_path="$1" |
|
local full_destination_path="$2" |
|
|
|
if [[ $VERBOSE -eq 1 ]]; then |
|
eval inspect-command bin/generate-from-template '"$full_source_path"' '"$full_destination_path"' "${GIT_NAME:+GIT_NAME=\"\\\"\$GIT_NAME\\\"\"}" "${GIT_EMAIL:+GIT_EMAIL=\"\\\"\$GIT_EMAIL\\\"\"}" "${GITHUB_USER:+GITHUB_USER=\"\\\"\$GITHUB_USER\\\"\"}" "${GITHUB_TOKEN:+GITHUB_TOKEN=\"\\\"\$GITHUB_TOKEN\\\"\"}" |
|
fi |
|
|
|
if [[ $DRY_RUN -eq 0 ]]; then |
|
eval bin/generate-from-template '"$full_source_path"' '"$full_destination_path"' "${GIT_NAME:+GIT_NAME=\"\$GIT_NAME\"}" "${GIT_EMAIL:+GIT_EMAIL=\"\$GIT_EMAIL\"}" "${GITHUB_USER:+GITHUB_USER=\"\$GITHUB_USER\"}" "${GITHUB_TOKEN:+GITHUB_TOKEN=\"\$GITHUB_TOKEN\"}" |
|
fi |
|
} |
|
|
|
always-generate-from-template() { |
|
local old_dry_run=$DRY_RUN |
|
local verbose=$VERBOSE |
|
DRY_RUN=0 |
|
VERBOSE=0 |
|
generate-from-template "$@" |
|
DRY_RUN=$old_dry_run |
|
VERBOSE=$verbose |
|
} |
|
|
|
link-file() { |
|
local full_source_path="$1" |
|
local full_destination_path="$2" |
|
|
|
if [[ $VERBOSE -eq 1 ]]; then |
|
inspect-command mkdir -p $(dirname "$full_destination_path") |
|
inspect-command ln -s "$full_source_path" "$full_destination_path" |
|
fi |
|
|
|
if [[ $DRY_RUN -eq 0 ]]; then |
|
mkdir -p $(dirname "$full_destination_path") |
|
ln -s "$full_source_path" "$full_destination_path" |
|
fi |
|
} |
|
|
|
process-previously-generated-template() { |
|
local full_source_path="$1" |
|
local full_destination_path="$2" |
|
|
|
always-generate-from-template "$full_source_path" "$generated_file_path" |
|
|
|
if files-equal "$generated_file_path" "$full_destination_path"; then |
|
announce gen same "$full_source_path" "$full_destination_path" |
|
elif [[ $FORCE_TEMPLATES -eq 1 || $FORCE_ALL -eq 1 ]]; then |
|
announce gen overwrite "$full_source_path" "$full_destination_path" |
|
else |
|
announce entry unknown "$full_destination_path" |
|
fi |
|
} |
|
|
|
process-template() { |
|
local full_source_path="$1" |
|
local full_source_path_without_extension="${full_source_path%.erb}" |
|
local destination_path="${full_source_path_without_extension#$PROJECT_DIR/src/}" |
|
local full_destination_path=$(build-destination-path "$destination_path") |
|
local generated_file_path="/tmp/setup_script_generator/generated-file" |
|
|
|
if [[ -e $full_destination_path ]]; then |
|
if [[ -f "${full_destination_path}.context" ]]; then |
|
process-previously-generated-template "$full_source_path" "$full_destination_path" |
|
elif [[ $FORCE_TEMPLATES -eq 1 || $FORCE_ALL -eq 1 ]]; then |
|
announce gen overwrite "$full_source_path" "$full_destination_path" |
|
generate-from-template "$full_source_path" "$full_destination_path" |
|
else |
|
announce entry exists "$full_source_path" "$full_destination_path" |
|
fi |
|
else |
|
announce gen create "$full_source_path" "$full_destination_path" |
|
generate-from-template "$full_source_path" "$full_destination_path" |
|
fi |
|
} |
|
|
|
process-link() { |
|
local full_source_path="$1" |
|
local destination_path="${full_source_path#$PROJECT_DIR/src/}" |
|
local full_destination_path=$(build-destination-path "$destination_path") |
|
|
|
if [[ -e $full_destination_path ]]; then |
|
if [[ $FORCE_ALL -eq 1 ]]; then |
|
announce link overwrite "$full_source_path" "$full_destination_path" |
|
link-file "$full_source_path" "$full_destination_path" |
|
else |
|
announce link exists "$full_source_path" "$full_destination_path" |
|
fi |
|
else |
|
announce link create "$full_source_path" "$full_destination_path" |
|
link-file "$full_source_path" "$full_destination_path" |
|
fi |
|
} |
|
|
|
process-entry() { |
|
local source_path="$1" |
|
local dir="$2" |
|
local full_source_path=$(absolute-path-of "$dir/$source_path") |
|
|
|
if [[ -d $full_source_path && ! -e "$full_source_path/.no-recurse" ]]; then |
|
recurse-dir "$full_source_path" |
|
elif [[ $full_source_path =~ \.erb$ ]]; then |
|
process-template "$full_source_path" |
|
else |
|
process-link "$full_source_path" |
|
fi |
|
} |
|
|
|
main() { |
|
parse-args "$@" |
|
|
|
if [[ $DRY_RUN -eq 1 ]]; then |
|
info "Running in dry-run mode." |
|
echo |
|
fi |
|
|
|
recurse-dir "$PROJECT_DIR/src" |
|
|
|
if [[ $DRY_RUN -eq 1 ]]; then |
|
echo |
|
info "Don't worry — nothing was written to the filesystem!" |
|
else |
|
echo |
|
success "All files are installed, you're good!" |
|
echo "(Not the output you expect? Run --force or --force-templates to force-update skipped files.)" |
|
fi |
|
} |
|
|
|
main "$@" |
Sexy bash script