bash-dap 0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bash_dap/__init__.py +3 -0
- bash_dap/__main__.py +5 -0
- bash_dap/adapter.py +537 -0
- bash_dap/harness.sh +206 -0
- bash_dap-0.1.dist-info/METADATA +88 -0
- bash_dap-0.1.dist-info/RECORD +10 -0
- bash_dap-0.1.dist-info/WHEEL +5 -0
- bash_dap-0.1.dist-info/entry_points.txt +2 -0
- bash_dap-0.1.dist-info/licenses/LICENSE +641 -0
- bash_dap-0.1.dist-info/top_level.txt +1 -0
bash_dap/harness.sh
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# harness.sh: the debugger of bash-dap, in the script it debugs.
|
|
2
|
+
#
|
|
3
|
+
# BASH_DAP_DIR=DIRECTORY bash -c '. harness.sh' SCRIPT [ARGUMENTS...]
|
|
4
|
+
#
|
|
5
|
+
# The script runs as it would by itself: its $0, its arguments, its stdin, stdout and stderr.
|
|
6
|
+
# The trap DEBUG stops it before a command, in the subshells of $(...) and of pipes too.
|
|
7
|
+
#
|
|
8
|
+
# DIRECTORY has the fifos "commands", which the adapter writes a command a line to, and
|
|
9
|
+
# "replies", which it reads what the debugger says from, a line each:
|
|
10
|
+
#
|
|
11
|
+
# commands: run | step | next | finish | print EXPRESSION | vars | stack
|
|
12
|
+
# replies: stopped REASON LINE FUNCTION FILE (REASON: entry, breakpoint, step)
|
|
13
|
+
# value TEXT | var NAME TEXT | frame LEVEL FUNCTION LINE FILE | error TEXT
|
|
14
|
+
# done, after the reply to a command | exited CODE
|
|
15
|
+
#
|
|
16
|
+
# TEXT is quoted as printf %q does, so that a reply is one line. A command is read only while
|
|
17
|
+
# the script is stopped. At any time the adapter may write the breakpoints to the file "breaks",
|
|
18
|
+
# FILE:LINE a line, then make the empty file "gen.N" of the next generation N; and stop the
|
|
19
|
+
# script at its next command by writing "step 0" to the file "state".
|
|
20
|
+
#
|
|
21
|
+
# A subshell is a process of its own: what it is told, a step, a breakpoint, goes to the others
|
|
22
|
+
# through these files. The names of the debugger begin with __bdap_.
|
|
23
|
+
|
|
24
|
+
__bdap_self=${BASH_SOURCE[0]}
|
|
25
|
+
__bdap_dir=$BASH_DAP_DIR
|
|
26
|
+
unset BASH_DAP_DIR BASH_DAP_HARNESS
|
|
27
|
+
exec {__bdap_in}<"$__bdap_dir/commands" {__bdap_out}>"$__bdap_dir/replies" || exit 125
|
|
28
|
+
|
|
29
|
+
# how the script goes on: run, step, next or finish, with the depth of the stop; in the file
|
|
30
|
+
# "state" while it is not "run", so that a subshell and its parent go on as one: a step out of a
|
|
31
|
+
# function of $(...) stops in the line that called it
|
|
32
|
+
__bdap_state=$__bdap_dir/state
|
|
33
|
+
__bdap_mode=run
|
|
34
|
+
__bdap_depth=0
|
|
35
|
+
# the breakpoints by FILE:LINE, and the generation of the file they came from; __bdap_names has
|
|
36
|
+
# NAME:LINE, the name of the file only, that the trap tests at each command
|
|
37
|
+
declare -A __bdap_breaks=() __bdap_names=()
|
|
38
|
+
__bdap_gen=0
|
|
39
|
+
__bdap_inside=
|
|
40
|
+
__bdap_prev= # the place of the command before: a line stops when it is come to, not again
|
|
41
|
+
|
|
42
|
+
# the variables of bash itself, and those it makes for a function: not shown
|
|
43
|
+
__bdap_known=" $(compgen -v | tr '\n' ' ') FUNCNAME BASH_ARGC BASH_ARGV BASH_LINENO BASH_SOURCE COLUMNS LINES "
|
|
44
|
+
|
|
45
|
+
__bdap_say () {
|
|
46
|
+
printf '%s\n' "$*" >&"$__bdap_out"
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
__bdap_vars () {
|
|
50
|
+
local __bdap_name
|
|
51
|
+
for __bdap_name in $(compgen -v); do
|
|
52
|
+
[[ $__bdap_name == __bdap_* || $__bdap_known == *" $__bdap_name "* ]] && continue
|
|
53
|
+
__bdap_say "var $__bdap_name $(printf '%q' "$(declare -p "$__bdap_name" 2>/dev/null)")"
|
|
54
|
+
done
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# The frames of the script: those of the debugger left out, the top of the script is main
|
|
58
|
+
__bdap_stack () {
|
|
59
|
+
local __bdap_i __bdap_level=0 __bdap_func
|
|
60
|
+
for ((__bdap_i = 1; __bdap_i < ${#FUNCNAME[@]}; __bdap_i++)); do
|
|
61
|
+
[[ ${FUNCNAME[__bdap_i]} == __bdap_* || ${BASH_SOURCE[__bdap_i]} == "$__bdap_self" ]] && continue
|
|
62
|
+
__bdap_func=${FUNCNAME[__bdap_i]}
|
|
63
|
+
[[ $__bdap_func == source || $__bdap_func == main ]] && __bdap_func=main
|
|
64
|
+
# the line a frame is at is the line its callee was called from
|
|
65
|
+
__bdap_say "frame $__bdap_level $__bdap_func ${__bdap_lines[__bdap_level]} $(__bdap_path "${BASH_SOURCE[__bdap_i]}")"
|
|
66
|
+
((__bdap_level++))
|
|
67
|
+
done
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# A file as the adapter names it: whole
|
|
71
|
+
__bdap_path () {
|
|
72
|
+
if [[ $1 == /* ]]; then
|
|
73
|
+
printf '%s' "$1"
|
|
74
|
+
else
|
|
75
|
+
printf '%s' "$PWD/${1#./}"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
__bdap_resume () {
|
|
80
|
+
__bdap_mode=$1
|
|
81
|
+
__bdap_depth=$2
|
|
82
|
+
if [[ $__bdap_mode == run ]]; then
|
|
83
|
+
rm -f "$__bdap_state"
|
|
84
|
+
else
|
|
85
|
+
printf '%s %s\n' "$__bdap_mode" "$__bdap_depth" >"$__bdap_state"
|
|
86
|
+
fi
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
__bdap_breaks_load () {
|
|
90
|
+
local __bdap_place
|
|
91
|
+
while [[ -e $__bdap_dir/gen.$((__bdap_gen + 1)) ]]; do
|
|
92
|
+
((__bdap_gen++))
|
|
93
|
+
done
|
|
94
|
+
__bdap_breaks=()
|
|
95
|
+
__bdap_names=()
|
|
96
|
+
while read -r __bdap_place; do
|
|
97
|
+
[[ -z $__bdap_place ]] && continue
|
|
98
|
+
__bdap_breaks["$__bdap_place"]=1
|
|
99
|
+
__bdap_names["${__bdap_place##*/}"]=1
|
|
100
|
+
done <"$__bdap_dir/breaks"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
# Wait for the commands of the adapter, until one runs the script on
|
|
104
|
+
__bdap_wait () {
|
|
105
|
+
local __bdap_command __bdap_argument
|
|
106
|
+
while read -r __bdap_command __bdap_argument <&"$__bdap_in"; do
|
|
107
|
+
case $__bdap_command in
|
|
108
|
+
run) __bdap_resume run 0; return ;;
|
|
109
|
+
step) __bdap_resume step 0; return ;;
|
|
110
|
+
next | finish) __bdap_resume "$__bdap_command" "$1"; return ;;
|
|
111
|
+
print)
|
|
112
|
+
# a name is a variable; anything else is a word of bash, expanded
|
|
113
|
+
if [[ $__bdap_argument =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]]; then
|
|
114
|
+
__bdap_say "value $(printf '%q' "$(declare -p "$__bdap_argument" 2>&1)")"
|
|
115
|
+
else
|
|
116
|
+
__bdap_say "value $(printf '%q' "$(eval "printf '%s' $__bdap_argument" 2>&1)")"
|
|
117
|
+
fi
|
|
118
|
+
;;
|
|
119
|
+
vars) __bdap_vars ;;
|
|
120
|
+
stack) __bdap_stack ;;
|
|
121
|
+
*) __bdap_say "error $(printf '%q' "unknown command $__bdap_command")" ;;
|
|
122
|
+
esac
|
|
123
|
+
__bdap_say done
|
|
124
|
+
done
|
|
125
|
+
# the adapter has gone: the script goes on by itself
|
|
126
|
+
__bdap_resume run 0
|
|
127
|
+
__bdap_breaks=()
|
|
128
|
+
__bdap_names=()
|
|
129
|
+
trap - DEBUG
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
# Whether the command about to run is on its line: bash gives a command of several lines, a
|
|
133
|
+
# pipe into a loop, the line of a command in it, where it has not got to yet
|
|
134
|
+
__bdap_on_line () {
|
|
135
|
+
local -a __bdap_text
|
|
136
|
+
local __bdap_word=${__bdap_command%%[[:space:]]*}
|
|
137
|
+
mapfile -t -s $((__bdap_line - 1)) -n 1 __bdap_text <"$__bdap_file" 2>/dev/null || return 0
|
|
138
|
+
[[ -z $__bdap_word || ${__bdap_text[0]-} == *"$__bdap_word"* ]]
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# Before a command of the script that may stop: what the script has set, -e, -u, -x, is not
|
|
142
|
+
# the debugger's, which runs without
|
|
143
|
+
__bdap_hook () {
|
|
144
|
+
{ local __bdap_opts=$- __bdap_command=$1; set +eux; } 2>/dev/null
|
|
145
|
+
__bdap_check
|
|
146
|
+
[[ $__bdap_opts == *e* ]] && set -e
|
|
147
|
+
[[ $__bdap_opts == *u* ]] && set -u
|
|
148
|
+
[[ $__bdap_opts == *x* ]] && set -x
|
|
149
|
+
return 0
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
__bdap_check () {
|
|
153
|
+
[[ -n $__bdap_inside ]] && return 0
|
|
154
|
+
# 0 is this function, 1 the hook, 2 the frame of the script
|
|
155
|
+
local __bdap_file=${BASH_SOURCE[2]} __bdap_line=${BASH_LINENO[1]} __bdap_d=$((${#FUNCNAME[@]} - 1))
|
|
156
|
+
local __bdap_reason= __bdap_lines __bdap_func
|
|
157
|
+
[[ $__bdap_file == "$__bdap_self" ]] && return 0
|
|
158
|
+
[[ $__bdap_file == /* ]] || __bdap_file=$PWD/${__bdap_file#./}
|
|
159
|
+
# what another process of the script has said: a subshell has stepped, or the adapter has
|
|
160
|
+
# changed the breakpoints, or wants a stop
|
|
161
|
+
[[ -e $__bdap_dir/gen.$((__bdap_gen + 1)) ]] && __bdap_breaks_load
|
|
162
|
+
if [[ -e $__bdap_state ]]; then
|
|
163
|
+
read -r __bdap_mode __bdap_depth <"$__bdap_state"
|
|
164
|
+
elif [[ $__bdap_mode != run ]]; then
|
|
165
|
+
__bdap_mode=run
|
|
166
|
+
fi
|
|
167
|
+
case $__bdap_mode in
|
|
168
|
+
step) __bdap_reason=step ;;
|
|
169
|
+
next) ((__bdap_d <= __bdap_depth)) && __bdap_reason=step ;;
|
|
170
|
+
finish) ((__bdap_d < __bdap_depth)) && __bdap_reason=step ;;
|
|
171
|
+
esac
|
|
172
|
+
if [[ -z $__bdap_reason && -n ${__bdap_breaks["$__bdap_file:$__bdap_line"]-} ]] && __bdap_on_line; then
|
|
173
|
+
__bdap_reason=breakpoint
|
|
174
|
+
fi
|
|
175
|
+
# the next command of a line is no new place to stop at
|
|
176
|
+
[[ -z $__bdap_reason || $__bdap_prev == "$__bdap_line:${BASH_SOURCE[2]}" ]] && return 0
|
|
177
|
+
# the lines of the frames, the one stopped in first
|
|
178
|
+
__bdap_lines=("$__bdap_line" "${BASH_LINENO[@]:2}")
|
|
179
|
+
__bdap_func=${FUNCNAME[2]:-main}
|
|
180
|
+
[[ $__bdap_func == source || $__bdap_func == main ]] && __bdap_func=main
|
|
181
|
+
__bdap_inside=1
|
|
182
|
+
__bdap_say "stopped $__bdap_reason $__bdap_line $__bdap_func $__bdap_file"
|
|
183
|
+
__bdap_wait "$__bdap_d"
|
|
184
|
+
__bdap_inside=
|
|
185
|
+
return 0
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
__bdap_say "stopped entry 0 main $(__bdap_path "$0")"
|
|
189
|
+
__bdap_inside=1
|
|
190
|
+
__bdap_wait 0
|
|
191
|
+
__bdap_inside=
|
|
192
|
+
|
|
193
|
+
# the end, by an exit of the script too
|
|
194
|
+
trap '__bdap_say "exited $?"' EXIT
|
|
195
|
+
shopt -s extdebug
|
|
196
|
+
set -o functrace
|
|
197
|
+
# the hook only when there may be something to do: a step, a breakpoint in a file of that name
|
|
198
|
+
# on the line, what another process has said; else a command costs a test of two files. One
|
|
199
|
+
# line: a second one would have LINENO one more
|
|
200
|
+
# (with set -u of the script it runs too: nothing unset is read, BASH_SOURCE is empty at the end)
|
|
201
|
+
__bdap_trap='[[ -n $__bdap_inside || ( $__bdap_mode == run'
|
|
202
|
+
__bdap_trap+=' && -z ${__bdap_names["${BASH_SOURCE[0]:+${BASH_SOURCE[0]##*/}}:$LINENO"]-}'
|
|
203
|
+
__bdap_trap+=' && ! -e $__bdap_state && ! -e $__bdap_dir/gen.$((__bdap_gen + 1)) ) ]] || __bdap_hook "$BASH_COMMAND";'
|
|
204
|
+
__bdap_trap+=' __bdap_prev=$LINENO:${BASH_SOURCE[0]-}'
|
|
205
|
+
trap "$__bdap_trap" DEBUG
|
|
206
|
+
. "$0" "$@"
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bash-dap
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: A debug adapter (Debug Adapter Protocol) for bash scripts, written in bash and Python
|
|
5
|
+
Author-email: Ilia Maslakov <il.smind@gmail.com>
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/blue-panels/bash-dap
|
|
8
|
+
Project-URL: Issues, https://github.com/blue-panels/bash-dap/issues
|
|
9
|
+
Keywords: bash,debugger,dap,debug-adapter-protocol
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Unix Shell
|
|
12
|
+
Classifier: Topic :: Software Development :: Debuggers
|
|
13
|
+
Requires-Python: >=3.8
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# bash-dap
|
|
19
|
+
|
|
20
|
+
A debug adapter of the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
|
|
21
|
+
for bash scripts: breakpoints, steps into, over and out of functions, the call stack, the
|
|
22
|
+
variables (arrays and associative arrays as trees), expressions, pause.
|
|
23
|
+
|
|
24
|
+
It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
|
|
25
|
+
of macOS is 3.2: a newer one, from Homebrew say, is given to the launch as *bash*.) The debugger itself is a script
|
|
26
|
+
of bash, `harness.sh`, which the adapter runs the script under; it stops the script with the
|
|
27
|
+
trap DEBUG, in the subshells of `$(...)` and of pipes too.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
pipx install bash-dap # or: pip install --user bash-dap
|
|
33
|
+
pipx install . # from a clone of this repository
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The command `bash-dap` speaks the protocol on its stdin and stdout.
|
|
37
|
+
|
|
38
|
+
## Editors
|
|
39
|
+
|
|
40
|
+
**coole**: a script `.sh` or `.bash` gets bash-dap by itself: F6 on a line, F5.
|
|
41
|
+
|
|
42
|
+
**Neovim** (nvim-dap):
|
|
43
|
+
|
|
44
|
+
```lua
|
|
45
|
+
local dap = require("dap")
|
|
46
|
+
dap.adapters.bashdap = { type = "executable", command = "bash-dap" }
|
|
47
|
+
dap.configurations.sh = {
|
|
48
|
+
{ type = "bashdap", request = "launch", name = "bash-dap",
|
|
49
|
+
program = "${file}", cwd = "${workspaceFolder}", args = {} },
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**Helix**, **Zed** and the others: an adapter of type `executable`, command `bash-dap`, a
|
|
54
|
+
request `launch`.
|
|
55
|
+
|
|
56
|
+
## Launch
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
program the script
|
|
60
|
+
args its arguments
|
|
61
|
+
cwd the directory it runs in
|
|
62
|
+
env variables added to its environment
|
|
63
|
+
console integratedTerminal (the default, when the client has runInTerminal): the script
|
|
64
|
+
runs in the terminal of the client, and reads what is typed there;
|
|
65
|
+
internalConsole: what it writes comes as output, it reads nothing
|
|
66
|
+
stopOnEntry stop before its first command
|
|
67
|
+
bash the bash to run it with
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## How it works
|
|
71
|
+
|
|
72
|
+
The adapter makes a directory of its own with two fifos: it writes the commands of the harness
|
|
73
|
+
to one (run, step, next, finish, print, vars, stack) and reads its replies from the other. A
|
|
74
|
+
subshell is a process of its own, so what is said to one, a step, a breakpoint, goes to the
|
|
75
|
+
others through files of that directory, which every process looks at, at a test of two files a
|
|
76
|
+
command. A script under the debugger runs some 25 times slower than by itself.
|
|
77
|
+
|
|
78
|
+
When the session ends the adapter ends the script by its process group, the session it has to
|
|
79
|
+
itself; never by a parent, never anything else.
|
|
80
|
+
|
|
81
|
+
## Not yet
|
|
82
|
+
|
|
83
|
+
Conditional breakpoints, breakpoints on functions, a script that another script runs with a
|
|
84
|
+
new bash.
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
GPL-3.0-or-later.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
bash_dap/__init__.py,sha256=5YiggUcRJI8amPeiyVtTcCAaKtOPekKG9u2QCDLF8F0,101
|
|
2
|
+
bash_dap/__main__.py,sha256=A2Mj5VcTnFyu8sHimEGtvVLPYC1H75tVLE9pvkbL3GA,64
|
|
3
|
+
bash_dap/adapter.py,sha256=YcHu3JojRRStjM3kNyR5l_zsT5rhybOOTwsy00mNCEY,21185
|
|
4
|
+
bash_dap/harness.sh,sha256=b4H881KWx3tnIYoCIfmrAiytifqjTB-FlKF1_PV1u4Y,8612
|
|
5
|
+
bash_dap-0.1.dist-info/licenses/LICENSE,sha256=td02ij1L2QjnJpzAGrb3lTv6mhANDHZPh68TuMiTCO0,37350
|
|
6
|
+
bash_dap-0.1.dist-info/METADATA,sha256=Mi-rD2-1sWtUQXK1D46Yb1WczUxjLajiwaT_jyO6PeE,3268
|
|
7
|
+
bash_dap-0.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
bash_dap-0.1.dist-info/entry_points.txt,sha256=xmz3o7GMQWGfejnzFKNerj4PyKU6dHU-_FK6hHzmuQk,51
|
|
9
|
+
bash_dap-0.1.dist-info/top_level.txt,sha256=M70WhxJWD3n6pgxN5gDAJTa0NCT9pnYYqmcWvliT2I8,9
|
|
10
|
+
bash_dap-0.1.dist-info/RECORD,,
|