ruby_jard 0.1.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.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/ISSUE_TEMPLATE/bug_report.md +32 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  5. data/.github/workflows/documentation.yml +65 -0
  6. data/.github/workflows/rspec.yml +96 -0
  7. data/.gitignore +1 -0
  8. data/.rubocop.yml +90 -2
  9. data/CHANGELOG.md +112 -0
  10. data/Gemfile +14 -4
  11. data/README.md +95 -3
  12. data/benchmark/path_filter_bench.rb +58 -0
  13. data/bin/console +1 -2
  14. data/lib/ruby_jard.rb +68 -32
  15. data/lib/ruby_jard/box_drawer.rb +175 -0
  16. data/lib/ruby_jard/color_scheme.rb +28 -0
  17. data/lib/ruby_jard/color_schemes.rb +54 -0
  18. data/lib/ruby_jard/color_schemes/256_color_scheme.rb +50 -0
  19. data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +50 -0
  20. data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +49 -0
  21. data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +48 -0
  22. data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +47 -0
  23. data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +49 -0
  24. data/lib/ruby_jard/column.rb +26 -0
  25. data/lib/ruby_jard/commands/color_helpers.rb +32 -0
  26. data/lib/ruby_jard/commands/continue_command.rb +4 -9
  27. data/lib/ruby_jard/commands/down_command.rb +9 -8
  28. data/lib/ruby_jard/commands/exit_command.rb +27 -0
  29. data/lib/ruby_jard/commands/frame_command.rb +13 -11
  30. data/lib/ruby_jard/commands/jard/color_scheme_command.rb +74 -0
  31. data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
  32. data/lib/ruby_jard/commands/jard/hide_command.rb +40 -0
  33. data/lib/ruby_jard/commands/jard/output_command.rb +36 -0
  34. data/lib/ruby_jard/commands/jard/show_command.rb +41 -0
  35. data/lib/ruby_jard/commands/jard_command.rb +52 -0
  36. data/lib/ruby_jard/commands/list_command.rb +31 -0
  37. data/lib/ruby_jard/commands/next_command.rb +11 -8
  38. data/lib/ruby_jard/commands/step_command.rb +11 -8
  39. data/lib/ruby_jard/commands/step_out_command.rb +34 -0
  40. data/lib/ruby_jard/commands/up_command.rb +10 -8
  41. data/lib/ruby_jard/commands/validation_helpers.rb +50 -0
  42. data/lib/ruby_jard/config.rb +61 -0
  43. data/lib/ruby_jard/console.rb +158 -0
  44. data/lib/ruby_jard/control_flow.rb +73 -0
  45. data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
  46. data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
  47. data/lib/ruby_jard/decorators/color_decorator.rb +80 -0
  48. data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
  49. data/lib/ruby_jard/decorators/inspection_decorator.rb +109 -0
  50. data/lib/ruby_jard/decorators/loc_decorator.rb +108 -119
  51. data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
  52. data/lib/ruby_jard/decorators/path_decorator.rb +56 -60
  53. data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
  54. data/lib/ruby_jard/decorators/source_decorator.rb +3 -1
  55. data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
  56. data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
  57. data/lib/ruby_jard/frame.rb +68 -0
  58. data/lib/ruby_jard/key_binding.rb +14 -0
  59. data/lib/ruby_jard/key_bindings.rb +96 -0
  60. data/lib/ruby_jard/keys.rb +48 -0
  61. data/lib/ruby_jard/layout.rb +17 -88
  62. data/lib/ruby_jard/layout_calculator.rb +168 -0
  63. data/lib/ruby_jard/layout_picker.rb +34 -0
  64. data/lib/ruby_jard/layouts.rb +52 -0
  65. data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +32 -0
  66. data/lib/ruby_jard/layouts/narrow_vertical_layout.rb +32 -0
  67. data/lib/ruby_jard/layouts/tiny_layout.rb +29 -0
  68. data/lib/ruby_jard/layouts/wide_layout.rb +50 -0
  69. data/lib/ruby_jard/pager.rb +112 -0
  70. data/lib/ruby_jard/path_classifier.rb +133 -0
  71. data/lib/ruby_jard/path_filter.rb +125 -0
  72. data/lib/ruby_jard/reflection.rb +97 -0
  73. data/lib/ruby_jard/repl_processor.rb +151 -89
  74. data/lib/ruby_jard/repl_proxy.rb +337 -0
  75. data/lib/ruby_jard/row.rb +31 -0
  76. data/lib/ruby_jard/row_renderer.rb +119 -0
  77. data/lib/ruby_jard/screen.rb +14 -41
  78. data/lib/ruby_jard/screen_adjuster.rb +104 -0
  79. data/lib/ruby_jard/screen_drawer.rb +25 -0
  80. data/lib/ruby_jard/screen_manager.rb +167 -82
  81. data/lib/ruby_jard/screen_renderer.rb +152 -0
  82. data/lib/ruby_jard/screens.rb +31 -12
  83. data/lib/ruby_jard/screens/backtrace_screen.rb +118 -116
  84. data/lib/ruby_jard/screens/menu_screen.rb +73 -45
  85. data/lib/ruby_jard/screens/source_screen.rb +86 -106
  86. data/lib/ruby_jard/screens/threads_screen.rb +103 -78
  87. data/lib/ruby_jard/screens/variables_screen.rb +224 -142
  88. data/lib/ruby_jard/session.rb +151 -16
  89. data/lib/ruby_jard/span.rb +23 -0
  90. data/lib/ruby_jard/templates/layout_template.rb +35 -0
  91. data/lib/ruby_jard/templates/screen_template.rb +34 -0
  92. data/lib/ruby_jard/thread_info.rb +69 -0
  93. data/lib/ruby_jard/version.rb +1 -1
  94. data/ruby_jard.gemspec +7 -8
  95. metadata +84 -50
  96. data/.travis.yml +0 -6
  97. data/lib/ruby_jard/commands/finish_command.rb +0 -31
  98. data/lib/ruby_jard/decorators/text_decorator.rb +0 -61
  99. data/lib/ruby_jard/layout_template.rb +0 -101
  100. data/lib/ruby_jard/screens/breakpoints_screen.rb +0 -23
  101. data/lib/ruby_jard/screens/empty_screen.rb +0 -13
  102. data/lib/ruby_jard/screens/expressions_sreen.rb +0 -22
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Pick layout smartly depending on current window height and width
6
+ class LayoutPicker
7
+ def initialize(width, height, layouts: RubyJard::Layouts, config: RubyJard.config)
8
+ @width = width
9
+ @height = height
10
+ @layouts = layouts
11
+ @config = config
12
+ end
13
+
14
+ def pick
15
+ unless @config.layout.nil?
16
+ return @layouts[@config.layout] || @layouts.fallback_layout
17
+ end
18
+
19
+ @layouts.each do |_name, template|
20
+ matched = true
21
+ matched &&= (
22
+ template.min_width.nil? ||
23
+ @width > template.min_width
24
+ )
25
+ matched &&= (
26
+ template.min_height.nil? ||
27
+ @height > template.min_height
28
+ )
29
+ return template if matched
30
+ end
31
+ @layouts.fallback_layout
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ruby_jard/layout'
4
+ require 'ruby_jard/templates/layout_template'
5
+ require 'ruby_jard/templates/screen_template'
6
+ require 'ruby_jard/layout_picker'
7
+ require 'ruby_jard/layout_calculator'
8
+
9
+ module RubyJard
10
+ ##
11
+ # Layouts registry.
12
+ class Layouts
13
+ class << self
14
+ extend Forwardable
15
+ def_delegators :instance, :fallback_layout, :add_layout, :[], :get, :each
16
+
17
+ def instance
18
+ @instance ||= new
19
+ end
20
+ end
21
+
22
+ attr_reader :fallback_layout
23
+
24
+ def initialize(fallback_layout = RubyJard::Layouts::WideLayout)
25
+ @layout_registry = {}
26
+ @fallback_layout = fallback_layout
27
+ end
28
+
29
+ def add_layout(name, layout_class)
30
+ unless layout_class.is_a?(RubyJard::Templates::LayoutTemplate)
31
+ raise RubyJard::Error, "#{layout_class} must be a #{RubyJard::Templates::LayoutTemplate}"
32
+ end
33
+
34
+ @layout_registry[name] = layout_class
35
+ end
36
+
37
+ def [](name)
38
+ @layout_registry[name.to_s.strip]
39
+ end
40
+ alias_method :get, :[]
41
+
42
+ def each(&block)
43
+ @layout_registry.each(&block)
44
+ end
45
+ end
46
+ end
47
+
48
+ # Registering order is also priority when picking layout
49
+ require 'ruby_jard/layouts/wide_layout'
50
+ require 'ruby_jard/layouts/narrow_vertical_layout'
51
+ require 'ruby_jard/layouts/narrow_horizontal_layout'
52
+ require 'ruby_jard/layouts/tiny_layout'
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ class Layouts
5
+ NarrowHorizontalLayout = RubyJard::Templates::LayoutTemplate.new(
6
+ min_width: 80,
7
+ min_height: 10,
8
+ fill_height: false,
9
+ children: [
10
+ RubyJard::Templates::LayoutTemplate.new(
11
+ height_ratio: 80,
12
+ width_ratio: 100,
13
+ children: [
14
+ RubyJard::Templates::ScreenTemplate.new(
15
+ screen: :source,
16
+ width_ratio: 60
17
+ ),
18
+ RubyJard::Templates::ScreenTemplate.new(
19
+ screen: :variables,
20
+ width_ratio: 40
21
+ )
22
+ ]
23
+ ),
24
+ RubyJard::Templates::ScreenTemplate.new(
25
+ height: 2,
26
+ screen: :menu
27
+ )
28
+ ]
29
+ )
30
+ end
31
+ end
32
+ RubyJard::Layouts.add_layout('narrow-horizontal', RubyJard::Layouts::NarrowHorizontalLayout)
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ class Layouts
5
+ NarrowVerticalLayout = RubyJard::Templates::LayoutTemplate.new(
6
+ min_width: 40,
7
+ min_height: 24,
8
+ fill_height: false,
9
+ children: [
10
+ RubyJard::Templates::LayoutTemplate.new(
11
+ height_ratio: 80,
12
+ width_ratio: 100,
13
+ children: [
14
+ RubyJard::Templates::ScreenTemplate.new(
15
+ screen: :source,
16
+ height_ratio: 60
17
+ ),
18
+ RubyJard::Templates::ScreenTemplate.new(
19
+ screen: :variables,
20
+ height_ratio: 40
21
+ )
22
+ ]
23
+ ),
24
+ RubyJard::Templates::ScreenTemplate.new(
25
+ height: 2,
26
+ screen: :menu
27
+ )
28
+ ]
29
+ )
30
+ end
31
+ end
32
+ RubyJard::Layouts.add_layout('narrow-vertical', RubyJard::Layouts::NarrowVerticalLayout)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ class Layouts
5
+ TinyLayout = RubyJard::Templates::LayoutTemplate.new(
6
+ min_height: 10,
7
+ fill_height: false,
8
+ children: [
9
+ RubyJard::Templates::LayoutTemplate.new(
10
+ height_ratio: 80,
11
+ width_ratio: 100,
12
+ min_height: 7,
13
+ fill_height: true,
14
+ children: [
15
+ RubyJard::Templates::ScreenTemplate.new(
16
+ screen: :source,
17
+ height_ratio: 100
18
+ )
19
+ ]
20
+ ),
21
+ RubyJard::Templates::ScreenTemplate.new(
22
+ height: 2,
23
+ screen: :menu
24
+ )
25
+ ]
26
+ )
27
+ end
28
+ end
29
+ RubyJard::Layouts.add_layout('tiny', RubyJard::Layouts::TinyLayout)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ class Layouts
5
+ WideLayout = RubyJard::Templates::LayoutTemplate.new(
6
+ min_width: 120,
7
+ min_height: 24,
8
+ fill_height: false,
9
+ children: [
10
+ RubyJard::Templates::LayoutTemplate.new(
11
+ height_ratio: 80,
12
+ width_ratio: 50,
13
+ children: [
14
+ RubyJard::Templates::ScreenTemplate.new(
15
+ screen: :source,
16
+ height_ratio: 70,
17
+ adjust_mode: :expand
18
+ ),
19
+ RubyJard::Templates::ScreenTemplate.new(
20
+ screen: :backtrace,
21
+ height_ratio: 30,
22
+ min_height: 3
23
+ )
24
+ ]
25
+ ),
26
+ RubyJard::Templates::LayoutTemplate.new(
27
+ height_ratio: 80,
28
+ width_ratio: 50,
29
+ children: [
30
+ RubyJard::Templates::ScreenTemplate.new(
31
+ screen: :variables,
32
+ height_ratio: 80,
33
+ adjust_mode: :expand
34
+ ),
35
+ RubyJard::Templates::ScreenTemplate.new(
36
+ screen: :threads,
37
+ height_ratio: 20,
38
+ min_height: 3
39
+ )
40
+ ]
41
+ ),
42
+ RubyJard::Templates::ScreenTemplate.new(
43
+ height: 2,
44
+ screen: :menu
45
+ )
46
+ ]
47
+ )
48
+ end
49
+ end
50
+ RubyJard::Layouts.add_layout('wide', RubyJard::Layouts::WideLayout)
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ ##
5
+ # Override Pry's pager system. Again, Pry doesn't support customizing pager. So...
6
+ class Pager
7
+ def initialize(pry_instance)
8
+ @pry_instance = pry_instance
9
+ end
10
+
11
+ def page(text)
12
+ open do |pager|
13
+ pager << text
14
+ end
15
+ end
16
+
17
+ def open(options = {})
18
+ pager = LessPager.new(@pry_instance, **options)
19
+ yield pager
20
+ rescue Pry::Pager::StopPaging
21
+ # Ignore
22
+ ensure
23
+ pager.close
24
+ end
25
+
26
+ private
27
+
28
+ def enabled?
29
+ !!@enabled
30
+ end
31
+
32
+ ##
33
+ # Pager tracker in Pry does not expose enough
34
+ class JardPageTracker < Pry::Pager::PageTracker
35
+ attr_reader :row, :col
36
+ end
37
+
38
+ ##
39
+ # Pager using GNU Less
40
+ class LessPager < Pry::Pager::NullPager
41
+ def initialize(pry_instance, force_open: false, pager_start_at_the_end: false, prompt: nil)
42
+ super(pry_instance.output)
43
+ @pry_instance = pry_instance
44
+ @buffer = ''
45
+
46
+ @pager_start_at_the_end = pager_start_at_the_end
47
+ @prompt = prompt
48
+
49
+ @window_width, @window_height = RubyJard::Console.screen_size(@pry_instance.output)
50
+ @tracker = JardPageTracker.new(@window_height, @window_width)
51
+ @pager = force_open ? open_pager : nil
52
+ end
53
+
54
+ def write(str)
55
+ if invoked_pager?
56
+ write_into_pager str
57
+ else
58
+ @tracker.record str
59
+ @buffer += str
60
+ if @tracker.page?
61
+ @pager = open_pager
62
+ write_into_pager(@buffer)
63
+ end
64
+ end
65
+ rescue Errno::EPIPE
66
+ raise Pry::Pager::StopPaging
67
+ end
68
+
69
+ def close
70
+ if invoked_pager?
71
+ @pager.close
72
+ @pry_instance.exec_hook :after_pager, self
73
+
74
+ list_prompt
75
+ elsif @tracker.row > @window_height / 2
76
+ @out.write @buffer
77
+
78
+ list_prompt
79
+ else
80
+ @out.write @buffer
81
+ end
82
+ end
83
+
84
+ def invoked_pager?
85
+ @pager
86
+ end
87
+
88
+ def open_pager
89
+ @pry_instance.exec_hook :before_pager, self
90
+ less_command = ['less', '-R', '-X']
91
+ less_command << "--prompt \"#{@prompt}\"" if @prompt
92
+ less_command << '+G' if @pager_start_at_the_end
93
+
94
+ IO.popen(
95
+ less_command.join(' '), 'w',
96
+ out: @pry_instance.output, err: @pry_instance.output
97
+ )
98
+ end
99
+
100
+ def write_into_pager(str)
101
+ return unless invoked_pager?
102
+
103
+ @pager.write str.encode('UTF-8', undef: :replace)
104
+ end
105
+
106
+ def list_prompt
107
+ prompt = @pry_instance.prompt.wait_proc.call
108
+ @out.puts "#{prompt}Tips: You can use `list` command to show back debugger screens"
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'rbconfig'
5
+
6
+ module RubyJard
7
+ ##
8
+ # Classify a particular path by its orign such as stdlib, gem, evaluation, etc.
9
+ # Usage
10
+ # type, *info = PathClassifier.new.class('lib/abc')
11
+ class PathClassifier
12
+ GEM_PATTERN = /(.*)-(\d+\.\d+[.\d]*[.\d]*[-.\w]*)/i.freeze
13
+ STDLIB_PATTERN = /(.*)\.rb$/.freeze
14
+ INTERNAL_PATTERN = /<internal:[^>]+>/.freeze
15
+ EVALUATION_SIGNATURE = '(eval)'
16
+ RUBY_SCRIPT_SIGNATURE = '-e'
17
+
18
+ TYPES = [
19
+ TYPE_SOURCE_TREE = :source_tree,
20
+ TYPE_GEM = :gem,
21
+ TYPE_STDLIB = :stdlib,
22
+ TYPE_INTERNAL = :internal,
23
+ TYPE_EVALUATION = :evaluation,
24
+ TYPE_RUBY_SCRIPT = :ruby_script,
25
+ TYPE_UNKNOWN = :unknown
26
+ ].freeze
27
+
28
+ def initialize
29
+ @gem_paths = fetch_gem_paths
30
+ end
31
+
32
+ def classify(path)
33
+ return TYPE_UNKNOWN if path.nil?
34
+
35
+ return TYPE_INTERNAL if try_classify_internal(path)
36
+
37
+ return TYPE_EVALUATION if try_classify_evaluation(path)
38
+
39
+ return TYPE_RUBY_SCRIPT if try_classify_ruby_script(path)
40
+
41
+ return TYPE_SOURCE_TREE if try_classify_source_tree(path)
42
+
43
+ matched, *info = try_classify_gem(path)
44
+ return TYPE_GEM, *info if matched
45
+
46
+ matched, *info = try_classify_stdlib(path)
47
+ return TYPE_STDLIB, *info if matched
48
+
49
+ TYPE_UNKNOWN
50
+ end
51
+
52
+ private
53
+
54
+ def try_classify_gem(path)
55
+ @gem_paths.each do |gem_path|
56
+ next unless path.start_with?(gem_path)
57
+
58
+ splitted_path =
59
+ path[gem_path.length..-1]
60
+ .split('/')
61
+ .reject(&:empty?)
62
+ gem_name = splitted_path.shift
63
+ gem_version = nil
64
+
65
+ match = GEM_PATTERN.match(gem_name)
66
+ if match
67
+ gem_name = match[1]
68
+ gem_version = match[2]
69
+ end
70
+
71
+ return true, gem_name, gem_version, splitted_path.join('/')
72
+ end
73
+
74
+ false
75
+ end
76
+
77
+ def try_classify_internal(path)
78
+ # https://github.com/ruby/ruby/blob/master/template/prelude.c.tmpl#L18
79
+ path =~ INTERNAL_PATTERN
80
+ end
81
+
82
+ def try_classify_stdlib(path)
83
+ lib_dir = RbConfig::CONFIG['rubylibdir'].to_s.strip
84
+
85
+ return false if lib_dir.empty?
86
+ return false unless path.start_with?(lib_dir)
87
+
88
+ splitted_path =
89
+ path[lib_dir.length..-1]
90
+ .split('/')
91
+ .reject(&:empty?)
92
+ lib_name = splitted_path.first
93
+ match = STDLIB_PATTERN.match(lib_name)
94
+ lib_name = match[1] if match
95
+
96
+ [true, lib_name, splitted_path.join('/')]
97
+ rescue NameError
98
+ # RbConfig is not available
99
+ false
100
+ end
101
+
102
+ def try_classify_evaluation(path)
103
+ path == EVALUATION_SIGNATURE
104
+ end
105
+
106
+ def try_classify_ruby_script(path)
107
+ path == RUBY_SCRIPT_SIGNATURE
108
+ end
109
+
110
+ def try_classify_source_tree(path)
111
+ path.start_with?(Dir.pwd)
112
+ end
113
+
114
+ def fetch_gem_paths
115
+ paths = []
116
+
117
+ if defined?(Gem)
118
+ Gem.path.each do |gem_path|
119
+ paths << File.join(gem_path, 'gems')
120
+ paths << gem_path
121
+ end
122
+ end
123
+
124
+ if defined?(Bundler)
125
+ bundle_path = Bundler.bundle_path.to_s
126
+ paths << File.join(bundle_path, 'gems')
127
+ paths << bundle_path
128
+ end
129
+
130
+ paths
131
+ end
132
+ end
133
+ end