-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathshell-call-graph.in
executable file
·122 lines (110 loc) · 2.71 KB
/
shell-call-graph.in
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
#!/usr/bin/env bash
#
# SPDX-FileCopyrightText: 2024 Andrew L. Moore <slewsys@gmail.com>, SlewSys Research
#
# SPDX-License-Identifier: CC0-1.0
#
# @(#) shell-call-graph
#
# SYNOPSIS
#
# shell-call-graph SHELL-SCRIPT [...]
#
# DESCRIPTION
#
# This script generates a call graph for shell scripts on its command line that are
# assumed to be structured as follows:
#
# #!/usr/bin/env bash
# #
# func1 ()
# {
# ...
# }
#
# func2 ()
# {
# ...
# }
#
# ...
#
# funcN ()
# {
# ...
# }
#
# if test ."$0" = ."${BASH_SOURCE[0]}"; then
# ...
# fi
#
# In particular:
# - The keyword `function' is omitted.
# - Function declarations are on lines by themselves.
# - Function-closing curly braces (`}') are on lines by themselves.
# - An `if` statement in column 1 at the end of the file referencing
# `$0' serves as the script's "main" function.
# - Statements within the `if' statement are on lines by themselves.
# - The closing `fi' keyword is in column 1 as well.
#
: ${CAT_CMD:='@CAT_CMD@'}
: ${ED_CMD:='@ED_CMD@'}
: ${FILE_CMD:='@FILE_CMD@'}
: ${MKTEMP_CMD:='@MKTEMP_CMD@'}
: ${PERL_CMD:='@PERL_CMD@'}
: ${PRDG_CMD:='@PRDG_CMD@'}
: ${RM_CMD:='@RM_CMD@'}
: ${SED_CMD:='@SED_CMD@'}
is-file-type ()
{
file=$1
file_type=$2
if [[ ! ."$($FILE_CMD "$file")" =~ \..*$file_type ]]; then
echo "$file: Expected file of type: ${file_type}" >&2
return 1
fi
}
parse-shell-scripts ()
{
local function_list_string=''
local -a function_list=()
local script=''
for script; do
is-file-type "$script" "shell script" || return $?
function_list_string+=$(
$ED_CMD -E -e 'g;^([_[:alpha:]][-+_~!@%^:./?[:alnum:]]+)\s*\(\)\s*\{?;s;;\1;p' "$script"
) || return $?
done
mapfile -t function_list <<<"$function_list_string"
local funcall_alternates=$(IFS='|'; echo "${function_list[*]}")
local delim='[[:space:]()<>;|&\`]'
local funcall_regex='.*?(^|'"${delim}"')('"${funcall_alternates}"')($|'"${delim}"')'
local ed_script=''
for script; do
ed_script='g/^\s*#/d
g;^if.*$0.*;s;;_'${script##*/}'_ ()\
{;
g/^fi.*/s;;};
g;^(\S+).*\(\);s;^(\S+).*;\1;p\
s;$; { '${script}': };p\
.=\
+,/^\s*}/-w ! '${PERL_CMD}' -ne '\''while (s/'$funcall_regex'/ \1/) { print " $2\n"; }'\'
$ED_CMD -E -e "$ed_script" "$script" || return $?
done
}
create-adjacency-list ()
{
local tmpfile=$1
local ed_script='g/^[0-9]+/-,.j\
s;( })(.*);\2\1;
g;^\s+;-ka\
?\{?-t'\''a
g;.;s;$; ;\
.,+j
,p'
$ED_CMD -E -e "$ed_script" $tmpfile
}
if test ."$0" = ."${BASH_SOURCE[0]}"; then
create-adjacency-list <(parse-shell-scripts "$@") |
$PRDG_CMD -m -r _${1##*/}_
fi