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,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # Default decorator for non-primitive data structure. It is aimed to replace default `inspect`.
7
+ # If a variable re-implement `#inspect`, it hornors this decision, but still try to
8
+ # parse the result.
9
+ # Otherwise, it use `Kernel#to_s`, and try to push instance variables into the result.
10
+ class ObjectDecorator
11
+ DEFAULT_INSPECTION_PATTERN = /#<(.*:0x[0-9a-z]+)(.*)>/i.freeze
12
+
13
+ def initialize(generic_decorator)
14
+ @generic_decorator = generic_decorator
15
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
16
+ end
17
+
18
+ def decorate_singleline(variable, line_limit:, depth: 0)
19
+ if native_inspect?(variable)
20
+ decorate_native_inspection(variable, line_limit: line_limit, depth: depth)
21
+ else
22
+ decorate_custom_inspection(variable, line_limit: line_limit)
23
+ end
24
+ end
25
+
26
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
27
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
28
+ return [singleline] if singleline.map(&:content_length).sum < line_limit
29
+
30
+ spans = [decorate_native_inspection(variable, line_limit: first_line_limit, with_children: false)]
31
+
32
+ item_count = 0
33
+ instance_variables = RubyJard::Reflection.call_instance_variables(variable)
34
+ instance_variables.each do |key|
35
+ spans << @attributes_decorator.pair(
36
+ key, RubyJard::Reflection.call_instance_variable_get(variable, key),
37
+ line_limit: line_limit, process_key: false, depth: depth + 1
38
+ )
39
+
40
+ item_count += 1
41
+ break if item_count >= lines - 2
42
+ end
43
+
44
+ if instance_variables.length > item_count
45
+ spans << [
46
+ RubyJard::Span.new(
47
+ content: "▸ #{instance_variables.length - item_count} more...",
48
+ margin_left: 2, styles: :text_dim
49
+ )
50
+ ]
51
+ end
52
+
53
+ spans
54
+ end
55
+
56
+ private
57
+
58
+ def native_inspect?(variable)
59
+ return true unless RubyJard::Reflection.call_respond_to?(variable, :inspect)
60
+
61
+ RubyJard::Reflection.bind_call(::Kernel, :method, variable, :inspect).owner == ::Kernel
62
+ end
63
+
64
+ def call_inspect(variable)
65
+ variable.inspect
66
+ rescue StandardError
67
+ RubyJard::Reflection.call_to_s(variable)
68
+ end
69
+
70
+ def decorate_native_inspection(variable, line_limit:, depth: 0, with_children: true)
71
+ raw_inspection = RubyJard::Reflection.call_to_s(variable)
72
+ match = raw_inspection.match(DEFAULT_INSPECTION_PATTERN)
73
+
74
+ if match
75
+ instance_variables = RubyJard::Reflection.call_instance_variables(variable)
76
+ spans = [
77
+ RubyJard::Span.new(content: '#<', styles: :text_primary),
78
+ RubyJard::Span.new(content: match[1], styles: :text_primary)
79
+ ]
80
+ if with_children && !instance_variables.empty?
81
+ spans << RubyJard::Span.new(content: ' ', styles: :text_primary)
82
+ spans += @attributes_decorator.inline_pairs(
83
+ instance_variables.each_with_index, total: instance_variables.length,
84
+ line_limit: line_limit - spans.map(&:content_length).sum - 1,
85
+ depth: depth + 1, process_key: false,
86
+ value_proc: ->(key) { RubyJard::Reflection.call_instance_variable_get(variable, key) }
87
+ )
88
+ end
89
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
90
+ spans
91
+ elsif raw_inspection.length <= line_limit
92
+ [RubyJard::Span.new(content: raw_inspection[0..line_limit], styles: :text_primary)]
93
+ else
94
+ [RubyJard::Span.new(content: raw_inspection[0..line_limit - 3] + '…>', styles: :text_primary)]
95
+ end
96
+ end
97
+
98
+ def decorate_custom_inspection(variable, line_limit:)
99
+ raw_inspection = call_inspect(variable)
100
+ match = raw_inspection.match(DEFAULT_INSPECTION_PATTERN)
101
+ if match
102
+ detail =
103
+ if match[2].length < line_limit - match[1].length - 3
104
+ match[2]
105
+ else
106
+ match[2][0..line_limit - match[1].length - 4] + '…'
107
+ end
108
+ [
109
+ RubyJard::Span.new(content: '#<', styles: :text_primary),
110
+ RubyJard::Span.new(content: match[1], styles: :text_primary),
111
+ RubyJard::Span.new(content: detail, styles: :text_dim),
112
+ RubyJard::Span.new(content: '>', styles: :text_primary)
113
+ ]
114
+ elsif raw_inspection.length <= line_limit
115
+ [RubyJard::Span.new(content: raw_inspection[0..line_limit], styles: :text_primary)]
116
+ else
117
+ [RubyJard::Span.new(content: raw_inspection[0..line_limit - 3] + '…>', styles: :text_primary)]
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -1,100 +1,83 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'pathname'
4
+ require 'rbconfig'
4
5
 
5
6
  module RubyJard
6
7
  module Decorators
7
8
  ##
8
9
  # Simplify and generate labels to indicate the location of a path.
9
- # If it's from gem, strip Gem paths, or Bundler paths to expose relative
10
- # location of the file.
11
- # If it's from the current working dir, strip the working dir.
10
+ # The return value is an array of two elements. The first one is overview,
11
+ # the second is detailed path location.
12
12
  class PathDecorator
13
- GEM_PATTERN = /(.*)-(\d+\.\d+[.\d]*[.\d]*[-.\w]*)/i.freeze
14
- PATH_TYPES = [
15
- TYPE_UNKNOWN = :unknown,
16
- TYPE_PWD = :pwd,
17
- TYPE_GEM = :gem
18
- ].freeze
19
-
20
- attr_reader :path, :lineno, :gem, :gem_version
21
-
22
- def initialize(path, lineno)
23
- @gem = nil
24
- @gem_version = nil
25
- @path = path.to_s
26
- @lineno = lineno
27
- @type = TYPE_UNKNOWN
28
-
29
- decorate
13
+ def initialize(path_classifier: nil)
14
+ @path_classifier = path_classifier || RubyJard::PathClassifier.new
30
15
  end
31
16
 
32
- def decorate
33
- try_parse_gem_path
34
- return if gem?
35
-
36
- @type = TYPE_PWD
37
- if @path.start_with?(Dir.pwd)
38
- @path = @path[Dir.pwd.length..-1]
39
- @path = @path[1..-1] if @path.start_with?('/')
17
+ def decorate(path, lineno = nil)
18
+ return ['at ???', 'at ???'] if path.nil?
19
+
20
+ type, *info = @path_classifier.classify(path)
21
+
22
+ lineno = ":#{lineno}" unless lineno.nil?
23
+
24
+ case type
25
+ when RubyJard::PathClassifier::TYPE_SOURCE_TREE
26
+ path = File.expand_path(path)
27
+ decorate_source_tree(path, lineno)
28
+ when RubyJard::PathClassifier::TYPE_GEM
29
+ decorate_gem(path, lineno, info)
30
+ when RubyJard::PathClassifier::TYPE_STDLIB
31
+ decorate_stdlib(path, lineno, info)
32
+ when RubyJard::PathClassifier::TYPE_INTERNAL
33
+ ["in #{path}", path]
34
+ when RubyJard::PathClassifier::TYPE_EVALUATION
35
+ ["at #{path}#{lineno}", "#{path}#{lineno}"]
36
+ when RubyJard::PathClassifier::TYPE_RUBY_SCRIPT
37
+ ["at (-e ruby script)#{lineno}", "(-e ruby script)#{lineno}"]
40
38
  else
41
- try_relative_path
39
+ path = compact_with_relative_path(path)
40
+ ["at #{path}#{lineno}", "#{path}#{lineno}"]
42
41
  end
43
42
  end
44
43
 
45
- def gem?
46
- @type == TYPE_GEM
47
- end
48
-
49
44
  private
50
45
 
51
- def try_parse_gem_path
52
- gem_paths.each do |gem_path|
53
- next unless path.start_with?(gem_path)
46
+ def decorate_source_tree(path, lineno)
47
+ path = path[Dir.pwd.length..-1]
48
+ path = path[1..-1] if path.start_with?('/')
49
+ path = "#{path}#{lineno}"
50
+ ["at #{path}", path]
51
+ end
54
52
 
55
- @type = TYPE_GEM
56
- splitted_path =
57
- @path[gem_path.length..-1]
58
- .split('/')
59
- .reject(&:empty?)
60
- @path = splitted_path[1..-1].join('/')
61
- @gem = splitted_path.first
62
- match = GEM_PATTERN.match(@gem)
63
- if match
64
- @gem = match[1]
65
- @gem_version = match[2]
53
+ def decorate_gem(_path, lineno, info)
54
+ gem_name, gem_version, relative_path = info
55
+ overview =
56
+ if gem_version.nil?
57
+ "<#{gem_name}>"
58
+ else
59
+ "<#{gem_name} #{gem_version}>"
66
60
  end
61
+ detail = "<#{gem_name}:#{relative_path}#{lineno}>"
62
+ ["in #{overview}", detail]
63
+ end
67
64
 
68
- break
69
- end
65
+ def decorate_stdlib(_path, lineno, info)
66
+ lib_name, relative_path = info
67
+
68
+ ["in <stdlib:#{lib_name}>", "<stdlib:#{relative_path}#{lineno}>"]
70
69
  end
71
70
 
72
- def try_relative_path
73
- relative_path = Pathname.new(@path).relative_path_from(Pathname.pwd).to_s
74
- if relative_path.length < @path.length
75
- @path = relative_path
71
+ def compact_with_relative_path(path)
72
+ relative_path = Pathname.new(path).relative_path_from(Pathname.pwd).to_s
73
+ if relative_path.length < path.length
74
+ relative_path
75
+ else
76
+ path
76
77
  end
77
78
  rescue ArgumentError
78
79
  # Fail to get relative path, ignore
79
- end
80
-
81
- def gem_paths
82
- paths = []
83
-
84
- if defined?(Gem)
85
- Gem.path.each do |gem_path|
86
- paths << File.join(gem_path, 'gems')
87
- paths << gem_path
88
- end
89
- end
90
-
91
- if defined?(Bundler)
92
- bundle_path = Bundler.bundle_path.to_s
93
- paths << File.join(bundle_path, 'gems')
94
- paths << bundle_path
95
- end
96
-
97
- paths
80
+ path
98
81
  end
99
82
  end
100
83
  end
@@ -0,0 +1,194 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyJard
4
+ module Decorators
5
+ ##
6
+ # A collection of rails-specific decorators.
7
+ # Why?
8
+ # Because Rails is magic, and it is like stepping on a minefield. Rails objects
9
+ # can trigger side-effects (like calling database queries, or even API queries).
10
+ # And from the end-user perspective, Rails' internal variables are useless. They
11
+ # care more about database attributes, which requires some extra steps to display
12
+ # if I don't want to use `#inspect`.
13
+ class RailsDecorator
14
+ ##
15
+ # Individual Active Record object is trivial. The object is a mapping from a DB
16
+ # entity to Ruby object. It is always in the memory.
17
+ class ActiveRecordBaseDecorator
18
+ def initialize(generic_decorator)
19
+ @generic_decorator = generic_decorator
20
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
21
+ end
22
+
23
+ def match?(variable)
24
+ return false unless defined?(ActiveRecord::Base)
25
+
26
+ RubyJard::Reflection.call_is_a?(variable, ActiveRecord::Base)
27
+ end
28
+
29
+ def decorate_singleline(variable, line_limit:, depth: 0)
30
+ label = RubyJard::Span.new(
31
+ content: RubyJard::Reflection.call_to_s(variable).chomp!('>'),
32
+ margin_right: 1, styles: :text_primary
33
+ )
34
+ spans = [label]
35
+ spans += @attributes_decorator.inline_pairs(
36
+ variable.attributes.each_with_index,
37
+ total: variable.attributes.length, line_limit: line_limit - label.content_length - 2,
38
+ process_key: false, depth: depth + 1
39
+ )
40
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
41
+ end
42
+
43
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
44
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
45
+
46
+ if singleline.map(&:content_length).sum < line_limit
47
+ [singleline]
48
+ else
49
+ spans = [RubyJard::Span.new(content: RubyJard::Reflection.call_to_s(variable), styles: :text_primary)]
50
+
51
+ item_count = 0
52
+ variable.attributes.each_with_index do |(key, value), index|
53
+ spans << @attributes_decorator.pair(
54
+ key, value, line_limit: line_limit, process_key: false, depth: depth + 1
55
+ )
56
+ item_count += 1
57
+ break if index >= lines - 2
58
+ end
59
+ if variable.attributes.length > item_count
60
+ spans << [RubyJard::Span.new(
61
+ content: "▸ #{variable.attributes.length - item_count} more...",
62
+ margin_left: 2, styles: :text_dim
63
+ )]
64
+ end
65
+ spans
66
+ end
67
+ end
68
+ end
69
+
70
+ ##
71
+ # When creating an active record relation, Rails won't trigger any SQL query, until
72
+ # to_ary events. It is required to check for records loaded before recursively display
73
+ # its children. Hint if the relation is not loaded yet.
74
+ class ActiveRecordRelationDecorator
75
+ def initialize(generic_decorator)
76
+ @generic_decorator = generic_decorator
77
+ @attributes_decorator = RubyJard::Decorators::AttributesDecorator.new(generic_decorator)
78
+ end
79
+
80
+ def match?(variable)
81
+ return false unless defined?(ActiveRecord::Relation)
82
+
83
+ RubyJard::Reflection.call_class(variable) < ActiveRecord::Relation
84
+ rescue StandardError
85
+ false
86
+ end
87
+
88
+ def decorate_singleline(variable, line_limit:, depth: 0)
89
+ if variable.respond_to?(:loaded?) && variable.loaded?
90
+ spans = []
91
+ label = RubyJard::Span.new(
92
+ content: RubyJard::Reflection.call_to_s(variable).chomp('>'), styles: :text_primary
93
+ )
94
+ spans << label
95
+ spans += @attributes_decorator.inline_values(
96
+ variable.each_with_index,
97
+ total: variable.length, line_limit: line_limit - label.content_length - 2,
98
+ depth: depth + 1
99
+ )
100
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
101
+
102
+ spans
103
+ else
104
+ relation_summary(variable, line_limit)
105
+ end
106
+ end
107
+
108
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
109
+ singleline = decorate_singleline(variable, line_limit: first_line_limit)
110
+ if singleline.map(&:content_length).sum < line_limit
111
+ [singleline]
112
+ elsif !variable.respond_to?(:loaded?) || !variable.loaded?
113
+ [relation_summary(variable, first_line_limit)]
114
+ else
115
+ spans = [[RubyJard::Span.new(content: RubyJard::Reflection.call_to_s(variable), styles: :text_primary)]]
116
+
117
+ item_count = 0
118
+ variable.each_with_index do |value, index|
119
+ spans << @attributes_decorator.value(value, line_limit: line_limit, depth: depth + 1)
120
+
121
+ item_count += 1
122
+ break if index >= lines - 2
123
+ end
124
+ if variable.length > item_count
125
+ spans << [RubyJard::Span.new(
126
+ content: "▸ #{variable.length - item_count} more...",
127
+ margin_left: 2, styles: :text_dim
128
+ )]
129
+ end
130
+ spans
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def relation_summary(variable, line_limit)
137
+ overview = RubyJard::Reflection.call_to_s(variable).chomp('>')
138
+ width = overview.length + 1 + 12
139
+ spans = [RubyJard::Span.new(content: overview, styles: :text_primary)]
140
+ if RubyJard::Reflection.call_respond_to?(variable, :to_sql) && width < line_limit
141
+ detail = variable.to_sql
142
+ detail = detail[0..line_limit - width - 2] + '…' if width + detail.length < line_limit
143
+ spans << RubyJard::Span.new(content: detail, styles: :text_dim, margin_left: 1)
144
+ end
145
+ spans << RubyJard::Span.new(content: '>', styles: :text_primary)
146
+ spans << RubyJard::Span.new(content: '(not loaded)', margin_left: 1, styles: :text_dim)
147
+ spans
148
+ end
149
+ end
150
+
151
+ def initialize(generic_decorator)
152
+ @generic_decorator = generic_decorator
153
+ @sub_decorators = [
154
+ @active_record_base_decorator = ActiveRecordBaseDecorator.new(generic_decorator),
155
+ @active_record_relation_decorator = ActiveRecordRelationDecorator.new(generic_decorator)
156
+ ]
157
+ end
158
+
159
+ def match?(variable)
160
+ @sub_decorators.any? { |sub_decorator| sub_decorator.match?(variable) }
161
+ rescue StandardError
162
+ false
163
+ end
164
+
165
+ def decorate_singleline(variable, line_limit:, depth: 0)
166
+ @sub_decorators.each do |sub_decorator|
167
+ next unless sub_decorator.match?(variable)
168
+
169
+ return sub_decorator.decorate_singleline(
170
+ variable, line_limit: line_limit, depth: depth
171
+ )
172
+ end
173
+
174
+ nil
175
+ end
176
+
177
+ def decorate_multiline(variable, first_line_limit:, lines:, line_limit:, depth: 0)
178
+ @sub_decorators.each do |sub_decorator|
179
+ next unless sub_decorator.match?(variable)
180
+
181
+ return sub_decorator.decorate_multiline(
182
+ variable,
183
+ first_line_limit: first_line_limit,
184
+ lines: lines,
185
+ line_limit: line_limit,
186
+ depth: depth
187
+ )
188
+ end
189
+
190
+ nil
191
+ end
192
+ end
193
+ end
194
+ end