rbs-dynamic 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.
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../refine/trace_point.rb"
4
+ require_relative "../refine/each_called_method.rb"
5
+
6
+ module RBS module Dynamic module Tracer
7
+ class CalledMethod
8
+ using Refine::TracePoint
9
+ using Refine::TracePoint::ToRBSType
10
+ using Refine::BasicObjectWithKernel
11
+
12
+ using Module.new {
13
+ refine BasicObject do
14
+ def instance_variable_table
15
+ instance_variables.to_h { |name|
16
+ [name, instance_variable_get(name)]
17
+ }
18
+ end
19
+ end
20
+
21
+ refine Hash do
22
+ using Module.new {
23
+ refine Module do
24
+ def original_classname
25
+ if singleton_class?
26
+ self.superclass.name
27
+ else
28
+ self.name
29
+ end
30
+ end
31
+ end
32
+
33
+ refine Hash do
34
+ def to_arg_s
35
+ if self[:op] == :key
36
+ "#{self[:name]}: #{self[:value].inspect}"
37
+ else
38
+ "#{self[:name]} = #{self[:value].inspect}"
39
+ end
40
+ end
41
+ end
42
+ }
43
+
44
+ def method_sig
45
+ if self[:defined_class].singleton_class?
46
+ "#{self[:defined_class].original_classname}.#{seif[:method_id]}(#{self[:arguments].map(&:to_arg_s).join(", ")})"
47
+ elsif self[:defined_class] == BasicObject
48
+ "#{self[:method_id]}(#{self[:arguments].map(&:to_arg_s).join(", ")})"
49
+ else
50
+ "#{self[:defined_class].original_classname}##{self[:method_id]}(#{self[:arguments].map(&:to_arg_s).join(", ")})"
51
+ end
52
+ end
53
+
54
+ def difference(other)
55
+ other.map { |key, value| [key, [self[key], value]] if value != self[key] }.compact.to_h
56
+ end
57
+ end
58
+ }
59
+
60
+ attr_reader :call_stack
61
+ attr_reader :called_line_stack
62
+ attr_reader :trace_point
63
+
64
+ def initialize(
65
+ target_filepath_pattern: /.*/,
66
+ ignore_filepath_pattern: nil,
67
+ target_classname_pattern: /.*/,
68
+ ignore_classname_pattern: nil,
69
+ trace_c_api_method: false
70
+ )
71
+ @target_filepath_pattern = Regexp.new(target_filepath_pattern)
72
+ @ignore_filepath_pattern = ignore_filepath_pattern && Regexp.new(ignore_filepath_pattern)
73
+ @target_classname_pattern = Regexp.new(target_classname_pattern)
74
+ @ignore_classname_pattern = ignore_classname_pattern && Regexp.new(ignore_classname_pattern)
75
+ @trace_c_api_method = trace_c_api_method
76
+ clear
77
+ end
78
+
79
+ def clear
80
+ @call_stack = [{ called_methods: [], block: [] }]
81
+ @last_event = nil
82
+ @called_line_stack = [{}]
83
+ @ignore_rbs_dynamic_trace = false
84
+ @trace_point = TracePoint.new(*tarvet_event, &method(:trace_call_methods))
85
+ @trace_point.extend trace_point_ext
86
+ end
87
+
88
+ def called_methods
89
+ {
90
+ called_methods: call_stack.first[:called_methods]
91
+ }
92
+ end
93
+
94
+ def ignore_rbs_dynamic_trace(&block)
95
+ @ignore_rbs_dynamic_trace = true
96
+ block.call
97
+ ensure
98
+ @ignore_rbs_dynamic_trace = false
99
+ end
100
+
101
+ def enable(**opt, &block)
102
+ clear
103
+ trace_point.enable(**opt, &block)
104
+ ignore_rbs_dynamic_trace { called_methods }
105
+ end
106
+
107
+ def disable(**opt)
108
+ ignore_rbs_dynamic_trace { trace_point.disable(**opt) }
109
+ end
110
+
111
+ def trace(**opt, &block)
112
+ enable(**opt, &block)
113
+ end
114
+
115
+ using Refine::EachCalledMethod
116
+ include Enumerable
117
+ def each(&block)
118
+ called_methods.each_called_method(&block)
119
+ end
120
+
121
+ private
122
+
123
+ def trace_point_ext
124
+ self_ = self
125
+ Module.new {
126
+ define_method(:__called_methods__) {
127
+ self_.called_methods
128
+ }
129
+
130
+ define_method(:__ignore_rbs_dynamic_trace__) {
131
+ self_.ignore_rbs_dynamic_trace(&block)
132
+ }
133
+ }
134
+ end
135
+
136
+ def tarvet_event
137
+ if @trace_c_api_method
138
+ %i(line call return b_call b_return c_call c_return)
139
+ else
140
+ %i(line call return b_call b_return)
141
+ end
142
+ end
143
+
144
+ def target_event?(tp)
145
+ tarvet_event.include?(tp.event)
146
+ end
147
+
148
+ def target_filepath?(path)
149
+ @target_filepath_pattern =~ path
150
+ end
151
+
152
+ def ignore_filepath?(path)
153
+ !target_filepath?(path) || (@ignore_filepath_pattern && @ignore_filepath_pattern =~ path)
154
+ end
155
+
156
+ def target_class?(klass)
157
+ return true if !(Module === klass) || klass.singleton_class?
158
+ @target_classname_pattern =~ klass&.name
159
+ end
160
+
161
+ def ignore_class?(klass)
162
+ return false if !(Module === klass) || klass.singleton_class?
163
+ @ignore_classname_pattern && @ignore_classname_pattern =~ klass&.name
164
+ end
165
+
166
+ def ignore_trace_call_methods?(tp)
167
+ tp.method_id == :ignore_rbs_dynamic_trace \
168
+ || tp.method_id == :__ignore_rbs_dynamic_trace__ \
169
+ || @ignore_rbs_dynamic_trace \
170
+ || ignore_filepath?(tp.path) \
171
+ || (ignore_class?(tp.receiver_class) || ignore_class?(tp.receiver_defined_class)) \
172
+ || !(target_class?(tp.receiver_class) || target_class?(tp.receiver_defined_class))
173
+ end
174
+
175
+ def trace_call_methods(tp)
176
+ return unless target_event?(tp)
177
+ return if ignore_trace_call_methods?(tp)
178
+
179
+ @last_event = tp.event
180
+ case tp.event
181
+ when :line
182
+ called_line_stack.last[:called_lineno] = tp.lineno
183
+ called_line_stack.last[:called_path] = tp.path
184
+ called_line_stack.last[:called_method_id] = tp.method_id
185
+ when :call
186
+ called_line_stack << called_line_stack.last.dup
187
+ called_line = called_line_stack.last
188
+ called_method = tp.meta.merge(
189
+ called_lineno: called_line[:called_lineno],
190
+ called_path: called_line[:called_path],
191
+ called_method_id: called_line[:called_method_id],
192
+ called_methods: [],
193
+ block?: false,
194
+ block: [],
195
+ trace_point_event: tp.event
196
+ )
197
+
198
+ call_stack.last[:called_methods] << called_method
199
+ call_stack << called_method
200
+ when :return
201
+ call_stack.last[:return_value_class] = tp.return_value.class
202
+ call_stack.last[:return_value_rbs_type] = tp.return_value.to_rbs_type
203
+ call_stack.last[:instance_variables_class] = tp.instance_variables_class
204
+ call_stack.last[:instance_variables_rbs_type] = tp.instance_variables_rbs_type
205
+ call_stack.pop if 1 < call_stack.size
206
+ called_line_stack.pop if 1 < called_line_stack.size
207
+ when :b_call
208
+ called_line_stack << called_line_stack.last.dup
209
+ called_line = called_line_stack.last
210
+ called_method = tp.meta.merge(
211
+ called_lineno: called_line[:called_lineno],
212
+ called_path: called_line[:called_path],
213
+ called_method_id: called_line[:called_method_id],
214
+ called_methods: [],
215
+ block?: true,
216
+ block: [],
217
+ trace_point_event: tp.event
218
+ )
219
+
220
+ call_stack.last[:called_methods] << called_method
221
+ call_stack.last[:block] << called_method
222
+ call_stack << called_method
223
+ when :b_return
224
+ call_stack.last[:return_value_class] = tp.return_value.class
225
+ call_stack.last[:return_value_rbs_type] = tp.return_value.to_rbs_type
226
+ call_stack.last[:instance_variables_class] = tp.instance_variables_class
227
+ call_stack.last[:instance_variables_rbs_type] = tp.instance_variables_rbs_type
228
+ call_stack.pop if 1 < call_stack.size
229
+ called_line_stack.pop if 1 < called_line_stack.size
230
+ when :c_call
231
+ called_line_stack << called_line_stack.last.dup
232
+ called_line = called_line_stack.last
233
+ called_method = tp.meta.merge(
234
+ called_lineno: called_line[:called_lineno],
235
+ called_path: called_line[:called_path],
236
+ called_method_id: called_line[:called_method_id],
237
+ called_methods: [],
238
+ block?: false,
239
+ block: [],
240
+ trace_point_event: tp.event
241
+ )
242
+
243
+ call_stack.last[:called_methods] << called_method
244
+ call_stack.last[:block] << called_method
245
+ call_stack << called_method
246
+ when :c_return
247
+ call_stack.last[:return_value_class] = tp.return_value.class
248
+ call_stack.last[:return_value_rbs_type] = tp.return_value.to_rbs_type
249
+ call_stack.last[:instance_variables_class] = tp.instance_variables_class
250
+ call_stack.last[:instance_variables_rbs_type] = tp.instance_variables_rbs_type
251
+ call_stack.pop if 1 < call_stack.size
252
+ called_line_stack.pop if 1 < called_line_stack.size
253
+ end
254
+ end
255
+ end
256
+ end end end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./tracer/called_method.rb"
4
+
5
+ module RBS module Dynamic
6
+ module Tracer
7
+ def self.trace(**opt, &block)
8
+ CalledMethod.new(**opt).enable(&block)[:called_methods].first[:called_methods]
9
+ end
10
+ end
11
+ end end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RBS
4
+ module Dynamic
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "stringio"
4
+ require_relative "./dynamic/version"
5
+ require_relative "./dynamic/config.rb"
6
+ require_relative "./dynamic/tracer.rb"
7
+ require_relative "./dynamic/converter/trace_to_rbs.rb"
8
+
9
+ module RBS
10
+ module Dynamic
11
+ def self.trace(**options, &block)
12
+ config = Config.new(options)
13
+ tmp_stdout= $stdout
14
+ $stdout = StringIO.new unless config.stdout?
15
+ called_methods = RBS::Dynamic::Tracer.trace(
16
+ target_filepath_pattern: config.target_filepath_pattern,
17
+ ignore_filepath_pattern: config.ignore_filepath_pattern,
18
+ # Fiter by Converter, not Tracer
19
+ # Because it is difficult to filter considering `receiver_class` and `receiver_defined_class
20
+ # target_classname_pattern: config.target_classname_pattern,
21
+ # ignore_classname_pattern: config.ignore_classname_pattern,
22
+ trace_c_api_method: config.trace_c_api_method?,
23
+ &block
24
+ )
25
+ Converter::TraceToRBS.new(called_methods).convert(
26
+ root_path: config.root_path,
27
+ except_build_members: config.except_build_members,
28
+ method_defined_calssses: config.method_defined_calssses,
29
+ include_method_location: config.show_method_location?,
30
+ use_literal_type: config.use_literal_type?,
31
+ with_literal_type: config.with_literal_type?,
32
+ use_interface_method_argument: config.use_interface_method_argument?,
33
+ target_classname_pattern: config.target_classname_pattern,
34
+ ignore_classname_pattern: config.ignore_classname_pattern
35
+ )
36
+ ensure
37
+ $stdout = tmp_stdout
38
+ end
39
+
40
+ def self.trace_to_rbs_text(**options, &block)
41
+ result = RBS::Dynamic.trace(**{ stdout: true }.merge(**options), &block)
42
+ decls = result.values
43
+ stdout = StringIO.new
44
+ writer = RBS::Writer.new(out: stdout)
45
+ writer.write(decls)
46
+ stdout.string
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/rbs/dynamic/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "rbs-dynamic"
7
+ spec.version = RBS::Dynamic::VERSION
8
+ spec.authors = ["manga_osyo"]
9
+ spec.email = ["manga.osyo@gmail.com"]
10
+
11
+ spec.summary = "dynamic rbs generator"
12
+ spec.description = "dynamic rbs generator"
13
+ spec.homepage = "https://github.com/osyo-manga/gem-rbs-dynamic"
14
+ spec.required_ruby_version = ">= 2.7.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/osyo-manga/gem-rbs-dynamic"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z`.split("\x0").reject do |f|
23
+ (f == __FILE__) || f.match(%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
24
+ end
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "rbs"
31
+ spec.add_dependency "thor"
32
+
33
+ # Uncomment to register a new dependency of your gem
34
+ # spec.add_dependency "example-gem", "~> 1.0"
35
+
36
+ # For more information and examples about making a new gem, check out our
37
+ # guide at: https://bundler.io/guides/creating_gem.html
38
+ end
@@ -0,0 +1,17 @@
1
+ require "rbs/dynamic"
2
+ require "csv"
3
+
4
+ rbs = RBS::Dynamic.trace_to_rbs_text do
5
+ users =<<-EOS
6
+ id|first name|last name|age
7
+ 1|taro|tanaka|20
8
+ 2|jiro|suzuki|18
9
+ 3|ami|sato|19
10
+ 4|yumi|adachi|21
11
+ EOS
12
+
13
+ csv = CSV.new(users, headers: true, col_sep: "|")
14
+ p csv.class # => CSV
15
+ p csv.first # => #<CSV::Row "id":"1" "first name":"taro" "last name":"tanaka" "age":"20">
16
+ end
17
+ puts rbs
@@ -0,0 +1,36 @@
1
+ require "rbs/dynamic"
2
+
3
+ class FizzBuzz
4
+ def initialize(value)
5
+ @value = value
6
+ end
7
+
8
+ def value; @value end
9
+
10
+ def apply
11
+ value % 15 == 0 ? "FizzBuzz"
12
+ : value % 3 == 0 ? "Fizz"
13
+ : value % 5 == 0 ? "Buzz"
14
+ : value
15
+ end
16
+ end
17
+
18
+ puts "Result"
19
+ rbs = RBS::Dynamic.trace_to_rbs_text do
20
+ p (1..20).map { FizzBuzz.new(_1).apply }
21
+ end
22
+ puts rbs
23
+
24
+ __END__
25
+ output:
26
+ Result
27
+ [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, "FizzBuzz", 16, 17, "Fizz", 19, "Buzz"]
28
+ class FizzBuzz
29
+ private def initialize: (Integer value) -> Integer
30
+
31
+ def apply: () -> (Integer | String)
32
+
33
+ def value: () -> Integer
34
+
35
+ @value: Integer
36
+ end
@@ -0,0 +1,66 @@
1
+ require "rbs/dynamic"
2
+
3
+ class Integer
4
+ def fizz?
5
+ self % 3 == 0
6
+ end
7
+
8
+ def buzz?
9
+ self % 5 == 0
10
+ end
11
+
12
+ def fizzbuzz?
13
+ fizz? && buzz?
14
+ end
15
+ end
16
+
17
+ class FizzBuzz
18
+ def to_fizzbuzz(value)
19
+ value.fizzbuzz? ? "FizzBuzz"
20
+ : value.fizz? ? "Fizz"
21
+ : value.buzz? ? "Buzz"
22
+ : value
23
+ end
24
+
25
+ def call(range, m = nil)
26
+ case [range, m]
27
+ in [Range, nil]
28
+ range.map { to_fizzbuzz(_1) }
29
+ in [Integer, nil]
30
+ call(0..range)
31
+ in [Integer, Integer]
32
+ call(range..m)
33
+ end
34
+ end
35
+ end
36
+ puts "Result"
37
+
38
+ fizzbuzz = FizzBuzz.new
39
+
40
+ # Generating RBS in block
41
+ rbs = RBS::Dynamic.trace_to_rbs_text do
42
+ fizzbuzz.call(0..3)
43
+ fizzbuzz.call(30)
44
+ fizzbuzz.call(10, 20)
45
+ end
46
+ puts rbs
47
+ __END__
48
+ Result
49
+ class Numeric
50
+ end
51
+
52
+ class FizzBuzz
53
+ def call: (Range[Integer] range, ?NilClass m) ?{ (?Integer _1) -> (String | Integer) } -> Array[String | Integer]
54
+ | (Integer range, ?NilClass m) -> Array[String | Integer]
55
+ | (Integer range, ?Integer m) -> Array[String | Integer]
56
+
57
+ def to_fizzbuzz: (Integer value) -> (String | Integer)
58
+ end
59
+
60
+ class Integer < Numeric
61
+ def fizzbuzz?: () -> bool
62
+
63
+ def fizz?: () -> bool
64
+
65
+ def buzz?: () -> bool
66
+ end
@@ -0,0 +1,6 @@
1
+ module Rbs
2
+ module Dynamic
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rbs-dynamic
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - manga_osyo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2022-09-10 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rbs
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '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'
27
+ - !ruby/object:Gem::Dependency
28
+ name: thor
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: dynamic rbs generator
42
+ email:
43
+ - manga.osyo@gmail.com
44
+ executables:
45
+ - rbs-dynamic
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".rspec"
50
+ - Gemfile
51
+ - README.md
52
+ - Rakefile
53
+ - exe/rbs-dynamic
54
+ - lib/bs/dynamic/trace.rb
55
+ - lib/rbs/dynamic.rb
56
+ - lib/rbs/dynamic/builder.rb
57
+ - lib/rbs/dynamic/builder/methods.rb
58
+ - lib/rbs/dynamic/builder/types.rb
59
+ - lib/rbs/dynamic/cli.rb
60
+ - lib/rbs/dynamic/config.rb
61
+ - lib/rbs/dynamic/converter/called_method_to_sigunature.rb
62
+ - lib/rbs/dynamic/converter/called_method_to_with_interface.rb
63
+ - lib/rbs/dynamic/converter/trace_to_rbs.rb
64
+ - lib/rbs/dynamic/refine/basic_object_with_kernel.rb
65
+ - lib/rbs/dynamic/refine/each_called_method.rb
66
+ - lib/rbs/dynamic/refine/signature_merger.rb
67
+ - lib/rbs/dynamic/refine/trace_point.rb
68
+ - lib/rbs/dynamic/trace.rb
69
+ - lib/rbs/dynamic/tracer.rb
70
+ - lib/rbs/dynamic/tracer/called_method.rb
71
+ - lib/rbs/dynamic/version.rb
72
+ - rbs-dynamic.gemspec
73
+ - sample/csv/sample.rb
74
+ - sample/fizzbuzz/sample.rb
75
+ - sample/fizzbuzz2/sample.rb
76
+ - sig/rbs/dynamic.rbs
77
+ homepage: https://github.com/osyo-manga/gem-rbs-dynamic
78
+ licenses: []
79
+ metadata:
80
+ homepage_uri: https://github.com/osyo-manga/gem-rbs-dynamic
81
+ source_code_uri: https://github.com/osyo-manga/gem-rbs-dynamic
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 2.7.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.3.7
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: dynamic rbs generator
101
+ test_files: []