toolbox 0.2.0 → 0.3.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 694d8c20e3efb7fad4c3ce738ab9784308fca74971e7198e627458349be2f316
4
- data.tar.gz: 41e1fa7e7352ade3a28fdc90ec0db233f937dfef0ddb1a1ac819c052c9edb1f4
3
+ metadata.gz: f8bc49b89f83b1baa5270a63e16df4c63dfc3de61fa98e8c6da8fabbcde66e87
4
+ data.tar.gz: cd5b34b044e80e8aa2b278a67fa138cd5230829da83aa9710b04b8b77478b8ff
5
5
  SHA512:
6
- metadata.gz: 65ebb449eadbcd6c92ba560aac7fd3216e9e50c2fdc2864eb2daf2c0169191d678c7cda7956e3a3ee359eab926bc08c13ff0d8fd1605509b999cb69516d8031a
7
- data.tar.gz: '0806493639960770d85ff768e2ad985657b46b7db46327acdfb6248e44cf1cb6b39586273975ce7c7f2e5128307cfc0a667cc13e846d29023615e3b7f9c79068'
6
+ metadata.gz: 891326e75355c9a42cbed524b63345f7b47e6ee8ed29f5c08ee96a5c8a40cb495ad1913bfeb0043b2479f1e08ff4c763d93b4f8231a30f5b958a356eabc0ae46
7
+ data.tar.gz: c3edb1db9689d4b399c96a112876d8b8ba3f3b4754903d61e2fafb999d2913409509f4ee2a4964096a4ed5b30ccec0647deee5843a31d77b8700de0ef62ee9b9
checksums.yaml.gz.sig CHANGED
Binary file
@@ -161,7 +161,7 @@ After switching to a fiber with `rb-fiber-scan-switch`, you can use standard GDB
161
161
  (gdb) bt # Show C backtrace
162
162
  (gdb) frame <n> # Switch to specific frame
163
163
  (gdb) info locals # Show local variables
164
- (gdb) rb-object-print $errinfo # Print exception if present
164
+ (gdb) rb-print $errinfo # Print exception if present
165
165
  ~~~
166
166
 
167
167
  The fiber switch command sets up several convenience variables:
@@ -15,7 +15,7 @@ $ gem install toolbox
15
15
  Install the GDB extensions (automatically adds to `~/.gdbinit`):
16
16
 
17
17
  ~~~ bash
18
- $ bake toolbox:gdb:install
18
+ $ bake -g toolbox toolbox:gdb:install
19
19
  ~~~
20
20
 
21
21
  This adds a single line to your `~/.gdbinit` that sources the extensions from the gem's data directory. The extensions will then load automatically every time you start GDB.
@@ -23,7 +23,7 @@ This adds a single line to your `~/.gdbinit` that sources the extensions from th
23
23
  To install to a custom `.gdbinit` location:
24
24
 
25
25
  ~~~ bash
26
- $ bake toolbox:gdb:install gdbinit=/path/to/custom/gdbinit
26
+ $ bake -g toolbox toolbox:gdb:install gdbinit=/path/to/custom/gdbinit
27
27
  ~~~
28
28
 
29
29
  ### Installing LLDB Extensions
@@ -31,7 +31,7 @@ $ bake toolbox:gdb:install gdbinit=/path/to/custom/gdbinit
31
31
  Install the LLDB extensions (automatically adds to `~/.lldbinit`):
32
32
 
33
33
  ~~~ bash
34
- $ bake toolbox:lldb:install
34
+ $ bake -g toolbox toolbox:lldb:install
35
35
  ~~~
36
36
 
37
37
  This adds a command script import line to your `~/.lldbinit` that loads the extensions from the gem's data directory. The extensions will then load automatically every time you start LLDB.
@@ -39,7 +39,7 @@ This adds a command script import line to your `~/.lldbinit` that loads the exte
39
39
  To install to a custom `.lldbinit` location:
40
40
 
41
41
  ~~~ bash
42
- $ bake toolbox:lldb:install lldbinit=/path/to/custom/lldbinit
42
+ $ bake -g toolbox toolbox:lldb:install lldbinit=/path/to/custom/lldbinit
43
43
  ~~~
44
44
 
45
45
  ### Verifying Installation
@@ -47,7 +47,7 @@ $ bake toolbox:lldb:install lldbinit=/path/to/custom/lldbinit
47
47
  Check GDB installation status:
48
48
 
49
49
  ~~~ bash
50
- $ bake toolbox:gdb:info
50
+ $ bake -g toolbox toolbox:gdb:info
51
51
  Ruby Toolbox GDB Extensions v0.1.0
52
52
  Status: ✓ Installed
53
53
  ~~~
@@ -55,7 +55,7 @@ Status: ✓ Installed
55
55
  Check LLDB installation status:
56
56
 
57
57
  ~~~ bash
58
- $ bake toolbox:lldb:info
58
+ $ bake -g toolbox toolbox:lldb:info
59
59
  Ruby Toolbox LLDB Extensions v0.1.0
60
60
  Status: ✓ Installed
61
61
  ~~~
@@ -63,7 +63,7 @@ Status: ✓ Installed
63
63
  Test that extensions load automatically:
64
64
 
65
65
  ~~~ bash
66
- $ gdb --batch -ex "help rb-object-print"
66
+ $ gdb --batch -ex "help rb-print"
67
67
  Recursively print Ruby hash and array structures...
68
68
  ~~~
69
69
 
@@ -72,23 +72,23 @@ Recursively print Ruby hash and array structures...
72
72
  To remove the GDB extensions:
73
73
 
74
74
  ~~~ bash
75
- $ bake toolbox:gdb:uninstall
75
+ $ bake -g toolbox toolbox:gdb:uninstall
76
76
  ~~~
77
77
 
78
78
  To remove the LLDB extensions:
79
79
 
80
80
  ~~~ bash
81
- $ bake toolbox:lldb:uninstall
81
+ $ bake -g toolbox toolbox:lldb:uninstall
82
82
  ~~~
83
83
 
84
- This removes the source line from your `~/.gdbinit`.
84
+ This removes the source line from your `~/.gdbinit` or `~/.lldbinit`.
85
85
 
86
86
  ## Core Concepts
87
87
 
88
88
  Ruby GDB provides specialized commands for debugging Ruby at multiple levels:
89
89
 
90
90
  - **Context Setup** (`rb-context`) - Get current execution context and set up convenience variables
91
- - **Object Inspection** (`rb-object-print`) - View Ruby objects, hashes, arrays, and structs with proper formatting
91
+ - **Object Inspection** (`rb-print`) - View Ruby objects, hashes, arrays, and structs with proper formatting
92
92
  - **Fiber Debugging** (`rb-fiber-*`) - Scan heap for fibers, inspect state, and switch contexts
93
93
  - **Stack Analysis** (`rb-stack-trace`) - Examine combined VM (Ruby) and C (native) stack frames
94
94
  - **Heap Navigation** (`rb-heap-scan`) - Scan the Ruby heap to find objects by type
@@ -132,7 +132,7 @@ Diagnose the issue (extensions load automatically if installed):
132
132
  (gdb) rb-fiber-scan-heap # Scan heap for all fibers
133
133
  (gdb) rb-fiber-scan-stack-trace-all # Show backtraces for all fibers
134
134
  (gdb) rb-fiber-scan-switch 0 # Switch to main fiber
135
- (gdb) rb-object-print $errinfo --depth 2 # Print exception (now $errinfo is set)
135
+ (gdb) rb-print $errinfo --depth 2 # Print exception (now $errinfo is set)
136
136
  (gdb) rb-heap-scan --type RUBY_T_HASH --limit 10 # Find hashes
137
137
  ~~~
138
138
 
@@ -146,7 +146,7 @@ When a Ruby exception occurs, you can inspect it in detail:
146
146
  (gdb) break rb_exc_raise
147
147
  (gdb) run
148
148
  (gdb) rb-context
149
- (gdb) rb-object-print $errinfo --depth 2
149
+ (gdb) rb-print $errinfo --depth 2
150
150
  ~~~
151
151
 
152
152
  This shows the exception class, message, and any nested structures. The `rb-context` command displays the current execution context and sets up `$ec`, `$cfp`, and `$errinfo` convenience variables.
@@ -167,7 +167,7 @@ When working with fibers, you often need to see what each fiber is doing:
167
167
  Ruby hashes and arrays can contain nested structures:
168
168
 
169
169
  ~~~
170
- (gdb) rb-object-print $some_hash --depth 2
170
+ (gdb) rb-print $some_hash --depth 2
171
171
  <T_HASH@...>
172
172
  [ 0] K: <T_SYMBOL> :name
173
173
  V: <T_STRING@...> "Alice"
@@ -176,3 +176,25 @@ Ruby hashes and arrays can contain nested structures:
176
176
  ~~~
177
177
 
178
178
  The `--depth` flag controls how deep to recurse into nested objects.
179
+
180
+ ## Requirements
181
+
182
+ - GDB with Python support (GDB 7.0+) or LLDB with Python support.
183
+ - Ruby 3.3+ recommended.
184
+
185
+ ### Platform Support
186
+
187
+ - **Linux**: Full support with all features (GDB or LLDB).
188
+ - **macOS**:
189
+ - Ruby head: Full support.
190
+ - Ruby 3.4.x: Limited support (see below).
191
+ - **BSD**: Should work similar to Linux (untested).
192
+
193
+ ### macOS + Ruby 3.4.x Limitation
194
+
195
+ On macOS with LLDB and Ruby <= 3.4.x, some commands including `rb-fiber-scan-heap` will not work due to a `dsymutil` bug that drops `struct RTypedData` from debug symbols. This appears fixed in `ruby-head`.
196
+
197
+ **Workarounds:**
198
+ - Use Ruby head: `ruby-install ruby-head -- CFLAGS="-g -O0"`
199
+ - Use GDB instead of LLDB (works with Ruby 3.4.x)
200
+ - Other commands like `rb-print`, `rb-stack-trace`, `rb-context` work fine
data/context/index.yaml CHANGED
@@ -12,7 +12,7 @@ files:
12
12
  programs and core dumps with GDB or LLDB.
13
13
  - path: object-inspection.md
14
14
  title: Object Inspection
15
- description: This guide explains how to use `rb-object-print` to inspect Ruby objects,
15
+ description: This guide explains how to use `rb-print` to inspect Ruby objects,
16
16
  hashes, arrays, and structs in GDB.
17
17
  - path: stack-inspection.md
18
18
  title: Stack Inspection
@@ -1,12 +1,12 @@
1
1
  # Object Inspection
2
2
 
3
- This guide explains how to use `rb-object-print` to inspect Ruby objects, hashes, arrays, and structs in GDB.
3
+ This guide explains how to use `rb-print` to inspect Ruby objects, hashes, arrays, and structs in GDB.
4
4
 
5
5
  ## Why Object Inspection Matters
6
6
 
7
7
  When debugging Ruby programs or analyzing core dumps, you often need to inspect complex data structures that are difficult to read in their raw memory representation. Standard GDB commands show pointer addresses and raw memory, but not the logical structure of Ruby objects.
8
8
 
9
- Use `rb-object-print` when you need:
9
+ Use `rb-print` when you need:
10
10
 
11
11
  - **Understand exception objects**: See the full exception hierarchy, message, and backtrace data
12
12
  - **Inspect fiber storage**: View thread-local data and fiber-specific variables
@@ -15,12 +15,12 @@ Use `rb-object-print` when you need:
15
15
 
16
16
  ## Basic Usage
17
17
 
18
- The `rb-object-print` command recursively prints Ruby objects in a human-readable format.
18
+ The `rb-print` command recursively prints Ruby objects in a human-readable format.
19
19
 
20
20
  ### Syntax
21
21
 
22
22
  ~~~
23
- rb-object-print <expression> [--depth N] [--debug]
23
+ rb-print <expression> [--depth N] [--debug]
24
24
  ~~~
25
25
 
26
26
  Where:
@@ -33,10 +33,10 @@ Where:
33
33
  Print immediate values and special constants:
34
34
 
35
35
  ~~~
36
- (gdb) rb-object-print 0 # <T_FALSE>
37
- (gdb) rb-object-print 8 # <T_NIL>
38
- (gdb) rb-object-print 20 # <T_TRUE>
39
- (gdb) rb-object-print 85 # <T_FIXNUM> 42
36
+ (gdb) rb-print 0 # <T_FALSE>
37
+ (gdb) rb-print 8 # <T_NIL>
38
+ (gdb) rb-print 20 # <T_TRUE>
39
+ (gdb) rb-print 85 # <T_FIXNUM> 42
40
40
  ~~~
41
41
 
42
42
  These work without any Ruby process running, making them useful for learning and testing.
@@ -46,10 +46,10 @@ These work without any Ruby process running, making them useful for learning and
46
46
  Use any valid GDB expression:
47
47
 
48
48
  ~~~
49
- (gdb) rb-object-print $ec->errinfo # Exception object
50
- (gdb) rb-object-print $ec->cfp->sp[-1] # Top of VM stack
51
- (gdb) rb-object-print $ec->storage # Fiber storage hash
52
- (gdb) rb-object-print (VALUE)0x00007f8a12345678 # Object at specific address
49
+ (gdb) rb-print $ec->errinfo # Exception object
50
+ (gdb) rb-print $ec->cfp->sp[-1] # Top of VM stack
51
+ (gdb) rb-print $ec->storage # Fiber storage hash
52
+ (gdb) rb-print (VALUE)0x00007f8a12345678 # Object at specific address
53
53
  ~~~
54
54
 
55
55
  ## Inspecting Hashes
@@ -61,7 +61,7 @@ Ruby hashes have two internal representations (ST table and AR table). The comma
61
61
  For hashes with fewer than 8 entries, Ruby uses an array-based implementation:
62
62
 
63
63
  ~~~
64
- (gdb) rb-object-print $some_hash
64
+ (gdb) rb-print $some_hash
65
65
  <T_HASH@...>
66
66
  [ 0] K: <T_SYMBOL> :name
67
67
  V: <T_STRING@...> "Alice"
@@ -76,7 +76,7 @@ For hashes with fewer than 8 entries, Ruby uses an array-based implementation:
76
76
  For larger hashes, Ruby uses a hash table (output format is similar):
77
77
 
78
78
  ~~~
79
- (gdb) rb-object-print $large_hash
79
+ (gdb) rb-print $large_hash
80
80
  <T_HASH@...>
81
81
  [ 0] K: <T_SYMBOL> :user_id
82
82
  V: <T_FIXNUM> 12345
@@ -90,12 +90,12 @@ For larger hashes, Ruby uses a hash table (output format is similar):
90
90
  Prevent overwhelming output from deeply nested structures:
91
91
 
92
92
  ~~~
93
- (gdb) rb-object-print $nested_hash --depth 1 # Only top level
93
+ (gdb) rb-print $nested_hash --depth 1 # Only top level
94
94
  <T_HASH@...>
95
95
  [ 0] K: <T_SYMBOL> :data
96
96
  V: <T_HASH@...> # Nested hash not expanded
97
97
 
98
- (gdb) rb-object-print $nested_hash --depth 2 # Expand one level
98
+ (gdb) rb-print $nested_hash --depth 2 # Expand one level
99
99
  <T_HASH@...>
100
100
  [ 0] K: <T_SYMBOL> :data
101
101
  V: <T_HASH@...>
@@ -114,7 +114,7 @@ Arrays also have two representations based on size:
114
114
  Arrays display their elements with type information:
115
115
 
116
116
  ~~~
117
- (gdb) rb-object-print $array
117
+ (gdb) rb-print $array
118
118
  <T_ARRAY@...>
119
119
  [ 0] <T_FIXNUM> 1
120
120
  [ 1] <T_FIXNUM> 2
@@ -124,7 +124,7 @@ Arrays display their elements with type information:
124
124
  For arrays with nested objects:
125
125
 
126
126
  ~~~
127
- (gdb) rb-object-print $array --depth 2
127
+ (gdb) rb-print $array --depth 2
128
128
  <T_ARRAY@...>
129
129
  [ 0] <T_STRING@...> "first item"
130
130
  [ 1] <T_HASH@...>
@@ -138,7 +138,7 @@ For arrays with nested objects:
138
138
  Ruby Struct objects work similarly to arrays:
139
139
 
140
140
  ~~~
141
- (gdb) rb-object-print $struct_instance
141
+ (gdb) rb-print $struct_instance
142
142
  <T_STRUCT@...>
143
143
  [ 0] <T_STRING@...> "John"
144
144
  [ 1] <T_FIXNUM> 25
@@ -155,7 +155,7 @@ When a fiber has an exception, inspect it:
155
155
  ~~~
156
156
  (gdb) rb-fiber-scan-heap
157
157
  (gdb) rb-fiber-scan-switch 5 # Switch to fiber #5
158
- (gdb) rb-object-print $errinfo --depth 3
158
+ (gdb) rb-print $errinfo --depth 3
159
159
  ~~~
160
160
 
161
161
  This reveals the full exception structure including any nested causes. After switching to a fiber, `$errinfo` and `$ec` convenience variables are automatically set.
@@ -167,8 +167,8 @@ Break at a method and examine arguments on the stack:
167
167
  ~~~
168
168
  (gdb) break some_method
169
169
  (gdb) run
170
- (gdb) rb-object-print $ec->cfp->sp[-1] # Last argument
171
- (gdb) rb-object-print $ec->cfp->sp[-2] # Second-to-last argument
170
+ (gdb) rb-print $ec->cfp->sp[-1] # Last argument
171
+ (gdb) rb-print $ec->cfp->sp[-2] # Second-to-last argument
172
172
  ~~~
173
173
 
174
174
  ### Examining Fiber Storage
@@ -178,17 +178,17 @@ Thread-local variables are stored in fiber storage:
178
178
  ~~~
179
179
  (gdb) rb-fiber-scan-heap
180
180
  (gdb) rb-fiber-scan-switch 0
181
- (gdb) rb-object-print $ec->storage --depth 2
181
+ (gdb) rb-print $ec->storage --depth 2
182
182
  ~~~
183
183
 
184
184
  This shows all thread-local variables and their values.
185
185
 
186
186
  ## Debugging with --debug Flag
187
187
 
188
- When `rb-object-print` doesn't show what you expect, use `--debug`:
188
+ When `rb-print` doesn't show what you expect, use `--debug`:
189
189
 
190
190
  ~~~
191
- (gdb) rb-object-print $suspicious_value --debug
191
+ (gdb) rb-print $suspicious_value --debug
192
192
  DEBUG: Evaluated '$suspicious_value' to 0x7f8a1c567890
193
193
  DEBUG: Loaded constant RUBY_T_MASK = 31
194
194
  DEBUG: Object at 0x7f8a1c567890 with flags=0x20040005, type=0x5
@@ -140,9 +140,9 @@ See what values are on the current frame's stack:
140
140
  (gdb) set $sp = $ec->cfp->sp
141
141
 
142
142
  # Print values on stack
143
- (gdb) rb-object-print *(VALUE*)($sp - 1) # Top of stack
144
- (gdb) rb-object-print *(VALUE*)($sp - 2) # Second value
145
- (gdb) rb-object-print *(VALUE*)($sp - 3) # Third value
143
+ (gdb) rb-print *(VALUE*)($sp - 1) # Top of stack
144
+ (gdb) rb-print *(VALUE*)($sp - 2) # Second value
145
+ (gdb) rb-print *(VALUE*)($sp - 3) # Third value
146
146
  ~~~
147
147
 
148
148
  ### Tracking Fiber Switches
@@ -19,6 +19,231 @@ class Arguments:
19
19
  """Get an option value with optional default"""
20
20
  return self.options.get(option_name, default)
21
21
 
22
+
23
+ class Usage:
24
+ """Command specification DSL for declarative command interfaces.
25
+
26
+ Defines what parameters, options, and flags a command accepts,
27
+ enabling validation and help text generation.
28
+
29
+ Example:
30
+ usage = Usage(
31
+ summary="Print Ruby objects with recursion",
32
+ parameters=['value'],
33
+ options={'depth': (int, 1), 'limit': (int, None)},
34
+ flags=['debug', 'verbose']
35
+ )
36
+
37
+ arguments = usage.parse("$var --depth 3 --debug")
38
+ # arguments.expressions = ['$var']
39
+ # arguments.options = {'depth': 3}
40
+ # arguments.flags = {'debug'}
41
+ """
42
+
43
+ def __init__(self, summary, parameters=None, options=None, flags=None, examples=None):
44
+ """Define command interface.
45
+
46
+ Args:
47
+ summary: One-line command description
48
+ parameters: List of parameter names or tuples (name, description)
49
+ options: Dict of {name: (type, default, description)}
50
+ flags: List of flag names or tuples (name, description)
51
+ examples: List of example command strings with descriptions
52
+
53
+ Example:
54
+ Usage(
55
+ summary="Scan heap for objects",
56
+ parameters=[('type_name', 'Ruby type to search for')],
57
+ options={
58
+ 'limit': (int, None, 'Maximum objects to find'),
59
+ 'depth': (int, 1, 'Recursion depth')
60
+ },
61
+ flags=[
62
+ ('terminated', 'Include terminated fibers'),
63
+ ('cache', 'Use cached results')
64
+ ],
65
+ examples=[
66
+ ("rb-heap-scan --type RUBY_T_STRING", "Find all strings"),
67
+ ("rb-heap-scan --type RUBY_T_HASH --limit 10", "Find first 10 hashes")
68
+ ]
69
+ )
70
+ """
71
+ self.summary = summary
72
+ self.parameters = self._normalize_params(parameters or [])
73
+ self.options = options or {}
74
+ self.flags = self._normalize_flags(flags or [])
75
+ self.examples = examples or []
76
+
77
+ def _normalize_params(self, params):
78
+ """Normalize parameters to list of (name, description) tuples."""
79
+ normalized = []
80
+ for param in params:
81
+ if isinstance(param, tuple):
82
+ normalized.append(param)
83
+ else:
84
+ normalized.append((param, None))
85
+ return normalized
86
+
87
+ def _normalize_flags(self, flags):
88
+ """Normalize flags to list of (name, description) tuples."""
89
+ normalized = []
90
+ for flag in flags:
91
+ if isinstance(flag, tuple):
92
+ normalized.append(flag)
93
+ else:
94
+ normalized.append((flag, None))
95
+ return normalized
96
+
97
+ def parse(self, argument_string):
98
+ """Parse and validate command arguments.
99
+
100
+ Args:
101
+ argument_string: Raw argument string from debugger
102
+
103
+ Returns:
104
+ Arguments object with validated and type-converted values
105
+
106
+ Raises:
107
+ ValueError: If validation fails (wrong parameter count, invalid types, etc.)
108
+ """
109
+ # Use existing parser to extract raw arguments
110
+ arguments = parse_arguments(argument_string)
111
+
112
+ # Validate parameter count
113
+ if len(arguments.expressions) != len(self.parameters):
114
+ if len(self.parameters) == 0 and len(arguments.expressions) > 0:
115
+ raise ValueError(f"Command takes no parameters, got {len(arguments.expressions)}")
116
+ elif len(self.parameters) == 1:
117
+ raise ValueError(f"Command requires 1 parameter, got {len(arguments.expressions)}")
118
+ else:
119
+ raise ValueError(f"Command requires {len(self.parameters)} parameters, got {len(arguments.expressions)}")
120
+
121
+ # Validate and convert option types
122
+ converted_options = {}
123
+ for option_name, option_value in arguments.options.items():
124
+ if option_name not in self.options:
125
+ raise ValueError(f"Unknown option: --{option_name}")
126
+
127
+ # Unpack option spec (handle 2-tuple or 3-tuple)
128
+ opt_spec = self.options[option_name]
129
+ option_type = opt_spec[0]
130
+ option_default = opt_spec[1]
131
+
132
+ # Convert to specified type
133
+ try:
134
+ if option_type == int:
135
+ converted_options[option_name] = int(option_value)
136
+ elif option_type == str:
137
+ converted_options[option_name] = str(option_value)
138
+ elif option_type == bool:
139
+ converted_options[option_name] = bool(option_value)
140
+ else:
141
+ # Custom type converter
142
+ converted_options[option_name] = option_type(option_value)
143
+ except (ValueError, TypeError) as e:
144
+ raise ValueError(f"Invalid value for --{option_name}: {option_value} (expected {option_type.__name__})")
145
+
146
+ # Add defaults for missing options
147
+ for option_name, opt_spec in self.options.items():
148
+ option_default = opt_spec[1]
149
+ if option_name not in converted_options and option_default is not None:
150
+ converted_options[option_name] = option_default
151
+
152
+ # Validate flags
153
+ flag_names = {flag[0] for flag in self.flags}
154
+ for flag_name in arguments.flags:
155
+ if flag_name not in flag_names:
156
+ raise ValueError(f"Unknown flag: --{flag_name}")
157
+
158
+ # Return new Arguments with converted options
159
+ return Arguments(arguments.expressions, arguments.flags, converted_options)
160
+
161
+ def print_to(self, terminal, command_name):
162
+ """Print help text from usage specification.
163
+
164
+ Args:
165
+ terminal: Terminal for colored output
166
+ command_name: Name of the command (e.g., "rb-print")
167
+ """
168
+ import format as fmt
169
+
170
+ # Summary with color
171
+ terminal.print(fmt.bold, self.summary, fmt.reset)
172
+ terminal.print()
173
+
174
+ # Print usage line
175
+ terminal.print("Usage: ", end='')
176
+ terminal.print(fmt.bold, command_name, fmt.reset, end='')
177
+
178
+ for param_name, _ in self.parameters:
179
+ terminal.print(' ', end='')
180
+ terminal.print(fmt.placeholder, f"<{param_name}>", fmt.reset, end='')
181
+
182
+ # Add option placeholders
183
+ for option_name in self.options.keys():
184
+ terminal.print(' ', end='')
185
+ terminal.print(fmt.placeholder, f"[--{option_name} N]", fmt.reset, end='')
186
+
187
+ # Add flag placeholders
188
+ for flag_name, _ in self.flags:
189
+ terminal.print(' ', end='')
190
+ terminal.print(fmt.placeholder, f"[--{flag_name}]", fmt.reset, end='')
191
+
192
+ terminal.print()
193
+ terminal.print()
194
+
195
+ # Parameter descriptions
196
+ if self.parameters:
197
+ terminal.print(fmt.title, "Parameters:", fmt.reset)
198
+
199
+ for param_name, param_desc in self.parameters:
200
+ terminal.print(" ", fmt.symbol, param_name, fmt.reset, end='')
201
+ if param_desc:
202
+ terminal.print(f" - {param_desc}")
203
+ else:
204
+ terminal.print()
205
+ terminal.print()
206
+
207
+ # Option descriptions
208
+ if self.options:
209
+ terminal.print(fmt.title, "Options:", fmt.reset)
210
+
211
+ for option_name, opt_spec in self.options.items():
212
+ opt_type, opt_default = opt_spec[0], opt_spec[1]
213
+ opt_desc = opt_spec[2] if len(opt_spec) > 2 else None
214
+
215
+ type_str = opt_type.__name__ if hasattr(opt_type, '__name__') else str(opt_type)
216
+ default_str = f" (default: {opt_default})" if opt_default is not None else ""
217
+
218
+ terminal.print(" ", fmt.symbol, f"--{option_name}", fmt.reset, end='')
219
+ terminal.print(fmt.placeholder, f" <{type_str}>", fmt.reset, end='')
220
+ terminal.print(default_str)
221
+
222
+ if opt_desc:
223
+ terminal.print(f" {opt_desc}")
224
+ terminal.print()
225
+
226
+ # Flag descriptions
227
+ if self.flags:
228
+ terminal.print(fmt.title, "Flags:", fmt.reset)
229
+
230
+ for flag_name, flag_desc in self.flags:
231
+ terminal.print(" ", fmt.symbol, f"--{flag_name}", fmt.reset, end='')
232
+ if flag_desc:
233
+ terminal.print(f" - {flag_desc}")
234
+ else:
235
+ terminal.print()
236
+ terminal.print()
237
+
238
+ # Examples section
239
+ if self.examples:
240
+ terminal.print(fmt.title, "Examples:", fmt.reset)
241
+
242
+ for example_cmd, example_desc in self.examples:
243
+ terminal.print(fmt.example, f" {example_cmd}", fmt.reset)
244
+ if example_desc:
245
+ terminal.print(f" {example_desc}")
246
+
22
247
  class ArgumentParser:
23
248
  """Parse GDB command arguments handling nested brackets, quotes, and flags.
24
249