ruby_jard 0.2.3 → 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 (91) hide show
  1. checksums.yaml +4 -4
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/documentation.yml +65 -0
  4. data/.github/workflows/{ruby.yml → rspec.yml} +22 -11
  5. data/.rubocop.yml +6 -0
  6. data/CHANGELOG.md +15 -3
  7. data/Gemfile +9 -2
  8. data/README.md +52 -332
  9. data/benchmark/path_filter_bench.rb +58 -0
  10. data/lib/ruby_jard.rb +15 -5
  11. data/lib/ruby_jard/color_schemes.rb +9 -1
  12. data/lib/ruby_jard/color_schemes/256_color_scheme.rb +21 -35
  13. data/lib/ruby_jard/color_schemes/256_light_color_scheme.rb +23 -35
  14. data/lib/ruby_jard/color_schemes/deep_space_color_scheme.rb +20 -34
  15. data/lib/ruby_jard/color_schemes/gruvbox_color_scheme.rb +20 -34
  16. data/lib/ruby_jard/color_schemes/one_half_dark_color_scheme.rb +20 -34
  17. data/lib/ruby_jard/color_schemes/one_half_light_color_scheme.rb +21 -34
  18. data/lib/ruby_jard/commands/color_helpers.rb +32 -0
  19. data/lib/ruby_jard/commands/frame_command.rb +2 -2
  20. data/lib/ruby_jard/commands/jard/color_scheme_command.rb +25 -3
  21. data/lib/ruby_jard/commands/jard/filter_command.rb +136 -0
  22. data/lib/ruby_jard/commands/jard/output_command.rb +13 -5
  23. data/lib/ruby_jard/commands/jard_command.rb +3 -1
  24. data/lib/ruby_jard/config.rb +9 -2
  25. data/lib/ruby_jard/decorators/array_decorator.rb +79 -0
  26. data/lib/ruby_jard/decorators/attributes_decorator.rb +172 -0
  27. data/lib/ruby_jard/decorators/color_decorator.rb +12 -5
  28. data/lib/ruby_jard/decorators/hash_decorator.rb +74 -0
  29. data/lib/ruby_jard/decorators/inspection_decorator.rb +91 -58
  30. data/lib/ruby_jard/decorators/object_decorator.rb +122 -0
  31. data/lib/ruby_jard/decorators/path_decorator.rb +55 -72
  32. data/lib/ruby_jard/decorators/rails_decorator.rb +194 -0
  33. data/lib/ruby_jard/decorators/string_decorator.rb +41 -0
  34. data/lib/ruby_jard/decorators/struct_decorator.rb +79 -0
  35. data/lib/ruby_jard/frame.rb +23 -10
  36. data/lib/ruby_jard/keys.rb +1 -0
  37. data/lib/ruby_jard/layouts/narrow_horizontal_layout.rb +4 -0
  38. data/lib/ruby_jard/layouts/tiny_layout.rb +4 -0
  39. data/lib/ruby_jard/pager.rb +21 -5
  40. data/lib/ruby_jard/path_classifier.rb +133 -0
  41. data/lib/ruby_jard/path_filter.rb +125 -0
  42. data/lib/ruby_jard/reflection.rb +97 -0
  43. data/lib/ruby_jard/repl_processor.rb +71 -38
  44. data/lib/ruby_jard/row_renderer.rb +5 -3
  45. data/lib/ruby_jard/screen.rb +2 -2
  46. data/lib/ruby_jard/screen_manager.rb +13 -36
  47. data/lib/ruby_jard/screen_renderer.rb +1 -1
  48. data/lib/ruby_jard/screens/backtrace_screen.rb +55 -39
  49. data/lib/ruby_jard/screens/menu_screen.rb +30 -30
  50. data/lib/ruby_jard/screens/source_screen.rb +46 -62
  51. data/lib/ruby_jard/screens/threads_screen.rb +59 -72
  52. data/lib/ruby_jard/screens/variables_screen.rb +168 -124
  53. data/lib/ruby_jard/session.rb +120 -16
  54. data/lib/ruby_jard/thread_info.rb +69 -0
  55. data/lib/ruby_jard/version.rb +1 -1
  56. data/ruby_jard.gemspec +3 -1
  57. metadata +20 -39
  58. data/.travis.yml +0 -6
  59. data/CNAME +0 -1
  60. data/_config.yml +0 -1
  61. data/docs/_config.yml +0 -8
  62. data/docs/color_schemes/256-light.png +0 -0
  63. data/docs/color_schemes/256.png +0 -0
  64. data/docs/color_schemes/deep-space.png +0 -0
  65. data/docs/color_schemes/gruvbox.png +0 -0
  66. data/docs/color_schemes/one-half-dark.png +0 -0
  67. data/docs/color_schemes/one-half-light.png +0 -0
  68. data/docs/demo.png +0 -0
  69. data/docs/guide-ui.png +0 -0
  70. data/docs/index.md +0 -238
  71. data/docs/logo.jpg +0 -0
  72. data/docs/screen-backtrace.png +0 -0
  73. data/docs/screen-repl.png +0 -0
  74. data/docs/screen-source.png +0 -0
  75. data/docs/screen-threads.png +0 -0
  76. data/docs/screen-variables.png +0 -0
  77. data/images/bg_hr.png +0 -0
  78. data/images/blacktocat.png +0 -0
  79. data/images/body-bg.jpg +0 -0
  80. data/images/download-button.png +0 -0
  81. data/images/github-button.png +0 -0
  82. data/images/header-bg.jpg +0 -0
  83. data/images/highlight-bg.jpg +0 -0
  84. data/images/icon_download.png +0 -0
  85. data/images/sidebar-bg.jpg +0 -0
  86. data/images/sprite_download.png +0 -0
  87. data/javascripts/main.js +0 -1
  88. data/stylesheets/github-light.css +0 -130
  89. data/stylesheets/normalize.css +0 -424
  90. data/stylesheets/print.css +0 -228
  91. data/stylesheets/stylesheet.css +0 -245
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ TYPICAL_DEPTH = 3
6
+ MAX_DEPTH = 5
7
+ DO_NOT_WASTE_LENGTH = 40
8
+ ##
9
+ # Decorate collection data structure. Support:
10
+ # - Collection of values
11
+ # - Collection of key-value pairs
12
+ # - Individual value
13
+ # - Individual pair
14
+ # This decorator should not be used directly.
15
+ class AttributesDecorator
16
+ def initialize(generic_decorator)
17
+ @generic_decorator = generic_decorator
18
+ end
19
+
20
+ def inline_pairs(enum, total:, line_limit:, process_key:, value_proc: nil, depth: 0)
21
+ return [ellipsis_span] if too_deep?(depth, line_limit)
22
+
23
+ spans = []
24
+ width = 1
25
+ item_limit = total == 0 ? 0 : [line_limit / total, pair_limit(depth)].max
26
+
27
+ enum.each do |(key, value), index|
28
+ key_inspection = inspect_key(key, item_limit, process_key: process_key, depth: depth)
29
+ key_inspection_length = key_inspection.map(&:content_length).sum
30
+
31
+ value_inspection = @generic_decorator.decorate_singleline(
32
+ value_proc.nil? ? value : value_proc.call(key),
33
+ line_limit: [item_limit - key_inspection_length, pair_limit(depth)].max, depth: depth
34
+ )
35
+ value_inspection_length = value_inspection.map(&:content_length).sum
36
+
37
+ if index > 0
38
+ spans << separator_span
39
+ width += 2
40
+ end
41
+
42
+ if width + key_inspection_length + value_inspection_length + 5 > line_limit
43
+ spans << ellipsis_span
44
+ break
45
+ end
46
+
47
+ spans += key_inspection
48
+ width += key_inspection_length
49
+
50
+ spans << arrow_span
51
+ width += 3
52
+
53
+ spans += value_inspection
54
+ width += value_inspection_length
55
+ end
56
+
57
+ spans
58
+ end
59
+
60
+ def pair(key, value, line_limit:, process_key:, depth: 0)
61
+ return [ellipsis_span] if too_deep?(depth, line_limit)
62
+
63
+ spans = []
64
+ spans << indent_span
65
+ width = indent_span.content_length
66
+
67
+ key_inspection = inspect_key(key, line_limit - width, process_key: process_key, depth: depth)
68
+ key_inspection_length = key_inspection.map(&:content_length).sum
69
+
70
+ spans += key_inspection
71
+ width += key_inspection_length
72
+
73
+ spans << arrow_span
74
+ width += 3
75
+
76
+ value_inspection = @generic_decorator.decorate_singleline(
77
+ value, line_limit: [line_limit - width, pair_limit(depth)].max, depth: depth
78
+ )
79
+
80
+ spans + value_inspection
81
+ end
82
+
83
+ def inline_values(enum, total:, line_limit:, depth: 0)
84
+ return [ellipsis_span] if too_deep?(depth, line_limit)
85
+
86
+ spans = []
87
+ width = 1
88
+ item_limit = total == 0 ? 0 : [line_limit / total, value_limit(depth)].max
89
+
90
+ enum.each do |value, index|
91
+ value_inspection = @generic_decorator.decorate_singleline(
92
+ value, line_limit: [item_limit, value_limit(depth)].max, depth: depth
93
+ )
94
+ value_inspection_length = value_inspection.map(&:content_length).sum
95
+
96
+ if index > 0
97
+ spans << separator_span
98
+ width += 2
99
+ end
100
+
101
+ if width + value_inspection_length + 2 > line_limit
102
+ spans << ellipsis_span
103
+ break
104
+ end
105
+
106
+ spans += value_inspection
107
+ width += value_inspection_length
108
+ end
109
+
110
+ spans
111
+ end
112
+
113
+ def value(value, line_limit:, depth: 0)
114
+ return [ellipsis_span] if too_deep?(depth, line_limit)
115
+
116
+ spans = []
117
+ spans << indent_span
118
+ width = indent_span.content_length
119
+
120
+ value_inspection = @generic_decorator.decorate_singleline(
121
+ value, line_limit: [line_limit - width, value_limit(depth)].max, depth: depth
122
+ )
123
+
124
+ spans + value_inspection
125
+ end
126
+
127
+ private
128
+
129
+ def inspect_key(key, item_limit, process_key:, depth: 0)
130
+ if process_key
131
+ @generic_decorator.decorate_singleline(
132
+ key, line_limit: item_limit, depth: depth
133
+ )
134
+ else
135
+ [RubyJard::Span.new(content: key.to_s, styles: :text_primary)]
136
+ end
137
+ end
138
+
139
+ def arrow_span
140
+ RubyJard::Span.new(content: '→', margin_left: 1, margin_right: 1, styles: :text_highlighted)
141
+ end
142
+
143
+ def separator_span
144
+ RubyJard::Span.new(content: ',', margin_right: 1, styles: :text_primary)
145
+ end
146
+
147
+ def ellipsis_span
148
+ RubyJard::Span.new(content: '…', styles: :text_dim)
149
+ end
150
+
151
+ def indent_span
152
+ RubyJard::Span.new(content: '▸', margin_right: 1, margin_left: 2, styles: :text_dim)
153
+ end
154
+
155
+ def too_deep?(depth, line_limit)
156
+ return true if depth > MAX_DEPTH
157
+ return false if line_limit > DO_NOT_WASTE_LENGTH
158
+
159
+ depth > TYPICAL_DEPTH
160
+ end
161
+
162
+ def pair_limit(depth)
163
+ # The deeper structure, the less meaningful the actual data is
164
+ 30 - depth * 5
165
+ end
166
+
167
+ def value_limit(_depth)
168
+ 30
169
+ end
170
+ end
171
+ end
172
+ end
@@ -23,22 +23,29 @@ module RubyJard
23
23
 
24
24
  CSI_ITALIC = "\e[3m"
25
25
  CSI_UNDERLINE = "\e[4m"
26
+ CSI_BOLD = "\e[1m"
26
27
 
27
28
  STYLES_CSI_MAP = {
28
29
  underline: CSI_UNDERLINE,
29
- italic: CSI_ITALIC
30
+ italic: CSI_ITALIC,
31
+ bold: CSI_BOLD
30
32
  }.freeze
31
33
 
32
34
  def initialize(color_scheme)
33
35
  @color_scheme = color_scheme
34
36
  end
35
37
 
36
- # TODO: rename and replace #decorate by this method
37
- def decorate(element, content)
38
- styles = @color_scheme.styles_for(element) || []
38
+ def decorate(style_names, content)
39
+ attributes = nil
40
+ if style_names.is_a?(Symbol)
41
+ styles = @color_scheme.styles_for(style_names)
42
+ else
43
+ attributes = translate_styles(style_names)
44
+ styles = @color_scheme.styles_for(style_names.shift)
45
+ end
39
46
  foreground = translate_color(styles.shift, true)
40
47
  background = translate_color(styles.shift, false)
41
- "#{foreground}#{background}#{translate_styles(styles)}#{content}#{CSI_RESET}"
48
+ "#{foreground}#{background}#{attributes}#{content}#{CSI_RESET}"
42
49
  end
43
50
 
44
51
  def translate_color(color, foreground)
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Decorate Hash data structure, supports singleline and multiline form.
7
+ class HashDecorator
8
+ def initialize(generic_decorator)
9
+ @generic_decorator = generic_decorator
10
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
11
+ end
12
+
13
+ def decorate_singleline(variable, line_limit:, depth: 0)
14
+ spans = []
15
+ spans << RubyJard::Span.new(content: '{', styles: :text_primary)
16
+ spans += @attributes_decorator.inline_pairs(
17
+ variable.each_with_index,
18
+ total: variable.length, line_limit: line_limit - 2, process_key: true, depth: depth + 1
19
+ )
20
+ spans << RubyJard::Span.new(content: '}', styles: :text_primary)
21
+ end
22
+
23
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
24
+ if variable.size > lines * 1.5
25
+ return do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
26
+ end
27
+
28
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
29
+ if singleline.map(&:content_length).sum < line_limit || variable.length <= 1
30
+ [singleline]
31
+ else
32
+ do_decorate_multiline(variable, lines: lines, line_limit: line_limit, depth: depth)
33
+ end
34
+ end
35
+
36
+ def match?(variable)
37
+ RubyJard::Reflection.call_is_a?(variable, Hash)
38
+ end
39
+
40
+ private
41
+
42
+ def do_decorate_multiline(variable, lines:, line_limit:, depth: 0)
43
+ spans = [[RubyJard::Span.new(content: '{', styles: :text_primary)]]
44
+
45
+ item_count = 0
46
+ variable.each_with_index do |(key, value), index|
47
+ spans << @attributes_decorator.pair(
48
+ key, value, line_limit: line_limit, process_key: true, depth: depth + 1
49
+ )
50
+ item_count += 1
51
+ break if index >= lines - 2
52
+ end
53
+ spans << last_line(variable.length, item_count)
54
+ end
55
+
56
+ def last_line(total, item_count)
57
+ if total > item_count
58
+ [
59
+ RubyJard::Span.new(
60
+ content: "▸ #{total - item_count} more...",
61
+ margin_left: 2, styles: :text_dim
62
+ ),
63
+ RubyJard::Span.new(
64
+ content: '}',
65
+ styles: :text_primary
66
+ )
67
+ ]
68
+ else
69
+ [RubyJard::Span.new(content: '}', styles: :text_primary)]
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -1,76 +1,109 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'ruby_jard/decorators/array_decorator'
4
+ require 'ruby_jard/decorators/string_decorator'
5
+ require 'ruby_jard/decorators/hash_decorator'
6
+ require 'ruby_jard/decorators/struct_decorator'
7
+ require 'ruby_jard/decorators/object_decorator'
8
+ require 'ruby_jard/decorators/attributes_decorator'
9
+ require 'ruby_jard/decorators/rails_decorator'
10
+
3
11
  module RubyJard
4
- # PP subclass for streaming inspect output in color.
5
- class InspectionDecorator < ::PP::SingleLine
6
- def self.inspect(obj, output = [], max_width = 79)
7
- queue = new(output, max_width)
8
- queue.guard_inspect_key { queue.pp obj }
9
- queue.flush
12
+ module Decorators
13
+ ##
14
+ # Generate beauty inspection of a particular variable.
15
+ # The inspection doesn't aim to become a better version of PP. Instead,
16
+ # it's scope is to generate an overview of a variable within a limited
17
+ # space. So, it only keeps useful information, and tries to reach the
18
+ # very shallow layers of a nested data structure.
19
+ # This class is inspired by Ruby's PP:
20
+ # https://github.com/ruby/ruby/blob/master/lib/pp.rb
21
+ class InspectionDecorator
22
+ PRIMITIVE_TYPES = {
23
+ # Intertal classes for those values may differ between Ruby versions
24
+ # For example: Bignum is renamed to Integer
25
+ # So, it's safer to use discrete value's class as the key for this mapping.
26
+ true.class.name => :literal,
27
+ false.class.name => :literal,
28
+ 1.class.name => :literal,
29
+ 1.1.class.name => :literal,
30
+ 1.to_r.class.name => :literal, # Rational: (1/1)
31
+ 1.to_c.class.name => :literal, # Complex: (1+0i)
32
+ :sym.class.name => :literal,
33
+ //.class.name => :literal, # TODO: create a new class to handle range
34
+ (0..0).class.name => :literal,
35
+ nil.class.name => :text_dim,
36
+ Class.class.name => :text_primary, # Sorry, I lied, Class will never change
37
+ Proc.name => :text_primary # TODO: create a new class to handle proc.
38
+ }.freeze
10
39
 
11
- queue.output.flatten
12
- end
40
+ def initialize
41
+ @klass_decorators = [
42
+ @array_decorator = ArrayDecorator.new(self),
43
+ @string_decorator = StringDecorator.new(self),
44
+ @hash_decorator = HashDecorator.new(self),
45
+ @struct_decorator = StructDecorator.new(self),
46
+ @rails_decorator = RailsDecorator.new(self)
47
+ ]
48
+ @object_decorator = ObjectDecorator.new(self)
49
+ end
13
50
 
14
- attr_reader :output
51
+ def decorate_singleline(variable, line_limit:, depth: 0)
52
+ if primitive?(variable)
53
+ return decorate_primitive(variable, line_limit)
54
+ end
15
55
 
16
- def initialize(output, max_width = nil, new_line = nil)
17
- @output = output
18
- @first = [true]
19
- @max_width = max_width
20
- @width = 0
21
- @new_line = new_line
22
- @loc_decorator = RubyJard::Decorators::LocDecorator.new
23
- end
56
+ @klass_decorators.each do |klass_decorator|
57
+ next unless klass_decorator.match?(variable)
24
58
 
25
- def pp(object)
26
- return if @width > @max_width
27
- return super unless object.is_a?(String)
59
+ spans = klass_decorator.decorate_singleline(variable, line_limit: line_limit, depth: depth)
60
+ return spans unless spans.nil?
61
+ end
62
+ @object_decorator.decorate_singleline(variable, line_limit: line_limit, depth: depth)
63
+ end
28
64
 
29
- puts @output.length
30
- text(object.inspect)
31
- end
65
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
66
+ if primitive?(variable)
67
+ return decorate_primitive(variable, first_line_limit)
68
+ end
32
69
 
33
- def text(str, _max_width = str.length)
34
- if str.start_with?('#<') || ['>'].include?(str)
35
- append_output(str, :object)
36
- elsif [' ', '=', '=>'].include?(str)
37
- append_output(str, :trivial_inspection)
38
- elsif str.start_with?("\e")
39
- append_output(str.gsub(/\e/i, '\e'), :trivial_inspection)
40
- else
41
- spans, _tokens = @loc_decorator.decorate(str)
42
- spans.each do |span|
43
- break if @width > @max_width
70
+ @klass_decorators.each do |klass_decorator|
71
+ next unless klass_decorator.match?(variable)
44
72
 
45
- @output << span
46
- @width += span.content_length
73
+ spans = klass_decorator.decorate_multiline(
74
+ variable,
75
+ first_line_limit: first_line_limit,
76
+ lines: lines,
77
+ line_limit: line_limit,
78
+ depth: depth
79
+ )
80
+ return spans unless spans.nil?
47
81
  end
82
+ @object_decorator.decorate_multiline(
83
+ variable,
84
+ first_line_limit: first_line_limit,
85
+ lines: lines,
86
+ line_limit: line_limit,
87
+ depth: depth
88
+ )
48
89
  end
49
- end
50
90
 
51
- def breakable(sep = ' ', _width = nil)
52
- append_output(sep, :trivial_inspection)
53
- end
91
+ private
54
92
 
55
- def group(_indent = nil, open_obj = nil, close_obj = nil, _open_width = nil, _close_width = nil)
56
- @first.push true
57
- append_output(open_obj, :normal_token) unless open_obj.nil?
58
- yield
59
- append_output(close_obj, :normal_token) unless close_obj.nil?
60
- @first.pop
61
- end
62
-
63
- def append_output(str, style)
64
- return if str.empty?
65
- return if @width > @max_width
93
+ def primitive?(variable)
94
+ !PRIMITIVE_TYPES[RubyJard::Reflection.call_class(variable).name].nil?
95
+ end
66
96
 
67
- @output << Span.new(
68
- span_template: nil,
69
- content: str,
70
- content_length: str.length,
71
- styles: style
72
- )
73
- @width += str.length
97
+ def decorate_primitive(variable, line_limit)
98
+ inspection = variable.inspect
99
+ inspection = inspection[0..line_limit - 2] + '…' if inspection.length >= line_limit
100
+ [
101
+ RubyJard::Span.new(
102
+ content: inspection,
103
+ styles: PRIMITIVE_TYPES[RubyJard::Reflection.call_class(variable).name]
104
+ )
105
+ ]
106
+ end
74
107
  end
75
108
  end
76
109
  end