peeky 0.0.27 → 0.0.40

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.
data/USAGE2.md ADDED
@@ -0,0 +1,5 @@
1
+ # Peeky
2
+
3
+ This document will show you how to use Peeky.
4
+
5
+ Peeky is a Ruby GEM for peaking into ruby classes and extracting meta
data/lib/peeky.rb CHANGED
@@ -12,6 +12,7 @@ require 'peeky/parameter_info'
12
12
  require 'peeky/predicates/attr_reader_predicate'
13
13
  require 'peeky/predicates/attr_writer_predicate'
14
14
 
15
+ require 'peeky/renderer/class_debug_render'
15
16
  require 'peeky/renderer/class_interface_render'
16
17
  require 'peeky/renderer/class_interface_yard_render'
17
18
  require 'peeky/renderer/method_call_minimum_params_render'
@@ -22,3 +23,5 @@ module Peeky
22
23
  class Error < StandardError; end
23
24
  # Your code goes here...
24
25
  end
26
+
27
+ puts "Peeky::Version: #{Peeky::VERSION}" if ENV['KLUE_DEBUG']&.to_s&.downcase == 'true'
data/lib/peeky/api.rb CHANGED
@@ -16,11 +16,24 @@ module Peeky
16
16
  # ClassInfo stores information about the instance of a
17
17
  # class that is passed in including methods, attr_accessors
18
18
  # attr_readers and attr_writers.
19
- def build_class_info(instance)
20
- Peeky::ClassInfo.new(instance)
19
+ #
20
+ # @param instance [Object] instance of class to gather information about (required)
21
+ # @param lazy [TrueClass] lazy load method and parameter information, laze: is optional, defaults to true
22
+ def build_class_info(instance, lazy: true)
23
+ ci = Peeky::ClassInfo.new(instance)
24
+ ci.load unless lazy
25
+ ci
21
26
  end
22
27
 
23
28
  # Render a class using a predefined class renderer
29
+ #
30
+ # ^1: One of class_info and instance must supplied, they are mutually
31
+ # exclusive to each other.
32
+ #
33
+ # @param render_key [Symbol] class render key (required)
34
+ # @param class_info [Object] class_info: is optional^1, defaults to nil
35
+ # @param instance [Object] instance: is optional^1, defaults to nil
36
+ # @param _opts [<key: value>...] **_opts - list of key/values that can help configure render
24
37
  def render_class(render_key, class_info: nil, instance: nil, **_opts)
25
38
  raise 'Call render_class with class_info OR instance.' if class_info.nil? && instance.nil?
26
39
  raise 'Call render_class with class_info OR instance, these parameters are mutually exclusive' if !class_info.nil? && !instance.nil?
@@ -35,6 +48,7 @@ module Peeky
35
48
  # Get a method renderer by :key
36
49
  #
37
50
  # TODO: Refactor to a configurable system
51
+ # @param key [String] key is a shortcut to a specific Peeky::Render that handles method_info (required)
38
52
  def method_renderer(key)
39
53
  case key
40
54
  when :signature
@@ -51,8 +65,11 @@ module Peeky
51
65
  # Get a class renderer by :key
52
66
  #
53
67
  # TODO: Refactor to a configurable system
68
+ # @param key [String] key is a shortcut to a specific Peeky::Renderer that handles class_info (required)
54
69
  def class_renderer(key)
55
70
  case key
71
+ when :class_debug
72
+ Peeky::Renderer::ClassDebugRender
56
73
  when :class_interface
57
74
  Peeky::Renderer::ClassInterfaceRender
58
75
  when :class_interface_yard
@@ -6,7 +6,9 @@ module Peeky
6
6
  # Attr Info is a container that holds read, write or read/write
7
7
  # attributes in the form of MethodInfo objects
8
8
  class AttrInfo
9
+ # reader stores a MethodInfo for a matching reader, nil if readable style method_info not found
9
10
  attr_reader :reader
11
+ # writer stores a MethodInfo for a matching writer, nil if writable style method_info not found
10
12
  attr_reader :writer
11
13
 
12
14
  def initialize(reader: nil, writer: nil)
@@ -16,6 +18,7 @@ module Peeky
16
18
  @writer = writer
17
19
  end
18
20
 
21
+ # Type of the attribute [:attr_writer, :attr_reader or :attr_accessor]
19
22
  def type
20
23
  @type ||= if @reader.nil?
21
24
  :attr_writer
@@ -24,6 +27,7 @@ module Peeky
24
27
  end
25
28
  end
26
29
 
30
+ # Name of the attribute
27
31
  def name
28
32
  @name ||= @reader.nil? ? @writer.clean_name : @reader.clean_name
29
33
  end
@@ -17,6 +17,43 @@ module Peeky
17
17
  @instance = instance
18
18
  end
19
19
 
20
+ # rubocop:disable Metrics/AbcSize
21
+ def to_s
22
+ result = []
23
+ result.push kv('class', class_full_name)
24
+ if defined?(@_ruby_instance_method_names)
25
+ result.push kv('# of instance methods', @_ruby_instance_method_names.join(', '))
26
+ else
27
+ result.push kv('# of instance methods', '')
28
+ end
29
+ if defined?(@_signatures)
30
+ result.push kv('# of accessors', accessors.length)
31
+ result.push kv('# of readers', readers.length)
32
+ result.push kv('# of writers', writers.length)
33
+ result.push kv('# of methods', methods.length)
34
+ else
35
+ result.push kv('# of accessors', '')
36
+ result.push kv('# of readers', '')
37
+ result.push kv('# of writers', '')
38
+ result.push kv('# of methods', '')
39
+ end
40
+ result.join("\n")
41
+ end
42
+ # rubocop:enable Metrics/AbcSize
43
+
44
+ # Load class_info
45
+ #
46
+ # Accessing information about methods and parameters is currently
47
+ # lazy-loaded via memoization.
48
+ #
49
+ # At times during debug or other edge cases, it may be useful to
50
+ # pre-load this information early.
51
+ def load
52
+ ruby_instance_methods
53
+ ruby_instance_method_names
54
+ signatures
55
+ end
56
+
20
57
  # Class full name includes the module namespace
21
58
  def class_full_name
22
59
  instance.class.name
@@ -144,45 +181,12 @@ module Peeky
144
181
  signatures.select { |im| im.name == name && im.implementation_type == filter_type }
145
182
  end
146
183
 
147
- # Build (not sure where I am going with this yet)
148
- # TODO: Refact: Currently the idea is to pre-load data
149
- # this is slower when you are not accessing things, but
150
- # it is easier to debug, so think about what I really want
151
- # here
152
- def build
153
- ruby_instance_methods
154
- ruby_instance_method_names
155
- signatures
156
- end
184
+ private
157
185
 
158
- # Debug
159
- #
160
- # Refact: PATTERN: Come up it an debug inclusion system so that
161
- # so that debug helpers can be included for development and excluded
162
- # for production
163
- # @param format [String] format: <value for format> (optional)
164
- def debug(format: [:signatures])
165
- debug_method_names if format.include?(:method_names)
166
-
167
- return unless format.include?(:signatures)
168
-
169
- puts '-' * 70
170
- puts 'Methods'
171
- puts '-' * 70
172
- signatures.each(&:debug)
173
- end
174
-
175
- def debug_method_names
176
- puts '-' * 70
177
- puts 'Method Names'
178
- puts '-' * 70
179
- ruby_instance_method_names.each do |method_name|
180
- puts method_name
181
- end
186
+ def kv(key, value)
187
+ "#{key.to_s.ljust(25)}: #{value}"
182
188
  end
183
189
 
184
- private
185
-
186
190
  def ruby_instance_method_names
187
191
  @_ruby_instance_method_names ||= instance.class.instance_methods(false).sort
188
192
  end
@@ -47,16 +47,16 @@ module Peeky
47
47
  # F method with required param and optional param
48
48
  #
49
49
  # @param first_name [String] first name (required)
50
- # @param last_name [String] last name (optional)
51
- def f_method_with_required_param_and_optional_param(first_name, last_name = nil)
50
+ # @param last_name [String] last_name is optional, defaults to ''
51
+ def f_method_with_required_param_and_optional_param(first_name, last_name = '')
52
52
  end
53
53
 
54
54
  # G method with required param and two optional params
55
55
  #
56
56
  # @param first_name [String] first name (required)
57
- # @param last_name [String] last name (optional)
58
- # @param age [String] age (optional)
59
- def g_method_with_required_param_and_two_optional_params(first_name, last_name = nil, age = nil)
57
+ # @param last_name [String] last_name is optional, defaults to ''
58
+ # @param age [Integer] age is optional, defaults to 21
59
+ def g_method_with_required_param_and_two_optional_params(first_name, last_name = '', age = 21)
60
60
  end
61
61
 
62
62
  # H list of optional parameters
@@ -79,6 +79,12 @@ module Peeky
79
79
  def j_method_with_list_of_named_arguments(**options)
80
80
  end
81
81
 
82
+ # Jin
83
+ #
84
+ # @param aaa [String] aaa (required)
85
+ def jin(aaa)
86
+ end
87
+
82
88
  # K method with block
83
89
  #
84
90
  # @param code_block [Block] &code_block
@@ -94,11 +100,12 @@ module Peeky
94
100
  # N method with key value param required and optional key value
95
101
  #
96
102
  # @param last_name [String] last_name: <value for last name> (required)
97
- # @param salutation [String] salutation: <value for salutation> (optional)
98
- def n_method_with_key_value_param_required_and_optional_key_value(last_name:, salutation: nil)
103
+ # @param salutation [String] salutation: is optional, defaults to 'Mr'
104
+ def n_method_with_key_value_param_required_and_optional_key_value(last_name:, salutation: 'Mr')
99
105
  end
100
106
 
101
107
  # P available?
108
+
102
109
  # @return [Boolean] true when p available?
103
110
  def p_available?
104
111
  end
@@ -110,13 +117,29 @@ module Peeky
110
117
  # Z complex
111
118
  #
112
119
  # @param aaa [String] aaa (required)
113
- # @param bbb [String] bbb (optional)
120
+ # @param bbb [Integer] bbb is optional, defaults to 1
114
121
  # @param ccc [Array<Object>] *ccc - list of ccc
115
122
  # @param ddd [String] ddd: <value for ddd> (required)
116
- # @param eee [String] eee: <value for eee> (optional)
123
+ # @param eee [Integer] eee: is optional, defaults to 1
117
124
  # @param fff [<key: value>...] **fff - list of key/values
118
125
  # @param ggg [Block] &ggg
119
- def z_complex(aaa, bbb = nil, *ccc, ddd:, eee: nil, **fff, &ggg)
126
+ def z_complex(aaa, bbb = 1, *ccc, ddd:, eee: 1, **fff, &ggg)
127
+ end
128
+
129
+ # Z optional styles
130
+ #
131
+ # @param aaa [String] aaa (required)
132
+ # @param bbb [Integer] bbb is optional, defaults to 123
133
+ # @param ccc [String] ccc is optional, defaults to 'abc'
134
+ # @param ddd [TrueClass] ddd is optional, defaults to true
135
+ # @param eee [FalseClass] eee is optional, defaults to false
136
+ # @param fff [Object] fff is optional, defaults to nil
137
+ # @param ggg [Integer] ggg: is optional, defaults to 123
138
+ # @param hhh [String] hhh: is optional, defaults to 'xyz'
139
+ # @param iii [TrueClass] iii: is optional, defaults to true
140
+ # @param jjj [FalseClass] jjj: is optional, defaults to false
141
+ # @param kkk [Object] kkk: is optional, defaults to nil
142
+ def z_optional_styles(aaa, bbb = 123, ccc = 'abc', ddd = true, eee = false, fff = nil, ggg: 123, hhh: 'xyz', iii: true, jjj: false, kkk: )
120
143
  end
121
144
  end
122
145
  end
@@ -16,26 +16,35 @@ module Peeky
16
16
 
17
17
  def_delegators :focal_method, :name, :receiver, :arity, :super_method
18
18
 
19
- # Stage 2 is the method likely to be an attribute reader or writer
19
+ ## Stage 2 is the method likely to be an attribute reader or writer
20
+ #
20
21
 
21
22
  # Implementation type indicates the probable representation of this
22
23
  # method in ruby, was it `def method` or `attr_reader` / `attr_writer`
23
24
  attr_reader :implementation_type
24
25
 
25
- # TODO: target_instance is required...
26
- def initialize(method, target_instance = nil)
26
+ def initialize(method, target_instance)
27
27
  @focal_method = method
28
+ @target_instance = target_instance
28
29
  @parameters = ParameterInfo.from_method(method)
29
30
  # stage 1
30
31
  # @implementation_type = :method
31
32
 
32
33
  # stage 2
33
- @implementation_type = infer_implementation_type(target_instance)
34
+ infer_implementation_type
35
+
36
+ # stage 3
37
+ infer_default_paramaters
34
38
  end
35
39
 
36
40
  # Stage 2 (working out) attr_accessor
41
+ #
42
+
37
43
  # Name of method minus writer annotations
38
- # Example :writable_attribute= becomes :writable_attribute
44
+ # Example
45
+ # :writable_attribute=
46
+ # # becomes
47
+ # :writable_attribute
39
48
  def clean_name
40
49
  @clean_name ||= begin
41
50
  n = name.to_s
@@ -43,43 +52,113 @@ module Peeky
43
52
  end
44
53
  end
45
54
 
46
- def infer_implementation_type(target_instance)
47
- if target_instance.nil?
48
- :method
49
- elsif match(target_instance, Peeky::Predicates::AttrReaderPredicate)
50
- :attr_reader
51
- elsif match(target_instance, Peeky::Predicates::AttrWriterPredicate)
52
- :attr_writer
53
- else
54
- :method
55
+ # Infer implementation type [:method, :attr_reader or :attr_writer]
56
+ # rubocop:disable Lint/DuplicateBranch
57
+ def infer_implementation_type
58
+ @implementation_type = if @target_instance.nil?
59
+ :method
60
+ elsif match(Peeky::Predicates::AttrReaderPredicate)
61
+ :attr_reader
62
+ elsif match(Peeky::Predicates::AttrWriterPredicate)
63
+ :attr_writer
64
+ else
65
+ :method
66
+ end
67
+ end
68
+ # rubocop:enable Lint/DuplicateBranch
69
+
70
+ # Get parameter by name
71
+ #
72
+ # @param name [String] name (required)
73
+ def get_parameter(name)
74
+ name = name.to_s
75
+ parameters.find { |p| p.name == name }
76
+ end
77
+
78
+ # Has any optional paramaters?
79
+
80
+ # @return [Boolean] true when any parameter is optional?
81
+ def optional?
82
+ parameters.any?(&:optional?)
83
+ end
84
+
85
+ # Infer default paramater values
86
+ #
87
+ # WARNING: Unit test coverage went from .1 seconds to 30-40 seconds
88
+ # when I first introduced this method.
89
+ #
90
+ # I now only call TracePoint if I have optional parameters to be inferred.
91
+ #
92
+ # The tests are now down to 5 seconds, but it highlights the cost of use
93
+ # TracePoint.
94
+ def infer_default_paramaters
95
+ minimalist_method = Peeky::Renderer::MethodCallMinimumParamsRender.new(self).render
96
+
97
+ return if minimalist_method.end_with?('=')
98
+ return unless optional?
99
+
100
+ tracer.enable do
101
+ @target_instance.instance_eval(minimalist_method)
102
+ rescue StandardError => e
103
+ # just print the error for now, we are only attempting to capture the
104
+ # first call, any errors inside the call cannot be dealt with and should
105
+ # not be re-raised
106
+ puts e.message
107
+ end
108
+ end
109
+
110
+ def tracer
111
+ TracePoint.trace(:call, :c_call) do |tp|
112
+ next unless tp.self.is_a?(@target_instance.class)
113
+ next unless tp.method_id == name
114
+
115
+ tp.parameters.each do |_type, param_name|
116
+ method_paramater = get_parameter(param_name)
117
+
118
+ if method_paramater.optional?
119
+ value = tp.binding.local_variable_get(param_name)
120
+ method_paramater.default_value = value
121
+ end
122
+ end
55
123
  end
56
124
  end
57
125
 
58
- def match(target_instance, predicate)
59
- predicate.new.match(target_instance, self)
126
+ # Match
127
+ #
128
+ # @param predicate [String] use a predicate object with the signature match(instance, method_info)
129
+ def match(predicate)
130
+ predicate.new.match(@target_instance, self)
60
131
  end
61
132
 
133
+ # Method?
134
+
135
+ # @return [Boolean] true when implementation type is method?
62
136
  def method?
63
137
  @implementation_type == :method
64
138
  end
65
139
 
66
- # https://github.com/rubyide/vscode-ruby/issues/454
67
- # if I prefix these methods with attr_ then will get an issue
68
- # in the language server.
69
- # Cannot read property 'namedChildren' of undefined
140
+ # Readable?
141
+ #
142
+ # @return [Boolean] true when readable?
70
143
  def readable?
144
+ # Method naming issue: VSCode Ruby Language Server
145
+ #
146
+ # If this method is renamed to attr_readable, same for attr_writable.
147
+ #
148
+ # https://github.com/rubyide/vscode-ruby/issues/454
149
+ # if I prefix these methods with attr_ then will get an issue
150
+ # in the language server.
151
+ #
152
+ # Cannot read property 'namedChildren' of undefined
153
+
71
154
  @implementation_type == :attr_reader
72
155
  end
73
156
 
157
+ # Writable?
158
+
159
+ # @return [Boolean] true when implementation_type writable?
74
160
  def writable?
75
161
  @implementation_type == :attr_writer
76
162
  end
77
-
78
- def debug
79
- puts '-' * 70
80
- puts name
81
- puts '-' * 70
82
- parameters.each(&:debug)
83
- end
84
163
  end
85
164
  end
@@ -20,11 +20,8 @@ module Peeky
20
20
  # type of the parameter
21
21
  attr_accessor :type
22
22
 
23
- # ruby code format when used in a signature
24
- attr_accessor :signature_format
25
-
26
- # minimal required usage in a call to the method with this paramater
27
- attr_accessor :minimal_call_format
23
+ # default value for positional or keyed parameters
24
+ attr_accessor :default_value
28
25
 
29
26
  def initialize(param)
30
27
  map(param)
@@ -51,54 +48,133 @@ module Peeky
51
48
  ruby_method.parameters.map { |ruby_paramater| from_parameter(ruby_paramater) }
52
49
  end
53
50
 
54
- def debug
55
- puts "name : #{name}"
56
- puts "type : #{type}"
57
- puts "signature_format : #{signature_format}"
58
- puts "minimal_call_format : #{minimal_call_format}"
51
+ # ruby code formatted for use in a method signature
52
+ def signature_format
53
+ @_signature_format ||= begin
54
+ method_name = "signature_format_#{@type}".to_sym
55
+
56
+ m = method(method_name)
57
+ m.call
58
+ end
59
+ end
60
+
61
+ # minimal required usage in a call to the method with this paramater
62
+ def minimal_call_format
63
+ @_minimal_call_format ||= begin
64
+ method_name = "minimal_call_format_#{@type}".to_sym
65
+
66
+ if respond_to?(method_name, true)
67
+ m = method(method_name)
68
+ m.call
69
+ else
70
+ minimal_call_format_ignore
71
+ end
72
+ end
73
+ end
74
+
75
+ # Optional?
76
+
77
+ # @return [Boolean] true when parameter is one of the optional types?
78
+ def optional?
79
+ @_optional |= (@type == :param_optional || @type == :key_optional)
80
+ end
81
+
82
+ # Default value type will look at the default value and try to
83
+ # infer the class behind it. Will default to 'Object' fi nil
84
+ def default_value_type
85
+ if @default_value.nil?
86
+ 'Object'
87
+ else
88
+ @default_value.class
89
+ end
90
+ end
91
+
92
+ # Wrap default value in quotes if string, or no wrapping otherwise
93
+ #
94
+ # @param value_for_nil [String] value for nil, generally '' or 'nil' (required)
95
+ def wrap_default_value(value_for_nil)
96
+ if @default_value.is_a?(String)
97
+ "'#{@default_value}'"
98
+ else
99
+ @default_value.nil? ? value_for_nil : @default_value
100
+ end
59
101
  end
60
102
 
61
103
  private
62
104
 
63
105
  # Convert the limited information provided by ruby method.parameters
64
106
  # to a richer structure.
65
- # rubocop:disable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
107
+ # rubocop:disable Metrics/CyclomaticComplexity
66
108
  def map(param)
67
109
  @name = param.length > 1 ? param[1].to_s : ''
68
110
 
111
+ @default_value = nil
112
+
69
113
  case param[0]
70
114
  when :req
71
115
  @type = :param_required
72
- @signature_format = name.to_s
73
- @minimal_call_format = "'#{name}'"
74
116
  when :opt
75
117
  @type = :param_optional
76
- @signature_format = "#{name} = nil"
77
- @minimal_call_format = ''
78
118
  when :rest
79
119
  @type = :splat
80
- @signature_format = "*#{name}"
81
- @minimal_call_format = ''
82
120
  when :keyreq
83
121
  @type = :key_required
84
- @signature_format = "#{name}:"
85
- @minimal_call_format = "#{name}: '#{name}'"
86
122
  when :key
87
123
  @type = :key_optional
88
- @signature_format = "#{name}: nil"
89
- @minimal_call_format = ''
90
124
  when :keyrest
91
125
  @type = :double_splat
92
- @signature_format = "**#{name}"
93
- @minimal_call_format = ''
94
126
  when :block
95
127
  @type = :block
96
- @signature_format = "&#{name}"
97
- @minimal_call_format = ''
98
128
  else
99
129
  raise 'unknown type'
100
130
  end
101
131
  end
102
- # rubocop:enable Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/AbcSize
132
+ # rubocop:enable Metrics/CyclomaticComplexity
133
+
134
+ # Signature format *: Is used to format a parameter when it is used
135
+ # inside of a method signature, eg. def my_method(p1, p2 = 'xyz', p3: :name_value)
136
+
137
+ def signature_format_param_required
138
+ name.to_s
139
+ end
140
+
141
+ def signature_format_param_optional
142
+ "#{name} = #{wrap_default_value('nil')}" # signature format needs to be moved to a method
143
+ end
144
+
145
+ def signature_format_splat
146
+ "*#{name}"
147
+ end
148
+
149
+ def signature_format_key_required
150
+ "#{name}:"
151
+ end
152
+
153
+ def signature_format_key_optional
154
+ "#{name}: #{wrap_default_value('')}"
155
+ end
156
+
157
+ def signature_format_double_splat
158
+ "**#{name}"
159
+ end
160
+
161
+ def signature_format_block
162
+ "&#{name}"
163
+ end
164
+
165
+ # Minimal call format *: Is used to format a call to a method with the least
166
+ # number of parameters needed to make it work.
167
+
168
+ def minimal_call_format_ignore
169
+ ''
170
+ end
171
+
172
+ def minimal_call_format_param_required
173
+ "'#{@name}'"
174
+ end
175
+
176
+ def minimal_call_format_key_required
177
+ "#{@name}: '#{@name}'"
178
+ end
103
179
  end
104
180
  end