bash-dap 0.1__tar.gz → 0.2__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bash-dap
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: A debug adapter (Debug Adapter Protocol) for bash scripts, written in bash and Python
5
5
  Author-email: Ilia Maslakov <il.smind@gmail.com>
6
6
  License: GPL-3.0-or-later
@@ -18,7 +18,8 @@ Dynamic: license-file
18
18
  # bash-dap
19
19
 
20
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
21
+ for bash scripts: breakpoints on lines and on functions, steps into, over and out of functions,
22
+ the call stack, the
22
23
  variables (arrays and associative arrays as trees), expressions, pause.
23
24
 
24
25
  It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
@@ -75,13 +76,16 @@ subshell is a process of its own, so what is said to one, a step, a breakpoint,
75
76
  others through files of that directory, which every process looks at, at a test of two files a
76
77
  command. A script under the debugger runs some 25 times slower than by itself.
77
78
 
79
+ A breakpoint on a function stops at its first command, at each call, in a subshell too;
80
+ what the script defines later, or a file it sources, is found as well, as the name is all
81
+ the debugger looks for.
82
+
78
83
  When the session ends the adapter ends the script by its process group, the session it has to
79
84
  itself; never by a parent, never anything else.
80
85
 
81
86
  ## Not yet
82
87
 
83
- Conditional breakpoints, breakpoints on functions, a script that another script runs with a
84
- new bash.
88
+ Conditional breakpoints, a script that another script runs with a new bash.
85
89
 
86
90
  ## License
87
91
 
@@ -1,7 +1,8 @@
1
1
  # bash-dap
2
2
 
3
3
  A debug adapter of the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)
4
- for bash scripts: breakpoints, steps into, over and out of functions, the call stack, the
4
+ for bash scripts: breakpoints on lines and on functions, steps into, over and out of functions,
5
+ the call stack, the
5
6
  variables (arrays and associative arrays as trees), expressions, pause.
6
7
 
7
8
  It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
@@ -58,13 +59,16 @@ subshell is a process of its own, so what is said to one, a step, a breakpoint,
58
59
  others through files of that directory, which every process looks at, at a test of two files a
59
60
  command. A script under the debugger runs some 25 times slower than by itself.
60
61
 
62
+ A breakpoint on a function stops at its first command, at each call, in a subshell too;
63
+ what the script defines later, or a file it sources, is found as well, as the name is all
64
+ the debugger looks for.
65
+
61
66
  When the session ends the adapter ends the script by its process group, the session it has to
62
67
  itself; never by a parent, never anything else.
63
68
 
64
69
  ## Not yet
65
70
 
66
- Conditional breakpoints, breakpoints on functions, a script that another script runs with a
67
- new bash.
71
+ Conditional breakpoints, a script that another script runs with a new bash.
68
72
 
69
73
  ## License
70
74
 
@@ -1,3 +1,3 @@
1
1
  """bash-dap: a debug adapter of the Debug Adapter Protocol for bash scripts."""
2
2
 
3
- __version__ = "0.1"
3
+ __version__ = "0.2"
@@ -15,7 +15,11 @@ import tempfile
15
15
  import threading
16
16
  import time
17
17
 
18
+ from . import __version__
19
+
18
20
  HARNESS = os.path.join(os.path.dirname(os.path.abspath(__file__)), "harness.sh")
21
+ # a name of a function of bash, as a breakpoint takes it: no space, no quote, no parenthesis
22
+ FUNCTION_NAME = re.compile(r"^[^\s()$`'\"\\]+$")
19
23
  VARIABLES_REF = 1 # the reference of the variables of a stop
20
24
 
21
25
 
@@ -108,6 +112,7 @@ class Adapter:
108
112
  self.alive = False
109
113
  self.pause_asked = False
110
114
  self.breaks = {}
115
+ self.functions = []
111
116
  self.gen = 0
112
117
  self.refs = {}
113
118
  self.next_ref = VARIABLES_REF + 1
@@ -196,6 +201,8 @@ class Adapter:
196
201
  for path, lines in self.breaks.items():
197
202
  for line in lines:
198
203
  f.write("%s:%d\n" % (path, line))
204
+ for name in self.functions:
205
+ f.write("%s()\n" % name)
199
206
  self.gen += 1
200
207
  open(os.path.join(self.dir, "gen.%d" % self.gen), "w").close()
201
208
 
@@ -255,6 +262,8 @@ class Adapter:
255
262
  return
256
263
  if self.pause_asked and reason == "step":
257
264
  reason = "pause"
265
+ elif reason == "function":
266
+ reason = "function breakpoint"
258
267
  self.pause_asked = False
259
268
  self.event("stopped", {"reason": reason, "threadId": 1, "allThreadsStopped": True})
260
269
 
@@ -287,6 +296,7 @@ class Adapter:
287
296
  self.client = request.get("arguments", {})
288
297
  self.respond(request, {
289
298
  "supportsConfigurationDoneRequest": True,
299
+ "supportsFunctionBreakpoints": True,
290
300
  "supportsEvaluateForHovers": True,
291
301
  "supportsTerminateRequest": True,
292
302
  })
@@ -353,9 +363,20 @@ class Adapter:
353
363
  for i, line in enumerate(lines)]})
354
364
 
355
365
  def on_setFunctionBreakpoints(self, request):
356
- self.respond(request, {"breakpoints": [
357
- {"verified": False, "message": "bash-dap has breakpoints on lines only"}
358
- for _ in request.get("arguments", {}).get("breakpoints", [])]})
366
+ """The functions to stop in at their first command; a name that is no name of a
367
+ function of bash is not taken"""
368
+ answers = []
369
+ self.functions = []
370
+ for b in request.get("arguments", {}).get("breakpoints", []):
371
+ name = b.get("name", "")
372
+ if FUNCTION_NAME.match(name):
373
+ self.functions.append(name)
374
+ answers.append({"id": 1000 + len(self.functions), "verified": True})
375
+ else:
376
+ answers.append({"verified": False,
377
+ "message": "no name of a function of bash: %s" % name})
378
+ self.save_breaks()
379
+ self.respond(request, {"breakpoints": answers})
359
380
 
360
381
  def on_setExceptionBreakpoints(self, request):
361
382
  self.respond(request, {"breakpoints": []})
@@ -527,7 +548,7 @@ def main():
527
548
  "on stdin and stdout")
528
549
  return 0
529
550
  if len(sys.argv) > 1 and sys.argv[1] == "--version":
530
- print("bash-dap 0.1")
551
+ print("bash-dap %s" % __version__)
531
552
  return 0
532
553
  Adapter(sys.stdin.buffer, sys.stdout.buffer).run()
533
554
  return 0
@@ -9,14 +9,15 @@
9
9
  # "replies", which it reads what the debugger says from, a line each:
10
10
  #
11
11
  # commands: run | step | next | finish | print EXPRESSION | vars | stack
12
- # replies: stopped REASON LINE FUNCTION FILE (REASON: entry, breakpoint, step)
12
+ # replies: stopped REASON LINE FUNCTION FILE (REASON: entry, breakpoint, function, step)
13
13
  # value TEXT | var NAME TEXT | frame LEVEL FUNCTION LINE FILE | error TEXT
14
14
  # done, after the reply to a command | exited CODE
15
15
  #
16
16
  # TEXT is quoted as printf %q does, so that a reply is one line. A command is read only while
17
17
  # the script is stopped. At any time the adapter may write the breakpoints to the file "breaks",
18
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".
19
+ # script at its next command by writing "step 0" to the file "state". A line NAME() of
20
+ # "breaks" is a breakpoint on the function NAME: the script stops at its first command.
20
21
  #
21
22
  # A subshell is a process of its own: what it is told, a step, a breakpoint, goes to the others
22
23
  # through these files. The names of the debugger begin with __bdap_.
@@ -33,11 +34,14 @@ __bdap_state=$__bdap_dir/state
33
34
  __bdap_mode=run
34
35
  __bdap_depth=0
35
36
  # 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=()
37
+ # NAME:LINE, the name of the file only, that the trap tests at each command; __bdap_funcs has
38
+ # NAME() of the functions to stop in
39
+ declare -A __bdap_breaks=() __bdap_names=() __bdap_funcs=()
38
40
  __bdap_gen=0
39
41
  __bdap_inside=
40
42
  __bdap_prev= # the place of the command before: a line stops when it is come to, not again
43
+ __bdap_fdepth=0 # the depth of the calls at the command before: more is a function entered
44
+ __bdap_fenter= # a function to stop in is entered: its first command is next
41
45
 
42
46
  # the variables of bash itself, and those it makes for a function: not shown
43
47
  __bdap_known=" $(compgen -v | tr '\n' ' ') FUNCNAME BASH_ARGC BASH_ARGV BASH_LINENO BASH_SOURCE COLUMNS LINES "
@@ -93,8 +97,13 @@ __bdap_breaks_load () {
93
97
  done
94
98
  __bdap_breaks=()
95
99
  __bdap_names=()
100
+ __bdap_funcs=()
96
101
  while read -r __bdap_place; do
97
102
  [[ -z $__bdap_place ]] && continue
103
+ if [[ $__bdap_place == *"()" ]]; then
104
+ __bdap_funcs["$__bdap_place"]=1
105
+ continue
106
+ fi
98
107
  __bdap_breaks["$__bdap_place"]=1
99
108
  __bdap_names["${__bdap_place##*/}"]=1
100
109
  done <"$__bdap_dir/breaks"
@@ -126,6 +135,7 @@ __bdap_wait () {
126
135
  __bdap_resume run 0
127
136
  __bdap_breaks=()
128
137
  __bdap_names=()
138
+ __bdap_funcs=()
129
139
  trap - DEBUG
130
140
  }
131
141
 
@@ -172,6 +182,16 @@ __bdap_check () {
172
182
  if [[ -z $__bdap_reason && -n ${__bdap_breaks["$__bdap_file:$__bdap_line"]-} ]] && __bdap_on_line; then
173
183
  __bdap_reason=breakpoint
174
184
  fi
185
+ # the first command of a function to stop in: the calls are deeper than at the command
186
+ # before; bash comes first to the line that defines it, with the call as the command
187
+ if [[ -z $__bdap_reason && -n ${__bdap_funcs["${FUNCNAME[2]-}()"]-} ]]; then
188
+ if ((__bdap_d - 1 > __bdap_fdepth)) && [[ $__bdap_command == "${FUNCNAME[2]}"* ]]; then
189
+ __bdap_fenter=1
190
+ elif ((__bdap_d - 1 > __bdap_fdepth)) || [[ -n $__bdap_fenter ]]; then
191
+ __bdap_reason=function
192
+ fi
193
+ fi
194
+ [[ -n $__bdap_reason ]] && __bdap_fenter=
175
195
  # the next command of a line is no new place to stop at
176
196
  [[ -z $__bdap_reason || $__bdap_prev == "$__bdap_line:${BASH_SOURCE[2]}" ]] && return 0
177
197
  # the lines of the frames, the one stopped in first
@@ -195,12 +215,14 @@ trap '__bdap_say "exited $?"' EXIT
195
215
  shopt -s extdebug
196
216
  set -o functrace
197
217
  # 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)
218
+ # on the line or on the function, what another process has said; else a command costs a test
219
+ # of two files. One line: a second one would have LINENO one more
220
+ # (with set -u of the script it runs too: nothing unset is read, BASH_SOURCE is empty at the end,
221
+ # FUNCNAME unset out of any function)
201
222
  __bdap_trap='[[ -n $__bdap_inside || ( $__bdap_mode == run'
202
223
  __bdap_trap+=' && -z ${__bdap_names["${BASH_SOURCE[0]:+${BASH_SOURCE[0]##*/}}:$LINENO"]-}'
224
+ __bdap_trap+=' && -z ${__bdap_funcs["${FUNCNAME[0]-}()"]-}'
203
225
  __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]-}'
226
+ __bdap_trap+=' __bdap_prev=$LINENO:${BASH_SOURCE[0]-} __bdap_fdepth=${FUNCNAME[0]+${#FUNCNAME[@]}}'
205
227
  trap "$__bdap_trap" DEBUG
206
228
  . "$0" "$@"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bash-dap
3
- Version: 0.1
3
+ Version: 0.2
4
4
  Summary: A debug adapter (Debug Adapter Protocol) for bash scripts, written in bash and Python
5
5
  Author-email: Ilia Maslakov <il.smind@gmail.com>
6
6
  License: GPL-3.0-or-later
@@ -18,7 +18,8 @@ Dynamic: license-file
18
18
  # bash-dap
19
19
 
20
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
21
+ for bash scripts: breakpoints on lines and on functions, steps into, over and out of functions,
22
+ the call stack, the
22
23
  variables (arrays and associative arrays as trees), expressions, pause.
23
24
 
24
25
  It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
@@ -75,13 +76,16 @@ subshell is a process of its own, so what is said to one, a step, a breakpoint,
75
76
  others through files of that directory, which every process looks at, at a test of two files a
76
77
  command. A script under the debugger runs some 25 times slower than by itself.
77
78
 
79
+ A breakpoint on a function stops at its first command, at each call, in a subshell too;
80
+ what the script defines later, or a file it sources, is found as well, as the name is all
81
+ the debugger looks for.
82
+
78
83
  When the session ends the adapter ends the script by its process group, the session it has to
79
84
  itself; never by a parent, never anything else.
80
85
 
81
86
  ## Not yet
82
87
 
83
- Conditional breakpoints, breakpoints on functions, a script that another script runs with a
84
- new bash.
88
+ Conditional breakpoints, a script that another script runs with a new bash.
85
89
 
86
90
  ## License
87
91
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bash-dap"
7
- version = "0.1"
7
+ version = "0.2"
8
8
  description = "A debug adapter (Debug Adapter Protocol) for bash scripts, written in bash and Python"
9
9
  readme = "README.md"
10
10
  license = { text = "GPL-3.0-or-later" }
@@ -198,6 +198,31 @@ class SessionTest(unittest.TestCase):
198
198
  c.wait_event("terminated")
199
199
  self.assertIn("3 files, 2800 bytes", c.output())
200
200
 
201
+ def test_function_breakpoints(self):
202
+ c = self.client = Client()
203
+ c.request("initialize", {"adapterID": "bash-dap", "linesStartAt1": True})
204
+ c.request("launch", {"program": script("backup.sh"), "cwd": SCRIPTS})
205
+ c.wait_event("initialized")
206
+ response = c.request("setFunctionBreakpoints", {"breakpoints": [
207
+ {"name": "size_of"}, {"name": "not a name"}]})
208
+ verified = [b["verified"] for b in response["body"]["breakpoints"]]
209
+ self.assertEqual(verified, [True, False])
210
+ c.request("configurationDone")
211
+ # at the first command of the function, in the subshell of $(...)
212
+ self.assertEqual(c.wait_event("stopped")["body"]["reason"], "function breakpoint")
213
+ self.assertEqual(c.stop_place(), [("size_of", "backup.sh", 7), ("main", "backup.sh", 15)])
214
+ # once a call, not at each of its commands
215
+ c.request("next", {"threadId": 1})
216
+ c.wait_event("stopped")
217
+ self.assertEqual(c.stop_place()[0], ("size_of", "backup.sh", 8))
218
+ c.request("continue", {"threadId": 1})
219
+ c.wait_event("stopped")
220
+ self.assertEqual(c.evaluate("$f")["body"]["result"], "photo.jpg")
221
+ # taken off, the script runs to its end
222
+ c.request("setFunctionBreakpoints", {"breakpoints": []})
223
+ c.request("continue", {"threadId": 1})
224
+ self.assertEqual(c.wait_event("exited")["body"]["exitCode"], 3)
225
+
201
226
  def test_pipe_sourced_file_and_dollar_zero(self):
202
227
  c = self.client = Client()
203
228
  c.start(script("pipe.sh"), [(script("lib.sh"), 3), (script("pipe.sh"), 6)])
File without changes
File without changes
File without changes