bash-dap 0.2__tar.gz → 0.3__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.2
3
+ Version: 0.3
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,9 +18,9 @@ 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 on lines and on functions, steps into, over and out of functions,
22
- the call stack, the
23
- variables (arrays and associative arrays as trees), expressions, pause.
21
+ for bash scripts: breakpoints on lines, with conditions, and on functions, steps into, over and
22
+ out of functions, the call stack, the variables (arrays and associative arrays as trees),
23
+ expressions, pause.
24
24
 
25
25
  It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
26
26
  of macOS is 3.2: a newer one, from Homebrew say, is given to the launch as *bash*.) The debugger itself is a script
@@ -80,12 +80,16 @@ A breakpoint on a function stops at its first command, at each call, in a subshe
80
80
  what the script defines later, or a file it sources, is found as well, as the name is all
81
81
  the debugger looks for.
82
82
 
83
+ The condition of a breakpoint is a command of bash, `[[ $f == report.pdf ]]` or
84
+ `(( count > 2 ))`: the script stops there when its status is 0. It runs in a subshell, so it
85
+ sees the variables of the script and changes none of them.
86
+
83
87
  When the session ends the adapter ends the script by its process group, the session it has to
84
88
  itself; never by a parent, never anything else.
85
89
 
86
90
  ## Not yet
87
91
 
88
- Conditional breakpoints, a script that another script runs with a new bash.
92
+ A script that another script runs with a new bash.
89
93
 
90
94
  ## License
91
95
 
@@ -1,9 +1,9 @@
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 on lines and on functions, steps into, over and out of functions,
5
- the call stack, the
6
- variables (arrays and associative arrays as trees), expressions, pause.
4
+ for bash scripts: breakpoints on lines, with conditions, and on functions, steps into, over and
5
+ out of functions, the call stack, the variables (arrays and associative arrays as trees),
6
+ expressions, pause.
7
7
 
8
8
  It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
9
9
  of macOS is 3.2: a newer one, from Homebrew say, is given to the launch as *bash*.) The debugger itself is a script
@@ -63,12 +63,16 @@ A breakpoint on a function stops at its first command, at each call, in a subshe
63
63
  what the script defines later, or a file it sources, is found as well, as the name is all
64
64
  the debugger looks for.
65
65
 
66
+ The condition of a breakpoint is a command of bash, `[[ $f == report.pdf ]]` or
67
+ `(( count > 2 ))`: the script stops there when its status is 0. It runs in a subshell, so it
68
+ sees the variables of the script and changes none of them.
69
+
66
70
  When the session ends the adapter ends the script by its process group, the session it has to
67
71
  itself; never by a parent, never anything else.
68
72
 
69
73
  ## Not yet
70
74
 
71
- Conditional breakpoints, a script that another script runs with a new bash.
75
+ A script that another script runs with a new bash.
72
76
 
73
77
  ## License
74
78
 
@@ -1,3 +1,3 @@
1
1
  """bash-dap: a debug adapter of the Debug Adapter Protocol for bash scripts."""
2
2
 
3
- __version__ = "0.2"
3
+ __version__ = "0.3"
@@ -199,8 +199,11 @@ class Adapter:
199
199
  return
200
200
  with open(os.path.join(self.dir, "breaks"), "w") as f:
201
201
  for path, lines in self.breaks.items():
202
- for line in lines:
203
- f.write("%s:%d\n" % (path, line))
202
+ for line, condition in lines:
203
+ if condition:
204
+ f.write("%s:%d\t%s\n" % (path, line, condition))
205
+ else:
206
+ f.write("%s:%d\n" % (path, line))
204
207
  for name in self.functions:
205
208
  f.write("%s()\n" % name)
206
209
  self.gen += 1
@@ -297,6 +300,7 @@ class Adapter:
297
300
  self.respond(request, {
298
301
  "supportsConfigurationDoneRequest": True,
299
302
  "supportsFunctionBreakpoints": True,
303
+ "supportsConditionalBreakpoints": True,
300
304
  "supportsEvaluateForHovers": True,
301
305
  "supportsTerminateRequest": True,
302
306
  })
@@ -355,12 +359,14 @@ class Adapter:
355
359
  def on_setBreakpoints(self, request):
356
360
  args = request.get("arguments", {})
357
361
  path = os.path.realpath(args.get("source", {}).get("path", ""))
358
- lines = [b["line"] for b in args.get("breakpoints", []) if "line" in b]
362
+ # a line and its condition, a command of bash whose status says whether to stop
363
+ lines = [(b["line"], " ".join(b.get("condition", "").split()))
364
+ for b in args.get("breakpoints", []) if "line" in b]
359
365
  self.breaks[path] = lines
360
366
  self.save_breaks()
361
367
  self.respond(request, {"breakpoints": [
362
368
  {"id": i + 1, "verified": True, "line": line, "source": {"path": path}}
363
- for i, line in enumerate(lines)]})
369
+ for i, (line, _) in enumerate(lines)]})
364
370
 
365
371
  def on_setFunctionBreakpoints(self, request):
366
372
  """The functions to stop in at their first command; a name that is no name of a
@@ -17,7 +17,9 @@
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
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
+ # "breaks" is a breakpoint on the function NAME: the script stops at its first command. After
21
+ # FILE:LINE and a tab, a command of bash is the condition of the breakpoint: the script stops
22
+ # there only when its status is 0.
21
23
  #
22
24
  # A subshell is a process of its own: what it is told, a step, a breakpoint, goes to the others
23
25
  # through these files. The names of the debugger begin with __bdap_.
@@ -35,8 +37,8 @@ __bdap_mode=run
35
37
  __bdap_depth=0
36
38
  # the breakpoints by FILE:LINE, and the generation of the file they came from; __bdap_names has
37
39
  # 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=()
40
+ # NAME() of the functions to stop in; __bdap_conds the conditions of FILE:LINE
41
+ declare -A __bdap_breaks=() __bdap_names=() __bdap_funcs=() __bdap_conds=()
40
42
  __bdap_gen=0
41
43
  __bdap_inside=
42
44
  __bdap_prev= # the place of the command before: a line stops when it is come to, not again
@@ -98,8 +100,13 @@ __bdap_breaks_load () {
98
100
  __bdap_breaks=()
99
101
  __bdap_names=()
100
102
  __bdap_funcs=()
101
- while read -r __bdap_place; do
103
+ __bdap_conds=()
104
+ while IFS= read -r __bdap_place; do
102
105
  [[ -z $__bdap_place ]] && continue
106
+ if [[ $__bdap_place == *$'\t'* ]]; then
107
+ __bdap_conds["${__bdap_place%%$'\t'*}"]=${__bdap_place#*$'\t'}
108
+ __bdap_place=${__bdap_place%%$'\t'*}
109
+ fi
103
110
  if [[ $__bdap_place == *"()" ]]; then
104
111
  __bdap_funcs["$__bdap_place"]=1
105
112
  continue
@@ -148,6 +155,13 @@ __bdap_on_line () {
148
155
  [[ -z $__bdap_word || ${__bdap_text[0]-} == *"$__bdap_word"* ]]
149
156
  }
150
157
 
158
+ # Whether the condition of a breakpoint holds, none always: run in a subshell, it sees the
159
+ # variables of the script and changes none of them
160
+ __bdap_condition () {
161
+ [[ -z $1 ]] && return 0
162
+ ( eval "$1" ) </dev/null >/dev/null 2>&1
163
+ }
164
+
151
165
  # Before a command of the script that may stop: what the script has set, -e, -u, -x, is not
152
166
  # the debugger's, which runs without
153
167
  __bdap_hook () {
@@ -179,7 +193,8 @@ __bdap_check () {
179
193
  next) ((__bdap_d <= __bdap_depth)) && __bdap_reason=step ;;
180
194
  finish) ((__bdap_d < __bdap_depth)) && __bdap_reason=step ;;
181
195
  esac
182
- if [[ -z $__bdap_reason && -n ${__bdap_breaks["$__bdap_file:$__bdap_line"]-} ]] && __bdap_on_line; then
196
+ if [[ -z $__bdap_reason && -n ${__bdap_breaks["$__bdap_file:$__bdap_line"]-} ]] && __bdap_on_line \
197
+ && __bdap_condition "${__bdap_conds["$__bdap_file:$__bdap_line"]-}"; then
183
198
  __bdap_reason=breakpoint
184
199
  fi
185
200
  # the first command of a function to stop in: the calls are deeper than at the command
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bash-dap
3
- Version: 0.2
3
+ Version: 0.3
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,9 +18,9 @@ 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 on lines and on functions, steps into, over and out of functions,
22
- the call stack, the
23
- variables (arrays and associative arrays as trees), expressions, pause.
21
+ for bash scripts: breakpoints on lines, with conditions, and on functions, steps into, over and
22
+ out of functions, the call stack, the variables (arrays and associative arrays as trees),
23
+ expressions, pause.
24
24
 
25
25
  It needs nothing but bash 4.1 or newer and Python 3.8 or newer: no bashdb, no Node.js. (The bash
26
26
  of macOS is 3.2: a newer one, from Homebrew say, is given to the launch as *bash*.) The debugger itself is a script
@@ -80,12 +80,16 @@ A breakpoint on a function stops at its first command, at each call, in a subshe
80
80
  what the script defines later, or a file it sources, is found as well, as the name is all
81
81
  the debugger looks for.
82
82
 
83
+ The condition of a breakpoint is a command of bash, `[[ $f == report.pdf ]]` or
84
+ `(( count > 2 ))`: the script stops there when its status is 0. It runs in a subshell, so it
85
+ sees the variables of the script and changes none of them.
86
+
83
87
  When the session ends the adapter ends the script by its process group, the session it has to
84
88
  itself; never by a parent, never anything else.
85
89
 
86
90
  ## Not yet
87
91
 
88
- Conditional breakpoints, a script that another script runs with a new bash.
92
+ A script that another script runs with a new bash.
89
93
 
90
94
  ## License
91
95
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "bash-dap"
7
- version = "0.2"
7
+ version = "0.3"
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" }
@@ -223,6 +223,27 @@ class SessionTest(unittest.TestCase):
223
223
  c.request("continue", {"threadId": 1})
224
224
  self.assertEqual(c.wait_event("exited")["body"]["exitCode"], 3)
225
225
 
226
+ def test_conditional_breakpoint(self):
227
+ c = self.client = Client()
228
+ c.request("initialize", {"adapterID": "bash-dap", "linesStartAt1": True})
229
+ c.request("launch", {"program": script("backup.sh"), "cwd": SCRIPTS})
230
+ c.wait_event("initialized")
231
+ response = c.request("setBreakpoints", {"source": {"path": script("backup.sh")},
232
+ "breakpoints": [
233
+ {"line": 15, "condition": "[[ $f == photo.jpg ]]"},
234
+ # an assignment in a condition changes nothing of the script
235
+ {"line": 16, "condition": "total=1000; false"}]})
236
+ self.assertTrue(all(b["verified"] for b in response["body"]["breakpoints"]))
237
+ c.request("configurationDone")
238
+ self.assertEqual(c.wait_event("stopped")["body"]["reason"], "breakpoint")
239
+ self.assertEqual(c.stop_place(), [("main", "backup.sh", 15)])
240
+ self.assertEqual(c.evaluate("$f")["body"]["result"], "photo.jpg")
241
+ self.assertEqual(c.evaluate("$total")["body"]["result"], "900")
242
+ # no other round stops, and the total is the script's own
243
+ c.request("continue", {"threadId": 1})
244
+ self.assertEqual(c.wait_event("exited")["body"]["exitCode"], 3)
245
+ self.assertIn("3 files, 2800 bytes", c.output())
246
+
226
247
  def test_pipe_sourced_file_and_dollar_zero(self):
227
248
  c = self.client = Client()
228
249
  c.start(script("pipe.sh"), [(script("lib.sh"), 3), (script("pipe.sh"), 6)])
File without changes
File without changes
File without changes