-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path__all
More file actions
executable file
·190 lines (167 loc) · 6.8 KB
/
__all
File metadata and controls
executable file
·190 lines (167 loc) · 6.8 KB
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
# Note that the script ordering produced here is not stable; the output may
# be any sequence that satisfies the dependencies specified by the scripts,
# with no actual regard for lexicographic order nor for the order in which
# dependencies are specified. This has the potential to introduce subtle
# bugs in your startup sequence.
#
# If one of the scripts has even the slightest need at all for another script
# to be run first, specify it as a dependency.
# This script is in bash and friends, but comments are included to aid the
# understanding of more conventional languages. Please keep these up to
# date. If a comment contains a regex prefixed with `qr`, it is being given
# in Perl-/JavaScript-like form rather than POSIX form.
# Open a script file and read its #require directives. Also include the name
# of the script itself at the end.
# Output: A list of names, one per line, with the given name as the final
# element. Because the list always contains the script's own name, the result
# shall never be blank.
_HgkUsRcdRunner__requirements()
{
# qr/^#require\s+/; line starts with '#require' followed by whitespace
local prefix_pattern='^#require\s\+'
# Name of the script currently being queried
local name="$1"
# Get the contents of any #require lines in the named script.
# This sed command says
# - Omit all lines that do not match the prefix pattern.
# - From remaining lines, omit the part of the line matching the prefix
# pattern.
# - Convert any runs of whitespace into newlines, turning
# space-separated lists into newline-separated lists.
sed '
/'"$prefix_pattern"'/!d
s/'"$prefix_pattern"'//
s/\s\+/\n/g
' < "$name"
# Report the script's own name as its last dependency.
echo "$name"
}
# Using the script names given as parameters, fetch all of their
# requirements (pseudo-)recursively and determine an order for them to
# execute in which scripts will run only after their requirements have run.
_HgkUsRcdRunner__schedule()
{
# Associative array that maps each script to a newline-separated list
# of dependencies.
declare -A reqs
# Load the initial keys.
# A blank value means the requirements are not listed yet.
# (This loop iterates the command arguments, where, for the queue of
# arguments, $# is its length, $1 reads its head, and shift discards the
# head.)
while [ $# -gt 0 ]; do
reqs[$1]=""
shift
done
# Traverses the list of keys. For each key where the value is still "",
# the script with the same name is queried for its list of requirements,
# and the value for the key is replaced with the requirements list.
# Also, each element of that requirements list that does not already
# have a key in the associative array is added with its value set to "".
# Repeat the traversal (catching any new keys in the process) until
# there are no more blank requirements lists.
#
# Requirements newly found in each pass are not queried for their own
# requirements until the next pass.
local state='more'
while [ $state == 'more' ]; do
state='done'
# For each k in reqs's key list
# (The key sequence is pre-evaluated, so mutating reqs in the loop
# is OK)
for k in "${!reqs[@]}"; do
# If the requirement list for $k is still blank
if [ "${reqs["$k"]}" == "" ] ; then
# Retrieve the requirements for the script named $k into req.
#
# We rely on the result of this being non-blank. It will
# always contain at least the file itself as its own
# dependency.
local req=`_HgkUsRcdRunner__requirements "$k"`
# For each dependency, if it is not a key in reqs yet, add
# it with value set to "".
for name in $req; do
# ${foo[$bar]+some_dummy_word} tests for the existence of
# $bar in foo. See "Shell Parameter Expansion" (the
# section for `${parameter:+word}`) in bash manual.
# If $name is not in reqs
if [ ! ${reqs[$name]+exists_test_dummy} ]; then
# Add a new script with "" as its requirements list
reqs["$name"]=""
# Ensure that there is at least one more pass to
# fill the requirements list
state='more'
fi
done
# Finally, set the requirements list for $k.
reqs["$k"]="$req"
fi
done
done
# Sort the dependencies in load order.
# - Create a list of ordered dependency pairs.
# - For each dependency d of a script s, print a line "$d $s" which
# describes this dependency. (This includes where s = d.)
# - Run tsort on the result.
# - tsort is an important, semi-obscure, POSIX-required program that
# accepts a whitespace-separated list of pairs, for which the left
# side of the pair is known to appear not later than the right. The
# result is a sequence of all the items mentioned, where the given
# partial orderings are preserved.
(
# For each k in reqs's key list
for k in "${!reqs[@]}"; do
# For each of k's dependencies, print a line containing $k, then
# a space, then the dependency. (Note that reqs[$k] is a
# multi-line string containing one dependency per line. The awk
# one-liner just prepends "$k " to each line.)
echo "${reqs[$k]}" | awk -v name="$k" '{ print name " " $0 }'
done
) | tsort | tac
}
# Given filenames as parameters, outputs a list of files that the runner
# will run by default. Filenames containing periods or leading with at least
# two underscores are not to be run by default. (They can, however, be
# loaded as requirements.)
_HgkUsRcdRunner__roots()
{
for i in "$@"; do
if [ -f "$i" -a -r "$i" ]; then
case $i in
# Files not run by default
# If $i matches __* or *.* or .*, skip it
__* | *.* | .* )
# Skip
;;
# Files run by default
# If $i doesn't match the non-default patterns
* )
echo "$i"
;;
esac
fi
done
}
# Given an rc directory, locates the scripts to be run and their
# dependencies, schedules them, and then sources each one in order. (The
# current working directory is used when sourcing.)
HgkUsRcdRunner__run()
{
local dir="$1"
local roots=`(cd "$dir"; _HgkUsRcdRunner__roots *)`
local sched=`(cd "$dir"; _HgkUsRcdRunner__schedule $roots)`
for i in $sched; do
. "$dir/$i"
done
}
# Since many scripts need to determine whether the current session is
# interactive, we provide the function `is_interactive` so that tests like
# `if is_interactive; then ... fi` or `is_interactive && ...` readily work.
is_interactive()
{
[[ "$-" == *i* ]]
}
if [ "-$HGK_RCD_RUNNER_DIR-" == "--" ]; then
HGK_RCD_RUNNER_DIR=~/.bashrc.d
fi
HgkUsRcdRunner__run "$HGK_RCD_RUNNER_DIR"