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/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,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ bash-dap = bash_dap.adapter:main