/
fresh
executable file
·1021 lines (891 loc) · 26.1 KB
/
fresh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bash
set -e
FRESH_RCFILE="${FRESH_RCFILE:-$HOME/.freshrc}"
FRESH_PATH="${FRESH_PATH:-$HOME/.fresh}"
FRESH_LOCAL="${FRESH_LOCAL:-$HOME/.dotfiles}"
FRESH_BIN_PATH="${FRESH_BIN_PATH:-$HOME/bin}"
set -o pipefail
set -o nounset
IN_RC_FILE=0
SKIP_INFO=0
_prefix_match() {
local glob="$2*"
[[ "$1" == $glob ]] && echo "$1" | awk 'index($0,x)==1' RS= x="$2" | grep -q '^'
}
fresh_install() {
# create new output directory
umask 0077
[ ! -e "$FRESH_PATH/build.new" ] || rm -rf "$FRESH_PATH/build.new"
mkdir -p "$FRESH_PATH/build.new"
_run_dsl install
_fresh_preamble
# safety check to ensure the user doesn't lock themselves out
if [[ -z "${FRESH_NO_BIN_CHECK:-}" ]] && [[ ! -x "$FRESH_PATH/build.new/bin/fresh" ]]; then
_fatal_error "It looks you do not have fresh in your freshrc file. This could result
in difficulties running \`fresh\` later. You probably want to add a line like
the following using \`fresh edit\`:
fresh freshshell/fresh bin/fresh --bin
To disable this error, add \`FRESH_NO_BIN_CHECK=true\` in your freshrc file."
fi
# output files should be read-only
find "$FRESH_PATH/build.new" -type f -exec chmod -w {} \;
# clean up any leftovers from previous runs
rm -rf "$FRESH_PATH/build.old"
rm -f "$FRESH_PATH/.tmp.old-bin"
# make room for the new build
if [[ -e "$FRESH_PATH/build" ]]; then
# move the running fresh binary to a safe place
# (Windows doesn't let us rename dirs with open files)
if [[ -e "$FRESH_PATH/build/bin/fresh" ]]; then
mv -f "$FRESH_PATH/build/bin/fresh" "$FRESH_PATH/.tmp.old-bin"
fi
# move the old build dir out of the way
mv "$FRESH_PATH/build" "$FRESH_PATH/build.old"
# keep the old build dir in a usable state until the new one is in place
if [[ -e "$FRESH_PATH/build.old/bin/fresh" ]]; then
mv -f "$FRESH_PATH/.tmp.old-bin" "$FRESH_PATH/build.old/bin/fresh"
fi
fi
# move the new build into place
mv "$FRESH_PATH/build.new" "$FRESH_PATH/build"
# remove the old still running fresh binary
# (we do this outside the build dir to avoid NFS unlink files getting in the way)
if [[ -e "$FRESH_PATH/build.old/bin/fresh" ]]; then
mv -f "$FRESH_PATH/build.old/bin/fresh" "$FRESH_PATH/.tmp.old-bin"
rm -f "$FRESH_PATH/.tmp.old-bin"
fi
# remove the old build dir
rm -rf "$FRESH_PATH/build.old"
fresh_after_build
# success!
echo $'Your dot files are now \033[1;32mfresh\033[0m.'
}
# placeholder method that can be overridden in your freshrc
fresh_after_build() {
true
}
_fresh_preamble() {
if [ -z "${__FRESH_PREAMBLE_DONE__:-}" ]; then
if [ -z "${FRESH_NO_PATH_EXPORT:-}" ]; then
echo "__FRESH_BIN_PATH__="${FRESH_BIN_PATH/#$HOME/\$HOME}"; [[ ! \$PATH =~ (^|:)\$__FRESH_BIN_PATH__(:|\$) ]] && export PATH=\"\$__FRESH_BIN_PATH__:\$PATH\"; unset __FRESH_BIN_PATH__" >> "$FRESH_PATH/build.new/shell.sh"
fi
echo "export FRESH_PATH=\"$FRESH_PATH\"" >> "$FRESH_PATH/build.new/shell.sh"
__FRESH_PREAMBLE_DONE__=1
fi
}
_dsl_install_fresh() {
_set_dsl_caller
_parse_fresh_dsl_args "$@"
_fresh_preamble
_prepare_file_source
case "${MODE:-shell}" in
shell)
_fresh_shell
;;
file)
_fresh_file
;;
bin)
_fresh_bin
;;
*)
_fatal_error "Unknown mode: $MODE"
;;
esac
}
_repo_url() {
local REPO_NAME="$1"
if echo "$REPO_NAME" | grep -q :; then
echo "$REPO_NAME"
else
echo "https://github.com/$REPO_NAME"
fi
}
_repo_name() {
local REPO_NAME="$1"
if echo "$REPO_NAME" | grep -q :; then
REPO_NAME="$(echo "$REPO_NAME" | sed -e 's#^.*@##' -e 's#^.*://##' -e 's#:#/#' -e 's/\.git$//')"
fi
if [[ "$REPO_NAME" == github.com/* ]]; then
REPO_NAME="$(echo "$REPO_NAME" | sed 's#^github\.com/##')"
else
REPO_NAME="$(echo "$REPO_NAME" | cut -d/ -f1)/$(echo "$REPO_NAME" | cut -d/ -f2- | tr / -)"
fi
echo "$REPO_NAME"
}
_prepare_file_source() {
# clone or update source repo
if [ -n "$REPO_NAME" ]; then
if [ -d "$FRESH_LOCAL/.git" ] && [ -z "${FRESH_NO_LOCAL_CHECK:-}" ]; then
if [ -z "${LOCAL_REPO_URL:-}" ]; then
local UPSTREAM_BRANCH="$(cd "$FRESH_LOCAL" && git rev-parse --abbrev-ref --symbolic-full-name @{u} 2> /dev/null)"
local UPSTREAM_REMOTE="$(echo "$UPSTREAM_BRANCH" | cut -d/ -f1)"
if [ -n "$UPSTREAM_REMOTE" ]; then
LOCAL_REPO_URL="$(cd "$FRESH_LOCAL" && git config --get remote.$UPSTREAM_REMOTE.url)"
else
LOCAL_REPO_URL=
fi
fi
LOCAL_REPO_NAME="$(_repo_name "$LOCAL_REPO_URL")"
SOURCE_REPO_NAME="$(_repo_name "$REPO_NAME")"
if [[ "$LOCAL_REPO_NAME" == "$SOURCE_REPO_NAME" ]]; then
_note "You seem to be sourcing your local files remotely."
cat <<EOF
$(_rc_file_line_reference)
You can remove "$REPO_NAME" when sourcing from your local dotfiles repo (${FRESH_LOCAL/#$HOME/~}).
Use \`fresh file\` instead of \`fresh $REPO_NAME file\`.
To disable this warning, add \`FRESH_NO_LOCAL_CHECK=true\` in your freshrc file.
EOF
FRESH_NO_LOCAL_CHECK=1
fi
fi
local REPO_DIR="$FRESH_PATH/source/$(_repo_name "$REPO_NAME")"
mkdir -p "$(dirname "$REPO_DIR")"
if ! [ -e "$REPO_DIR" ]; then
git clone "$(_repo_url "$REPO_NAME")" "$REPO_DIR"
fi
SOURCE_DIR="$REPO_DIR"
else
SOURCE_DIR="$FRESH_LOCAL"
if [[ "$FILE_NAME" == '.' ]]; then
_fatal_error "Cannot source whole of local dotfiles."
fi
fi
# check the source file exists
if [[ $(find "$SOURCE_DIR" -path "$SOURCE_DIR/$FILE_NAME" | wc -l) -lt 1 ]]; then
# but not if we are locking to a git ref
# since we would be checking the working tree
if [[ ! -n "$REF" ]] && [[ "$FILE_NAME" != '.' ]] && [[ -z "$IGNORE_MISSING" ]]; then
_fatal_error "Could not find \"$FILE_NAME\" source file."
fi
fi
}
_file_list() {
if [[ "$FILE_NAME" == '.' ]]; then
if [[ "$MODE" != file ]]; then
_fatal_error "Whole repositories can only be sourced in file mode."
fi
if [[ "$MODE_ARG" != */ ]]; then
_fatal_error "Whole repositories require destination to be a directory."
fi
fi
if [[ -n "$REF" ]]; then
cd "$SOURCE_DIR"
local MATCHED=0
local TEMP_ORDER_FILE="$(mktemp "${TMPDIR:-/tmp}/fresh.XXXXXX")"
if [[ "$FILE_NAME" == *\** ]]; then
(git show "$REF:$(dirname "$FILE_NAME")/.fresh-order") > "$TEMP_ORDER_FILE"
fi
while read FILE_PATH; do
if [[ "$FILE_NAME" == '.' ]]; then
echo "$FILE_PATH"
MATCHED=1
else
if [[ "$FILE_PATH" == $FILE_NAME ]]; then
if ! echo "${FILE_PATH#$FILE_NAME}" | grep -q /; then
if [[ "$(basename "$FILE_PATH")" != .* ]] || [[ "$(basename "$FILE_NAME")" == .* ]]; then
echo "$FILE_PATH"
MATCHED=1
fi
fi
fi
if _prefix_match "$FILE_PATH" "$FILE_NAME/"; then
echo "$FILE_PATH"
MATCHED=1
fi
fi
done < <(git ls-tree -r --name-only "$REF" | _file_list_sort "$(dirname "$FILE_NAME")" "$TEMP_ORDER_FILE")
rm "$TEMP_ORDER_FILE"
cd "$OLDPWD"
if [[ "$MATCHED" == 0 ]] && [[ -z "$IGNORE_MISSING" ]]; then
_fatal_error "Could not find \"$FILE_NAME\" source file."
fi
else
if [[ "$FILE_NAME" == '.' ]]; then
find "$SOURCE_DIR" -type f -not -path "$SOURCE_DIR/.git/*" | sort
else
local path_glob="$SOURCE_DIR/$FILE_NAME"
if basename "$FILE_NAME" | grep -q '^\.'; then
local not_name_glob=''
else
local not_name_glob='.*'
fi
if [[ -d $path_glob ]]; then
find "$path_glob" -type f -not -name "$not_name_glob"
else
find "$SOURCE_DIR" -path "$path_glob" -not -name "$not_name_glob" -type f | while read FILE_PATH; do
if ! echo "${FILE_PATH#$path_glob}" | grep -q /; then
echo "$FILE_PATH"
fi
done | _file_list_sort "$(dirname "$path_glob")" "$(dirname "$path_glob")/.fresh-order"
fi
fi
fi
}
_file_list_sort() {
local PREFIX="$1"
local ORDER_FILE="$2"
if [[ -s "$ORDER_FILE" ]]; then
local TEMP_PATHS_FILE="$(mktemp "${TMPDIR:-/tmp}/fresh.XXXXXX")"
cat > "$TEMP_PATHS_FILE"
local TEMP_ORDER_FILE="$(mktemp "${TMPDIR:-/tmp}/fresh.XXXXXX")"
cat "$ORDER_FILE" | while read NAME; do
echo "$PREFIX/$NAME" >> "$TEMP_ORDER_FILE"
done
(grep -xFf "$TEMP_PATHS_FILE" "$TEMP_ORDER_FILE" || true)
(grep -vxFf "$TEMP_ORDER_FILE" "$TEMP_PATHS_FILE" || true) | sort
rm "$TEMP_PATHS_FILE" "$TEMP_ORDER_FILE"
else
sort
fi | (grep -v '\.fresh-order$' || true)
}
_source_file_contents() {
if [[ -n "$REF" ]]; then
cd "$SOURCE_DIR"
if [[ -n "$FILTER" ]]; then
git show "$REF:$SOURCE_FILE" | eval "$FILTER"
else
git show "$REF:$SOURCE_FILE"
fi
cd "$OLDPWD"
else
if [[ -n "$FILTER" ]]; then
cat "$SOURCE_FILE" | eval "$FILTER"
else
cat "$SOURCE_FILE"
fi
fi
}
_source_file_url() {
if [[ -n "$REPO_NAME" ]]; then
if echo "$REPO_NAME" | grep -q :; then
echo "$(_repo_url "$REPO_NAME")"
elif [[ -n "$REF" ]]; then
_github_blob_url "$REPO_NAME" "$REF" "$SOURCE_FILE"
else
local file="${SOURCE_FILE#$SOURCE_DIR/}"
cd "$SOURCE_DIR"
local ref="$(git log --pretty="%H" -n 1 -- "$file")"
cd "$OLDPWD"
_github_blob_url "$REPO_NAME" "$ref" "$file"
fi
else
echo "$SOURCE_FILE"
fi
}
_github_blob_url() {
local REPO_NAME="$1"
local REF="$2"
local BLOB_PATH="$3"
echo "https://github.com/$REPO_NAME/blob/$REF/$BLOB_PATH"
}
_file_marker() {
echo -n "$MARKER fresh:"
if [[ -n "$REPO_NAME" ]]; then
echo -n " $REPO_NAME"
fi
echo -n " ${SOURCE_FILE#$SOURCE_DIR/}"
if [[ -n "$REF" ]]; then
echo -n " @ $REF"
fi
if [[ -n "$FILTER" ]]; then
echo -n " # $FILTER"
fi
echo
}
_fresh_shell() {
_file_list | while read SOURCE_FILE
do
echo >> "$FRESH_PATH/build.new/shell.sh"
MARKER='#' _file_marker >> "$FRESH_PATH/build.new/shell.sh"
echo >> "$FRESH_PATH/build.new/shell.sh"
_source_file_contents >> "$FRESH_PATH/build.new/shell.sh"
done
}
_fresh_clean_path() {
sed -e 's/^~\///' -e 's/\/$//' -e 's/[/ ()]/-/g' -e 's/--*/-/g' -e 's/-$//' -e 's/^\.//'
}
_fresh_file() {
_file_list | while read SOURCE_FILE
do
if [ -n "$MODE_ARG" ]; then
if echo "$MODE_ARG" | grep -q '/$'; then
if [[ "$FILE_NAME" == '.' ]]; then
local base_path=
else
local base_path="$FILE_NAME/"
fi
if [[ -z "$REF" ]]; then
local base_path="$SOURCE_DIR/$base_path"
fi
if echo "$MODE_ARG" | grep -q '^[~/]'; then
local dest_dir="$(echo "$MODE_ARG" | _fresh_clean_path)"
DEST_NAME="$dest_dir/${SOURCE_FILE#$base_path}"
SYMLINK_SOURCE="$dest_dir"
SYMLINK_PATH="${MODE_ARG/%\//}"
else
DEST_NAME="$MODE_ARG${SOURCE_FILE#$base_path}"
SYMLINK_SOURCE=""
SYMLINK_PATH=""
fi
elif echo "$MODE_ARG" | grep -q '^[~/]'; then
DEST_NAME="$(echo "$MODE_ARG" | _fresh_clean_path)"
SYMLINK_SOURCE="$DEST_NAME"
SYMLINK_PATH="$MODE_ARG"
else
if echo "$MODE_ARG" | grep -q '^\.\.'; then
_fatal_error "Relative paths must be inside build dir."
fi
DEST_NAME="$MODE_ARG"
SYMLINK_SOURCE=""
SYMLINK_PATH=""
fi
else
DEST_NAME="$(basename "$SOURCE_FILE" | sed 's/^\.//')"
SYMLINK_SOURCE="$DEST_NAME"
SYMLINK_PATH="~/.$DEST_NAME"
fi
_fresh_output
_fresh_symlink
done
}
_fresh_bin() {
_file_list | while read SOURCE_FILE
do
if [ -n "$MODE_ARG" ]; then
if echo "$MODE_ARG" | grep -q '^[~/]'; then
DEST_NAME="bin/$(basename "$MODE_ARG")"
SYMLINK_SOURCE="$DEST_NAME"
SYMLINK_PATH="$MODE_ARG"
else
_fatal_error "--bin file paths cannot be relative."
fi
else
DEST_NAME="bin/$(basename "$SOURCE_FILE")"
SYMLINK_SOURCE="$DEST_NAME"
SYMLINK_PATH="$FRESH_BIN_PATH/$(basename "$SOURCE_FILE")"
fi
_fresh_output
chmod +x "$FRESH_PATH/build.new/$DEST_NAME"
_fresh_symlink
done
}
_fresh_output() {
mkdir -p "$(dirname "$FRESH_PATH/build.new/$DEST_NAME")"
if [[ -n "$MARKER" ]]; then
if [[ -e "$FRESH_PATH/build.new/$DEST_NAME" ]]; then
echo >> "$FRESH_PATH/build.new/$DEST_NAME"
fi
_file_marker >> "$FRESH_PATH/build.new/$DEST_NAME"
echo >> "$FRESH_PATH/build.new/$DEST_NAME"
fi
if [[ -e "$FRESH_PATH/build.new/$DEST_NAME" && "$MODE" == "bin" && "${FRESH_NO_BIN_CONFLICT_CHECK:-}" != "true" ]]; then
_note "Multiple sources concatenated into a single bin file."
cat <<EOF
$(_rc_file_line_reference)
Typically bin files should not be concatenated together into one file.
"$DEST_NAME" may not function as expected.
To disable this warning, add \`FRESH_NO_BIN_CONFLICT_CHECK=true\` in your freshrc file.
EOF
fi
_source_file_contents >> "$FRESH_PATH/build.new/$DEST_NAME"
}
_fresh_symlink() {
if [[ -n "$SYMLINK_SOURCE" ]] && [[ -n "$SYMLINK_PATH" ]]; then
SYMLINK_SOURCE="$FRESH_PATH/build/$SYMLINK_SOURCE"
SYMLINK_PATH="${SYMLINK_PATH/#~/$HOME}"
if ! [ -L "$SYMLINK_PATH" ]; then
if ! mkdir -p "$(dirname "$SYMLINK_PATH")" 2> /dev/null || ! ln -s "$SYMLINK_SOURCE" "$SYMLINK_PATH" 2> /dev/null; then
SKIP_INFO=1
if [[ -e "$SYMLINK_PATH" ]]; then
_fatal_error "$SYMLINK_PATH already exists."
else
_fatal_error "Could not create $SYMLINK_PATH. Do you have permission?"
fi
fi
else
if [[ "$(readlink "$SYMLINK_PATH")" != "$SYMLINK_SOURCE" ]]; then
if [[ "$(readlink "$SYMLINK_PATH")" == "$FRESH_PATH"/build/* ]]; then
ln -sf "$SYMLINK_SOURCE" "$SYMLINK_PATH"
else
_fatal_error "$SYMLINK_PATH already exists (pointing to $(readlink "$SYMLINK_PATH"))."
fi
fi
fi
fi
}
_run_dsl() {
# define DSL functions
eval "fresh() { _dsl_$1_fresh \"\$@\"; }"
eval "fresh-options() { _dsl_fresh_options \"\$@\"; }"
# load the freshrc file
if [ -e "$FRESH_RCFILE" ]; then
IN_RC_FILE=1
fresh-options # init defaults
source "$FRESH_RCFILE"
IN_RC_FILE=0
fi
# remove DSL functions
unset -f fresh
unset -f fresh-options
}
_dsl_fresh_options() {
_set_dsl_caller
DEFAULT_MODE=
DEFAULT_MODE_ARG=
DEFAULT_REF=
DEFAULT_MARKER=
DEFAULT_FILTER=
DEFAULT_IGNORE_MISSING=
if [[ $# -gt 0 ]]; then
# __FRESH_OPTIONS__ placeholder as _parse_fresh_dsl_args expects a filename
_parse_fresh_dsl_args __FRESH_OPTIONS__ "$@"
DEFAULT_MODE="$MODE"
DEFAULT_MODE_ARG="$MODE_ARG"
DEFAULT_REF="$REF"
DEFAULT_MARKER="$MARKER"
DEFAULT_FILTER="$FILTER"
DEFAULT_IGNORE_MISSING="$IGNORE_MISSING"
fi
}
_set_dsl_caller() {
read RC_LINE _ RC_FILE <<< "$(caller 2)"
}
_parse_fresh_dsl_args() {
MODE="$DEFAULT_MODE"
MODE_ARG="$DEFAULT_MODE_ARG"
REPO_NAME=""
FILE_NAME=""
REF="$DEFAULT_REF"
MARKER="$DEFAULT_MARKER"
FILTER="$DEFAULT_FILTER"
IGNORE_MISSING="$DEFAULT_IGNORE_MISSING"
while [ $# -gt 0 ]
do
case "$1" in
--file|--file=*|--bin|--bin=*)
if [ -n "$MODE" ]; then
_fatal_error "Cannot have more than one mode."
fi
MODE="$(echo "$1" | sed -e 's/^--//' -e 's/=.*//')"
MODE_ARG="$(echo "$1" | sed 's/^--[^=]*=*//')"
;;
--ref)
_fatal_error "You must specify a Git reference."
;;
--ref=*)
REF="$(echo "$1" | sed 's/^--[^=]*=*//')"
;;
--marker|--marker=*)
if [[ "$MODE" != file ]]; then
_fatal_error "--marker is only valid with --file."
fi
if [[ "$1" == --marker ]]; then
MARKER='#'
else
MARKER="$(echo "$1" | sed 's/^--[^=]*=*//')"
if [[ -z "$MARKER" ]]; then
_fatal_error "Marker not specified."
fi
fi
;;
--filter=*)
FILTER="$(echo "$1" | sed 's/^--[^=]*=*//')"
;;
--filter)
_fatal_error "You must specify a filter program."
;;
--ignore-missing)
IGNORE_MISSING="1"
;;
-*)
_fatal_error "Unknown option: $1"
;;
*)
if [ -n "$FILE_NAME" ]; then
if [ -n "$REPO_NAME" ]; then
_fatal_error "Expected 1 or 2 args."
fi
REPO_NAME="$FILE_NAME"
FILE_NAME="$1"
else
FILE_NAME="$1"
fi
;;
esac
shift
done
if [ -z "$FILE_NAME" ]; then
_fatal_error "Filename is required"
fi
}
_dsl_fresh_bin_fresh() {
_set_dsl_caller
_parse_fresh_dsl_args "$@"
if [[ "$MODE" == bin ]]; then
_prepare_file_source
_file_list | while read SOURCE_FILE; do
if [[ "$(basename "$SOURCE_FILE")" == fresh ]]; then
_source_file_contents >> "$BIN_FILE_NAME"
fi
done
fi
}
fresh_install_with_latest_binary() {
BIN_FILE_NAME="$(mktemp "${TMPDIR:-/tmp}/fresh.XXXXXXXX")"
trap '{ rm -f "$BIN_FILE_NAME"; }' EXIT
_run_dsl fresh_bin
if [[ -s "$BIN_FILE_NAME" ]]; then
chmod +x "$BIN_FILE_NAME"
"$BIN_FILE_NAME" install
else
fresh_install
fi
}
fresh_update() {
if [[ $# -gt 1 ]]; then
_fatal_error "Invalid arguments.
usage: fresh update <filter>
The filter can be either a GitHub username or username/repo."
fi
mkdir -p "$FRESH_PATH/logs"
local LOG_FILE="$FRESH_PATH/logs/update-$(date +%Y-%m-%d-%H%M%S).log"
if [[ $# == 0 ]] || [[ "$1" == "--local" ]]; then
(
set -e
[[ -e "$FRESH_LOCAL/.git" ]] || exit 0
if ! (cd $FRESH_LOCAL && git rev-parse @{u} &> /dev/null); then
_note "Not updating $FRESH_LOCAL because the current branch is non-tracking."
if [[ "${1:-}" == "--local" ]]; then
exit 1
fi
else
if [[ -n "$(cd $FRESH_LOCAL && git status --porcelain)" ]]; then
_note "Not updating $FRESH_LOCAL because it has uncommitted changes."
if [[ "${1:-}" == "--local" ]]; then
exit 1
fi
else
_update_repo "$FRESH_LOCAL" "local files"
fi
fi
)
[[ $? == 0 ]] || exit 1
if [[ "${1:-}" == "--local" ]]; then
return
fi
fi
local FILTER="${1:-}"
if [[ -z "$FILTER" ]]; then
FILTER="*"
elif ! echo "$FILTER" | grep -q /; then
FILTER="$FILTER/*"
fi
local MATCHED=0
while read DIR; do
local DIR="$(dirname "$DIR")"
local REPO_NAME="$(_repo_name_from_source_path "$DIR")"
[[ "$REPO_NAME" == $FILTER ]] || continue
MATCHED=1
_update_repo "$DIR" "$REPO_NAME"
done < <(find $FRESH_PATH/source -type d -name '.git' | sort)
if [[ "$MATCHED" == 0 ]]; then
_fatal_error "No matching sources found."
fi
}
_update_repo() {
local DIR="$1"
local DISPLAY_NAME="$2"
echo "* Updating $DISPLAY_NAME" | tee -a "$LOG_FILE"
cd "$DIR"
local GIT_OUTPUT_FILE="$(mktemp "${TMPDIR:-/tmp}/fresh.XXXXXX")"
git pull --rebase 2>&1 | tee "$GIT_OUTPUT_FILE" | sed 's/^/| /' | tee -a "$LOG_FILE"
local GIT_RESULT="${PIPESTATUS[0]}"
if egrep -q '^From .*(://github.com/|git@github.com:)' "$GIT_OUTPUT_FILE"; then
local REPO_URL="$(egrep '^From .*(://github.com/|git@github.com:)' "$GIT_OUTPUT_FILE" | cut -d ' ' -f 2)"
local REPO_NAME="$(_repo_name "$REPO_URL")"
if [ -n "$REPO_NAME" ]; then
perl -n -e'/^ {2,}([0-9a-f]{7,})\.\.([0-9a-f]{7,}) / && print "| <'"$(_format_url "https://github.com/$REPO_NAME/compare/\$1...\$2")"'>\n"' "$GIT_OUTPUT_FILE" | tee -a "$LOG_FILE"
fi
fi
rm "$GIT_OUTPUT_FILE"
if [ "$GIT_RESULT" -ne 0 ]; then
_fatal_error "Update failed."
fi
cd "$OLDPWD"
}
_repo_name_from_source_path() {
echo "$1" | awk -F/ '{OFS="/"; print $(NF-1), $(NF-0)}'
}
fresh_edit() {
if [[ -L "$FRESH_RCFILE" ]]; then
local RCFILE="$(realpath "$FRESH_RCFILE")"
else
local RCFILE="$FRESH_RCFILE"
fi
${EDITOR:-vi} "$RCFILE"
}
fresh_search() {
if [[ "$#" -gt 0 ]]; then
RESULTS="$(curl -sS http://api.freshshell.com/directory --get --data-urlencode q="$*")"
if [[ -n "$RESULTS" ]]; then
echo "$RESULTS"
else
_fatal_error "No results."
fi
else
_fatal_error "No search query given."
fi
}
fresh_clean() {
_fresh_clean_symlinks ~
_fresh_clean_symlinks ~/bin
_fresh_clean_repos
}
_fresh_clean_symlinks() {
local BASE="$1"
if [[ -e "$BASE" ]]; then
local path_glob="$FRESH_PATH/build/*"
find "$BASE" -maxdepth 1 -type l | while read SYMLINK; do
DEST="$(readlink "$SYMLINK")"
if [[ $DEST == $path_glob ]] && ! [[ -e "$DEST" ]]; then
echo "Removing ${SYMLINK/#$HOME/~}"
rm "$SYMLINK"
fi
done
fi
}
_fresh_clean_repos() {
if [[ -e "$FRESH_PATH/source" ]]; then
REPO_NAME_FILE="$(mktemp "${TMPDIR:-/tmp}/fresh.XXXXXXXX")"
trap '{ rm -f "$REPO_NAME_FILE"; }' EXIT
_run_dsl clean
find "$FRESH_PATH/source" -type d -name '.git' | sort | while read DIR; do
local DIR="$(dirname "$DIR")"
local REPO_NAME="$(_repo_name_from_source_path "$DIR")"
if ! echo "$REPO_NAME" | grep -qF -f "$REPO_NAME_FILE"; then
echo "Removing source $REPO_NAME"
rm -rf "$DIR"
fi
done
find "$FRESH_PATH/source" -mindepth 1 -maxdepth 1 -type d -empty -not -name '.*' -exec rmdir {} \;
fi
}
_dsl_clean_fresh() {
_set_dsl_caller
_parse_fresh_dsl_args "$@"
if [[ -n "$REPO_NAME" ]]; then
local REPO_DIR="$(_repo_name "$REPO_NAME")"
echo "$REPO_DIR" >> "$REPO_NAME_FILE"
fi
}
fresh_show() {
LINE_COUNT=0
_run_dsl show
}
_dsl_show_fresh() {
_set_dsl_caller
LINE_COUNT=$((LINE_COUNT + 1))
if [[ $LINE_COUNT -gt 1 ]]; then
echo
fi
_escape fresh "$@"
_parse_fresh_dsl_args "$@"
_prepare_file_source
_file_list | while read SOURCE_FILE
do
echo "<$(_format_url "$(_source_file_url)")>"
done
}
fresh_help() {
echo $'Keep your dot files \033[1;32mfresh\033[0m.'
cat <<EOF
The following commands will install/update configuration files
as specified in your $(_freshrc_file) file.
See $(_format_url http://freshshell.com/readme) for more documentation.
usage: fresh <command> [<args>]
Available commands:
$(fresh_commands | sed -e 's/^/ /' -e 's/ #//')
EOF
}
_parse_fresh_add_args() {
if [[ "$1" == https://github.com/* ]]; then
URL="$1"
LINE="$(echo "$URL" | sed -e 's#^https://github.com/##' -e 's#/blob/[^/]*/# #')"
shift
for ARG in "$@"; do
LINE="$LINE $ARG"
done
# if we only have the URL try to infer if we have a bin or config file
if ! echo "$@" | grep -q "\--bin\|\--file"; then
if echo "$URL" | grep -q "/bin/"; then
LINE="$LINE --bin"
elif echo "$URL" | grep -q "/config/"; then
LINE="$LINE --file"
fi
fi
REF="$(echo "$URL" | sed 's#^.*/blob/\([^/]*\)/.*$#\1#')"
if [[ "$REF" != "master" ]]; then
LINE="$LINE --ref=$REF"
fi
echo "$LINE"
else
_escape "$@"
fi
}
fresh_add() {
LINE="$(_parse_fresh_add_args "$@")"
if _confirm "Add \`fresh $LINE\` to $(_freshrc_file)"; then
echo "Adding \`fresh $LINE\` to $(_freshrc_file)..."
echo "fresh $LINE" >> "$FRESH_RCFILE"
_dsl_fresh_options # init defaults
eval _parse_fresh_dsl_args "$LINE"
local REPO_DIR="$(_repo_name "$REPO_NAME")"
if [[ -n "$REPO_NAME" ]] && [[ -d "$FRESH_PATH/source/$REPO_DIR" ]] && [[ ! -e "$FRESH_PATH/source/$REPO_DIR/$FILE_NAME" ]]; then
fresh_update "$REPO_DIR"
fi
fresh_install
else
_note "Use \`fresh edit\` to manually edit your $(_freshrc_file)."
fi
}
_escape() {
printf "%q" "$1"
shift
for arg in "$@"; do
printf " %q" "$arg"
done
echo
}
_confirm() {
echo -n "$1 [Y/n]? "
read answer
case $answer in
[Yy]*|"")
true
;;
[Nn]*)
false
;;
*)
_confirm "$1"
;;
esac
}
_rc_file_line_reference() {
if [[ "$IN_RC_FILE" == 1 ]]; then
local DISPLAY_RC_FILE="${RC_FILE/#$HOME/~}"
local RC_CONTENT="$(head -$RC_LINE "$RC_FILE" | tail -1)"
echo "$DISPLAY_RC_FILE:$RC_LINE: $RC_CONTENT"
fi
}
_fatal_error() {
echo $'\033[4;31mError\033[0m:' "$1" >&2
if [[ "$IN_RC_FILE" == 1 ]]; then
echo "$(_rc_file_line_reference)" >&2
if ! [[ "$SKIP_INFO" == 1 ]]; then
cat >&2 <<EOF
You may need to run \`fresh update\` if you're adding a new line,
or the file you're referencing may have moved or been deleted.
EOF
fi
if [[ -n "$REPO_NAME" ]]; then
echo "Have a look at the repo: <$(_format_url "$(_repo_url "$REPO_NAME")")>" >&2
fi
fi
exit 1
}
_note() {
echo $'\033[1;33mNote\033[0m:' "$1"
}
_format_url() {
echo $'\033[4;34m'"$1"$'\033[0m'
}
_prevent_invalid_arguments() {
if [ $# -gt 1 ]; then
_fatal_error "Invalid arguments"
fi
}
realpath() {
perl - "$@" <<PERL
use Cwd 'abs_path';
if (-e \$ARGV[0]) {
print abs_path(\$ARGV[0]) . "\n";
} else {
exit 1;
}
PERL
}
_freshrc_file() {
echo "${FRESH_RCFILE/#$HOME/~}"
}
fresh_commands() {
shopt -s nullglob
cat <<EOF
install # Build shell configuration and relevant symlinks (default)
update [<filter>] # Update from source repos and rebuild
clean # Removes dead symlinks and source repos
search <query> # Search the fresh directory
edit # Open freshrc for editing
show # Show source references for freshrc lines
help # Show this help
EOF
{ for path in ${PATH//:/$'\n'}; do
for command in "${path}/fresh-"*; do
command="${command##*/fresh-}"
printf "%-19s# %s\n" "$command" "Run $command plugin"
done
done
} | sort | uniq
}
main() {
case "${1:-}" in
help|--help|-h)
fresh_help
;;
install|'')
_prevent_invalid_arguments "$@"
fresh_install
;;
update|up)
shift
fresh_update "$@"
fresh_install_with_latest_binary
;;
edit)
_prevent_invalid_arguments "$@"
fresh_edit
;;
show)
_prevent_invalid_arguments "$@"
fresh_show
;;
search)
shift
fresh_search "$@"
;;
clean)
_prevent_invalid_arguments "$@"
fresh_clean
;;
commands)