ruby_jard 0.1.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +13 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +0 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +12 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +16 -0
  11. data/Rakefile +8 -0
  12. data/bin/console +15 -0
  13. data/lib/ruby_jard.rb +77 -0
  14. data/lib/ruby_jard/commands/continue_command.rb +33 -0
  15. data/lib/ruby_jard/commands/down_command.rb +31 -0
  16. data/lib/ruby_jard/commands/finish_command.rb +31 -0
  17. data/lib/ruby_jard/commands/frame_command.rb +36 -0
  18. data/lib/ruby_jard/commands/next_command.rb +31 -0
  19. data/lib/ruby_jard/commands/step_command.rb +31 -0
  20. data/lib/ruby_jard/commands/up_command.rb +31 -0
  21. data/lib/ruby_jard/decorators/loc_decorator.rb +200 -0
  22. data/lib/ruby_jard/decorators/path_decorator.rb +88 -0
  23. data/lib/ruby_jard/decorators/source_decorator.rb +43 -0
  24. data/lib/ruby_jard/decorators/text_decorator.rb +61 -0
  25. data/lib/ruby_jard/layout.rb +99 -0
  26. data/lib/ruby_jard/layout_template.rb +101 -0
  27. data/lib/ruby_jard/repl_processor.rb +143 -0
  28. data/lib/ruby_jard/screen.rb +61 -0
  29. data/lib/ruby_jard/screen_manager.rb +121 -0
  30. data/lib/ruby_jard/screens.rb +26 -0
  31. data/lib/ruby_jard/screens/backtrace_screen.rb +150 -0
  32. data/lib/ruby_jard/screens/breakpoints_screen.rb +23 -0
  33. data/lib/ruby_jard/screens/empty_screen.rb +13 -0
  34. data/lib/ruby_jard/screens/expressions_sreen.rb +22 -0
  35. data/lib/ruby_jard/screens/menu_screen.rb +62 -0
  36. data/lib/ruby_jard/screens/source_screen.rb +133 -0
  37. data/lib/ruby_jard/screens/threads_screen.rb +116 -0
  38. data/lib/ruby_jard/screens/variables_screen.rb +234 -0
  39. data/lib/ruby_jard/session.rb +54 -0
  40. data/lib/ruby_jard/version.rb +6 -0
  41. data/ruby_jard.gemspec +39 -0
  42. metadata +160 -0
@@ -0,0 +1,234 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Screens
5
+ class VariablesScreen < RubyJard::Screen
6
+ TYPE_SYMBOLS = {
7
+ # Intertal classes for those values may differ between Ruby versions
8
+ # For example: Bignum is renamed to Integer
9
+ # So, it's safer to use discrete value's class as the key for this mapping.
10
+ true.class => :bool,
11
+ false.class => :bool,
12
+ 1.class => :int,
13
+ 1.1.class => :flt,
14
+ 1.to_r.class => :rat, # Rational: (1/1)
15
+ 1.to_c.class => :com, # Complex: (1+0i)
16
+ ''.class => :str,
17
+ :sym.class => :sym,
18
+ [].class => :arr,
19
+ {}.class => :hash,
20
+ //.class => :reg,
21
+ Class => :cls # Sorry, I lied, Class will never change
22
+ }.freeze
23
+ TYPE_SYMBOL_PADDING = TYPE_SYMBOLS.map { |_, s| s.to_s.length }.max + 1
24
+ DEFAULT_TYPE_SYMBOL = :var
25
+
26
+ INSPECTION_ELLIPSIS = ' [...]'
27
+
28
+ KINDS = [
29
+ KIND_LOC = :loc,
30
+ KIND_INS = :ins,
31
+ KIND_CON = :con
32
+ ].freeze
33
+
34
+ KIND_PRIORITIES = {
35
+ KIND_LOC => 1,
36
+ KIND_INS => 2,
37
+ KIND_CON => 3
38
+ }.freeze
39
+
40
+ KIND_COLORS = {
41
+ KIND_LOC => :yellow,
42
+ KIND_INS => :blue,
43
+ KIND_CON => :green
44
+ }.freeze
45
+
46
+ def draw
47
+ @output.print TTY::Box.frame(
48
+ **default_frame_styles.merge(
49
+ top: @row, left: @col, width: @layout.width, height: @layout.height
50
+ )
51
+ )
52
+
53
+ @output.print TTY::Cursor.move_to(@col + 1, @row)
54
+ @output.print decorate_text
55
+ .with_highlight(true)
56
+ .text('Variables ', :bright_yellow)
57
+ .content
58
+
59
+ decorated_variables.first(data_size).each_with_index do |variable, index|
60
+ @output.print TTY::Cursor.move_to(@col + 1, @row + index + 1)
61
+ @output.print variable.content
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def data_size
68
+ @layout.height - 1
69
+ end
70
+
71
+ def current_binding
72
+ RubyJard.current_session.frame._binding
73
+ end
74
+
75
+ def current_frame
76
+ RubyJard.current_session.frame
77
+ end
78
+
79
+ def current_frame_scope
80
+ RubyJard.current_session.backtrace[RubyJard.current_session.frame.pos][1]
81
+ end
82
+
83
+ def current_frame_scope_class
84
+ RubyJard.current_session.backtrace[RubyJard.current_session.frame.pos][2]
85
+ end
86
+
87
+ def decorated_variables
88
+ return [] if current_frame.nil?
89
+
90
+ variables = fetch_local_variables + fetch_instance_variables + fetch_constants
91
+
92
+ sort_variables(variables).map do |kind, name, value|
93
+ decorated_variable(kind, name, value)
94
+ end.flatten
95
+ end
96
+
97
+ def fetch_local_variables
98
+ variables = current_binding.local_variables
99
+ # Exclude Pry's sticky locals
100
+ pry_sticky_locals =
101
+ if variables.include?(:pry_instance)
102
+ current_binding.local_variable_get(:pry_instance).sticky_locals.keys
103
+ else
104
+ []
105
+ end
106
+ variables -= pry_sticky_locals
107
+ variables.map do |variable|
108
+ [KIND_LOC, variable, current_binding.local_variable_get(variable)]
109
+ rescue NameError
110
+ nil
111
+ end.compact
112
+ end
113
+
114
+ def fetch_instance_variables
115
+ current_frame_scope.instance_variables.map do |variable|
116
+ [KIND_INS, variable, current_frame_scope.instance_variable_get(variable)]
117
+ rescue NameError
118
+ nil
119
+ end.compact
120
+ end
121
+
122
+ def fetch_constants
123
+ # Filter out truly constants (CONSTANT convention) only
124
+ constant_source =
125
+ if current_frame_scope_class&.singleton_class?
126
+ current_frame_scope
127
+ else
128
+ current_frame_scope_class
129
+ end
130
+
131
+ return [] unless constant_source.respond_to?(:constants)
132
+
133
+ constants = constant_source.constants.select { |v| v.to_s.upcase == v.to_s }
134
+ constants.map do |variable|
135
+ [KIND_CON, variable, constant_source.const_get(variable)]
136
+ rescue NameError
137
+ nil
138
+ end.compact
139
+ end
140
+
141
+ def decorated_variable(kind, name, value)
142
+ text =
143
+ decorate_text
144
+ .text(decorated_type(value))
145
+ .with_highlight(true)
146
+ .text(name.to_s, kind_color(kind))
147
+ .text(addition_data(value), :white)
148
+ .text(' = ')
149
+ .with_highlight(false)
150
+ inspect_texts = inspect_value(text, value)
151
+ text.text(inspect_texts.first, :bright_white)
152
+
153
+ # TODO: Fix this ugly code
154
+ [text] +
155
+ inspect_texts[1..-1].map do |line|
156
+ decorate_text
157
+ .with_highlight(false)
158
+ .text(' ' * TYPE_SYMBOL_PADDING)
159
+ .text(line, :bright_white)
160
+ end
161
+ end
162
+
163
+ def addition_data(value)
164
+ if value.is_a?(Array) && !value.empty?
165
+ " (size: #{value.length})"
166
+ elsif value.is_a?(String) && value.length > 20
167
+ " (size: #{value.length})"
168
+ else
169
+ ''
170
+ end
171
+ end
172
+
173
+ def inspect_value(text, value)
174
+ # Split the lines, add padding to align with kind
175
+ length = @layout.width - TYPE_SYMBOL_PADDING - 1
176
+ value_inspect = value.inspect.to_s
177
+
178
+ start_pos = 0
179
+ end_pos = @layout.width - 2 - text.length
180
+
181
+ texts = []
182
+ 3.times do |_index|
183
+ texts << value_inspect[start_pos..end_pos]
184
+ break if end_pos >= value_inspect.length
185
+
186
+ start_pos = end_pos + 1
187
+ end_pos += length
188
+ end
189
+
190
+ if end_pos < value_inspect.length
191
+ texts.last[texts.last.length - INSPECTION_ELLIPSIS.length - 1..texts.last.length - 1] = INSPECTION_ELLIPSIS
192
+ end
193
+
194
+ texts
195
+ end
196
+
197
+ def decorated_type(value)
198
+ type_name = TYPE_SYMBOLS[value.class] || DEFAULT_TYPE_SYMBOL
199
+ decorate_text
200
+ .with_highlight(false)
201
+ .text(type_name.to_s.ljust(TYPE_SYMBOL_PADDING), :white)
202
+ end
203
+
204
+ def kind_color(kind)
205
+ KIND_COLORS[kind] || :white
206
+ end
207
+
208
+ def sort_variables(variables)
209
+ # Sort by kind
210
+ # Sort by "internal" character so that internal variable is pushed down
211
+ # Sort by name
212
+ variables.sort do |a, b|
213
+ if KIND_PRIORITIES[a[0]] != KIND_PRIORITIES[b[0]]
214
+ KIND_PRIORITIES[a[0]] <=> KIND_PRIORITIES[b[0]]
215
+ else
216
+ a_name = a[1].to_s.gsub(/^@/, '')
217
+ b_name = b[1].to_s.gsub(/^@/, '')
218
+ if a_name[0] == '_' && b_name[0] == '_'
219
+ a_name.to_s <=> b_name.to_s
220
+ elsif a_name[0] == '_'
221
+ 1
222
+ elsif b_name[0] == '_'
223
+ -1
224
+ else
225
+ a_name.to_s <=> b_name.to_s
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ RubyJard::Screens.add_screen(:variables, RubyJard::Screens::VariablesScreen)
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Centralized flow control and data storage to feed into screens. Each
6
+ # process supposes to have only one instance of this class.
7
+ # TODO: If attachment event happens after any threads are created, race
8
+ # condition may happen. Should use a mutex to wrap around.
9
+ # TODO: This class is created to store data, but byebug data structures are
10
+ # leaked, and accessible from outside and this doesn't work if screens stay in
11
+ # other processes. Therefore, an internal, jard-specific data mapping should
12
+ # be built.
13
+ class Session
14
+ attr_reader :screen_manager, :backtrace, :frame, :contexts, :current_context
15
+
16
+ def initialize(options = {})
17
+ @options = options
18
+
19
+ @backtrace = []
20
+ @frame = nil
21
+ @contexts = []
22
+
23
+ @started = false
24
+ @screen_manager = RubyJard::ScreenManager.new(session: self)
25
+ end
26
+
27
+ def start
28
+ return if started?
29
+
30
+ @screen_manager.start
31
+
32
+ @started = true
33
+ end
34
+
35
+ def started?
36
+ @started == true
37
+ end
38
+
39
+ def attach
40
+ start unless started?
41
+
42
+ Byebug.attach
43
+ Byebug.current_context.step_out(2, false)
44
+ end
45
+
46
+ def refresh
47
+ @backtrace = Byebug.current_context.backtrace
48
+ @frame = Byebug.current_context.frame
49
+ @contexts = Byebug.contexts
50
+ @current_context = Byebug.current_context
51
+ @screen_manager.refresh
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Semantic versionn
4
+ module RubyJard
5
+ VERSION = '0.1.0'
6
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/ruby_jard/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ruby_jard'
7
+ spec.version = RubyJard::VERSION
8
+ spec.authors = ['Minh Nguyen']
9
+ spec.email = ['nguyenquangminh0711@gmail.com']
10
+
11
+ spec.summary = 'Just Another Ruby Debugger'
12
+ spec.description = 'Ruby Jard provides an unified experience debugging Ruby source code in different platforms and \
13
+ editors.'
14
+ spec.homepage = 'https://github.com/nguyenquangminh0711/ruby_jard'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.5.0')
17
+
18
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
19
+
20
+ spec.metadata['homepage_uri'] = spec.homepage
21
+ spec.metadata['source_code_uri'] = 'https://github.com/nguyenquangminh0711/ruby_jard'
22
+ spec.metadata['changelog_uri'] = 'https://github.com/nguyenquangminh0711/ruby_jard'
23
+
24
+ # Specify which files should be added to the gem when it is released.
25
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
26
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
27
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
28
+ end
29
+ spec.bindir = 'bin'
30
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
31
+ spec.require_paths = ['lib']
32
+
33
+ spec.add_runtime_dependency 'tty-box', '>= 0.5.0'
34
+ spec.add_runtime_dependency 'tty-cursor', '>= 0.7.1'
35
+ spec.add_runtime_dependency 'tty-screen', '>= 0.8.0'
36
+
37
+ spec.add_runtime_dependency 'byebug', '>= 11.1.0'
38
+ spec.add_runtime_dependency 'pry', '>= 0.13.0'
39
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_jard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Minh Nguyen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-07-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tty-box
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.5.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.5.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: tty-cursor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.7.1
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.7.1
41
+ - !ruby/object:Gem::Dependency
42
+ name: tty-screen
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: 0.8.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: 0.8.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: byebug
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 11.1.0
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 11.1.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 0.13.0
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.13.0
83
+ description: |-
84
+ Ruby Jard provides an unified experience debugging Ruby source code in different platforms and \
85
+ editors.
86
+ email:
87
+ - nguyenquangminh0711@gmail.com
88
+ executables:
89
+ - console
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - ".gitignore"
94
+ - ".rspec"
95
+ - ".rubocop.yml"
96
+ - ".travis.yml"
97
+ - CHANGELOG.md
98
+ - CODE_OF_CONDUCT.md
99
+ - Gemfile
100
+ - LICENSE.txt
101
+ - README.md
102
+ - Rakefile
103
+ - bin/console
104
+ - lib/ruby_jard.rb
105
+ - lib/ruby_jard/commands/continue_command.rb
106
+ - lib/ruby_jard/commands/down_command.rb
107
+ - lib/ruby_jard/commands/finish_command.rb
108
+ - lib/ruby_jard/commands/frame_command.rb
109
+ - lib/ruby_jard/commands/next_command.rb
110
+ - lib/ruby_jard/commands/step_command.rb
111
+ - lib/ruby_jard/commands/up_command.rb
112
+ - lib/ruby_jard/decorators/loc_decorator.rb
113
+ - lib/ruby_jard/decorators/path_decorator.rb
114
+ - lib/ruby_jard/decorators/source_decorator.rb
115
+ - lib/ruby_jard/decorators/text_decorator.rb
116
+ - lib/ruby_jard/layout.rb
117
+ - lib/ruby_jard/layout_template.rb
118
+ - lib/ruby_jard/repl_processor.rb
119
+ - lib/ruby_jard/screen.rb
120
+ - lib/ruby_jard/screen_manager.rb
121
+ - lib/ruby_jard/screens.rb
122
+ - lib/ruby_jard/screens/backtrace_screen.rb
123
+ - lib/ruby_jard/screens/breakpoints_screen.rb
124
+ - lib/ruby_jard/screens/empty_screen.rb
125
+ - lib/ruby_jard/screens/expressions_sreen.rb
126
+ - lib/ruby_jard/screens/menu_screen.rb
127
+ - lib/ruby_jard/screens/source_screen.rb
128
+ - lib/ruby_jard/screens/threads_screen.rb
129
+ - lib/ruby_jard/screens/variables_screen.rb
130
+ - lib/ruby_jard/session.rb
131
+ - lib/ruby_jard/version.rb
132
+ - ruby_jard.gemspec
133
+ homepage: https://github.com/nguyenquangminh0711/ruby_jard
134
+ licenses:
135
+ - MIT
136
+ metadata:
137
+ allowed_push_host: https://rubygems.org
138
+ homepage_uri: https://github.com/nguyenquangminh0711/ruby_jard
139
+ source_code_uri: https://github.com/nguyenquangminh0711/ruby_jard
140
+ changelog_uri: https://github.com/nguyenquangminh0711/ruby_jard
141
+ post_install_message:
142
+ rdoc_options: []
143
+ require_paths:
144
+ - lib
145
+ required_ruby_version: !ruby/object:Gem::Requirement
146
+ requirements:
147
+ - - ">="
148
+ - !ruby/object:Gem::Version
149
+ version: 2.5.0
150
+ required_rubygems_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ requirements: []
156
+ rubygems_version: 3.1.2
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Just Another Ruby Debugger
160
+ test_files: []