rbs_goose 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.sample +1 -0
- data/.rspec +3 -0
- data/.rubocop.yml +42 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +190 -0
- data/LICENSE.txt +21 -0
- data/README-EN-US.md +78 -0
- data/README.md +78 -0
- data/Rakefile +61 -0
- data/Steepfile +14 -0
- data/assets/logo.svg +1 -0
- data/lib/rbs_goose/configuration.rb +49 -0
- data/lib/rbs_goose/examples/rbs_samples/Steepfile +6 -0
- data/lib/rbs_goose/examples/rbs_samples/lib/email.rb +16 -0
- data/lib/rbs_goose/examples/rbs_samples/lib/person.rb +19 -0
- data/lib/rbs_goose/examples/rbs_samples/lib/phone.rb +22 -0
- data/lib/rbs_goose/examples/rbs_samples/refined/sig/email.rbs +11 -0
- data/lib/rbs_goose/examples/rbs_samples/refined/sig/person.rbs +13 -0
- data/lib/rbs_goose/examples/rbs_samples/refined/sig/phone.rbs +15 -0
- data/lib/rbs_goose/examples/rbs_samples/sig/email.rbs +11 -0
- data/lib/rbs_goose/examples/rbs_samples/sig/person.rbs +13 -0
- data/lib/rbs_goose/examples/rbs_samples/sig/phone.rbs +15 -0
- data/lib/rbs_goose/io/example.rb +27 -0
- data/lib/rbs_goose/io/example_group.rb +45 -0
- data/lib/rbs_goose/io/file.rb +64 -0
- data/lib/rbs_goose/io/target_group.rb +21 -0
- data/lib/rbs_goose/io/typed_ruby.rb +33 -0
- data/lib/rbs_goose/io.rb +12 -0
- data/lib/rbs_goose/templates/default_template.rb +49 -0
- data/lib/rbs_goose/templates.rb +8 -0
- data/lib/rbs_goose/type_inferrer.rb +13 -0
- data/lib/rbs_goose/version.rb +5 -0
- data/lib/rbs_goose.rb +38 -0
- data/rbs_collection.lock.yaml +188 -0
- data/rbs_collection.yaml +19 -0
- data/renovate.json +15 -0
- data/sig/rbs_goose/configuration.rbs +14 -0
- data/sig/rbs_goose/error.rbs +2 -0
- data/sig/rbs_goose/io/example.rbs +9 -0
- data/sig/rbs_goose/io/example_group.rbs +8 -0
- data/sig/rbs_goose/io/file.rbs +19 -0
- data/sig/rbs_goose/io/target_group.rbs +3 -0
- data/sig/rbs_goose/io/typed_ruby.rbs +9 -0
- data/sig/rbs_goose/io.rbs +2 -0
- data/sig/rbs_goose/templates/default_template.rbs +10 -0
- data/sig/rbs_goose/templates.rbs +2 -0
- data/sig/rbs_goose/type_inferrer.rbs +3 -0
- data/sig/rbs_goose.rbs +12 -0
- data/sig_ext/class.rbs +3 -0
- data/sig_ext/langchain/llm.rbs +19 -0
- metadata +128 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'openai'
|
4
|
+
require 'langchain'
|
5
|
+
|
6
|
+
module RbsGoose
|
7
|
+
class Configuration
|
8
|
+
def initialize(&block)
|
9
|
+
self.instruction = default_instruction
|
10
|
+
self.example_groups = default_example_groups
|
11
|
+
self.template_class = default_template_class
|
12
|
+
instance_eval(&block) if block_given?
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :llm, :instruction, :example_groups, :template_class
|
16
|
+
|
17
|
+
def use_open_ai(open_ai_access_token, default_options: {})
|
18
|
+
@llm = ::Langchain::LLM::OpenAI.new(
|
19
|
+
api_key: open_ai_access_token,
|
20
|
+
default_options: {
|
21
|
+
completion_model_name: 'gpt-3.5-turbo-0613',
|
22
|
+
chat_completion_model_name: 'gpt-3.5-turbo-0613'
|
23
|
+
}.merge(default_options)
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def template
|
28
|
+
@template ||= template_class.new(instruction: instruction, example_groups: example_groups)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def default_template_class
|
34
|
+
Templates::DefaultTemplate
|
35
|
+
end
|
36
|
+
|
37
|
+
def default_instruction
|
38
|
+
<<~INSTRUCTION
|
39
|
+
Act as Ruby type inferrer.
|
40
|
+
When ruby source codes and RBS type signatures are given, refine each RBS type signatures. Each file should be split in markdown code format.
|
41
|
+
Use class names, variable names, etc., to infer type.
|
42
|
+
INSTRUCTION
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_example_groups
|
46
|
+
[RbsGoose::IO::ExampleGroup.default_examples[:rbs_samples]]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Email
|
2
|
+
# @dynamic address
|
3
|
+
attr_reader :address
|
4
|
+
|
5
|
+
def initialize(address:)
|
6
|
+
@address = address
|
7
|
+
end
|
8
|
+
|
9
|
+
def ==(other)
|
10
|
+
other.is_a?(self.class) && other.address == address
|
11
|
+
end
|
12
|
+
|
13
|
+
def hash
|
14
|
+
self.class.hash ^ address.hash
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class Person
|
2
|
+
# @dynamic name, contacts
|
3
|
+
attr_reader :name
|
4
|
+
attr_reader :contacts
|
5
|
+
|
6
|
+
def initialize(name:)
|
7
|
+
@name = name
|
8
|
+
@contacts = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def guess_country()
|
12
|
+
contacts.map do |contact|
|
13
|
+
case contact
|
14
|
+
when Phone
|
15
|
+
contact.country
|
16
|
+
end
|
17
|
+
end.compact.first
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Phone
|
2
|
+
# @dynamic country, number
|
3
|
+
attr_reader :country, :number
|
4
|
+
|
5
|
+
def initialize(country:, number:)
|
6
|
+
@country = country
|
7
|
+
@number = number
|
8
|
+
end
|
9
|
+
|
10
|
+
def ==(other)
|
11
|
+
if other.is_a?(Phone)
|
12
|
+
# @type var other: Phone
|
13
|
+
other.country == country && other.number == number
|
14
|
+
else
|
15
|
+
false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def hash
|
20
|
+
self.class.hash ^ country.hash ^ number.hash
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Phone
|
2
|
+
@country: String
|
3
|
+
|
4
|
+
@number: String
|
5
|
+
|
6
|
+
attr_reader country: String
|
7
|
+
|
8
|
+
attr_reader number: String
|
9
|
+
|
10
|
+
def initialize: (country: String, number: String) -> void
|
11
|
+
|
12
|
+
def ==: (untyped other) -> (bool | nil)
|
13
|
+
|
14
|
+
def hash: () -> Integer
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Phone
|
2
|
+
@country: untyped
|
3
|
+
|
4
|
+
@number: untyped
|
5
|
+
|
6
|
+
attr_reader country: untyped
|
7
|
+
|
8
|
+
attr_reader number: untyped
|
9
|
+
|
10
|
+
def initialize: (country: untyped, number: untyped) -> void
|
11
|
+
|
12
|
+
def ==: (untyped other) -> (untyped | nil)
|
13
|
+
|
14
|
+
def hash: () -> untyped
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsGoose
|
4
|
+
module IO
|
5
|
+
class Example
|
6
|
+
class << self
|
7
|
+
def from_path(ruby_path:, rbs_path:, refined_rbs_dir:, base_path:)
|
8
|
+
Example.new(
|
9
|
+
typed_ruby: TypedRuby.from_path(ruby_path: ruby_path, rbs_path: rbs_path, base_path: base_path),
|
10
|
+
refined_rbs: File.new(path: rbs_path, base_path: ::File.join(base_path, refined_rbs_dir))
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(typed_ruby:, refined_rbs:)
|
16
|
+
@typed_ruby = typed_ruby
|
17
|
+
@refined_rbs = refined_rbs
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
{ typed_ruby: typed_ruby, refined_rbs: refined_rbs }
|
22
|
+
end
|
23
|
+
|
24
|
+
attr_reader :typed_ruby, :refined_rbs
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsGoose
|
4
|
+
module IO
|
5
|
+
class ExampleGroup < Array
|
6
|
+
class << self
|
7
|
+
def load_from(base_path, code_dir: 'lib', sig_dir: 'sig', refined_dir: 'refined')
|
8
|
+
new.tap do |group|
|
9
|
+
Dir.glob('**/*.rb', base: ::File.join(base_path, code_dir)).each do |path|
|
10
|
+
group << Example.from_path(
|
11
|
+
ruby_path: ::File.join(code_dir, path),
|
12
|
+
rbs_path: to_rbs_path(path, sig_dir),
|
13
|
+
refined_rbs_dir: refined_dir,
|
14
|
+
base_path: base_path
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_examples
|
21
|
+
example_dir = ::File.join(__dir__.to_s, '../examples')
|
22
|
+
@default_examples ||= Dir.glob('*', base: example_dir).to_h do |dir|
|
23
|
+
[dir.to_sym, load_from(::File.join(example_dir, dir))]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def to_rbs_path(path, sig_dir)
|
30
|
+
::File.join(sig_dir, "#{path}s")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_target_group
|
35
|
+
TargetGroup.new.tap do |g|
|
36
|
+
each { g << _1.typed_ruby }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def to_refined_rbs_list
|
41
|
+
map(&:refined_rbs)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsGoose
|
4
|
+
module IO
|
5
|
+
class File
|
6
|
+
MARKDOWN_REGEXP = /\A```(?<type>ruby|rbs):(?<path>.+)\n(?<content>[\s\S]+)```\Z/
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def from_markdown(markdown)
|
10
|
+
parsed = markdown.match(MARKDOWN_REGEXP)
|
11
|
+
raise ArgumentError, "Ruby or RBS Markdown parsing failed.\n#{markdown}" unless parsed
|
12
|
+
|
13
|
+
new(path: parsed[:path].to_s, content: parsed[:content].to_s)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(path:, content: nil, base_path: nil)
|
18
|
+
@path = path
|
19
|
+
@base_path = base_path
|
20
|
+
@content = content&.strip
|
21
|
+
load_content if @content.nil?
|
22
|
+
end
|
23
|
+
|
24
|
+
def load_content
|
25
|
+
@content = ::File.read(absolute_path).strip
|
26
|
+
end
|
27
|
+
|
28
|
+
def absolute_path
|
29
|
+
base_path ? ::File.join(base_path, path) : path
|
30
|
+
end
|
31
|
+
|
32
|
+
def type
|
33
|
+
@type ||= case path
|
34
|
+
in /\.rb\z/
|
35
|
+
:ruby
|
36
|
+
in /\.rbs\z/
|
37
|
+
:rbs
|
38
|
+
else
|
39
|
+
raise ArgumentError, "Unknown file type: #{path}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
"```#{type}:#{path}\n#{content}\n```\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
def content=(content)
|
48
|
+
@content = content.strip
|
49
|
+
end
|
50
|
+
|
51
|
+
def write
|
52
|
+
::File.write(path, content)
|
53
|
+
end
|
54
|
+
|
55
|
+
def ==(other)
|
56
|
+
self.class == other.class &&
|
57
|
+
@path == other.path &&
|
58
|
+
@content == other.content
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :path, :content, :base_path
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsGoose
|
4
|
+
module IO
|
5
|
+
class TargetGroup < Array
|
6
|
+
class << self
|
7
|
+
def load_from(base_path, code_dir: 'lib', sig_dir: 'sig')
|
8
|
+
new.tap do |group|
|
9
|
+
Dir.glob('**/*.rb', base: ::File.join(base_path, code_dir)).each do |path|
|
10
|
+
group << TypedRuby.from_path(
|
11
|
+
ruby_path: ::File.join(code_dir, path),
|
12
|
+
rbs_path: ::File.join(sig_dir, "#{path}s"),
|
13
|
+
base_path: base_path
|
14
|
+
)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RbsGoose
|
4
|
+
module IO
|
5
|
+
class TypedRuby
|
6
|
+
class << self
|
7
|
+
def from_path(ruby_path:, rbs_path:, base_path:)
|
8
|
+
ruby = File.new(path: ruby_path, base_path: base_path)
|
9
|
+
rbs = begin
|
10
|
+
File.new(path: rbs_path, base_path: base_path)
|
11
|
+
rescue StandardError
|
12
|
+
nil
|
13
|
+
end
|
14
|
+
new(ruby: ruby, rbs: rbs)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize(ruby:, rbs:)
|
19
|
+
raise ArgumentError, 'ruby must have ".rb" extension' unless ruby.type == :ruby
|
20
|
+
raise ArgumentError, 'rbs must have ".rbs" extension' if !rbs.nil? && rbs.type != :rbs
|
21
|
+
|
22
|
+
@ruby = ruby
|
23
|
+
@rbs = rbs
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
rbs.nil? ? ruby.to_s : "#{ruby}\n#{rbs}"
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_reader :ruby, :rbs
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/rbs_goose/io.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'langchain'
|
4
|
+
|
5
|
+
module RbsGoose
|
6
|
+
module Templates
|
7
|
+
class DefaultTemplate
|
8
|
+
def initialize(instruction:, example_groups:)
|
9
|
+
@template = Langchain::Prompt::FewShotPromptTemplate.new(
|
10
|
+
prefix: instruction,
|
11
|
+
suffix: "#{input_template_string}\n",
|
12
|
+
example_prompt: example_prompt,
|
13
|
+
examples: example_groups.map { transform_example_group(_1) },
|
14
|
+
input_variables: %w[typed_ruby_list]
|
15
|
+
)
|
16
|
+
end
|
17
|
+
|
18
|
+
def format(typed_ruby_list)
|
19
|
+
template.format(typed_ruby_list: typed_ruby_list.join("\n"))
|
20
|
+
end
|
21
|
+
|
22
|
+
def parse_result(result)
|
23
|
+
result.scan(/```.+?```/m).map { IO::File.from_markdown(_1) }
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :template
|
29
|
+
|
30
|
+
def example_prompt
|
31
|
+
Langchain::Prompt::PromptTemplate.new(
|
32
|
+
template: "#{input_template_string}\n{refined_rbs_list}",
|
33
|
+
input_variables: %w[typed_ruby_list refined_rbs_list]
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def input_template_string
|
38
|
+
"========Input========\n{typed_ruby_list}\n\n========Output========"
|
39
|
+
end
|
40
|
+
|
41
|
+
def transform_example_group(example_group)
|
42
|
+
{
|
43
|
+
typed_ruby_list: example_group.map(&:typed_ruby).join("\n"),
|
44
|
+
refined_rbs_list: example_group.map(&:refined_rbs).join("\n")
|
45
|
+
}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'templates'
|
4
|
+
|
5
|
+
module RbsGoose
|
6
|
+
class TypeInferrer
|
7
|
+
def infer(target_group)
|
8
|
+
template = RbsGoose.configuration.template
|
9
|
+
result = RbsGoose.llm.complete(prompt: template.format(target_group)).completion
|
10
|
+
template.parse_result(result)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/rbs_goose.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'rbs_goose/configuration'
|
4
|
+
require_relative 'rbs_goose/io'
|
5
|
+
require_relative 'rbs_goose/type_inferrer'
|
6
|
+
require_relative 'rbs_goose/version'
|
7
|
+
|
8
|
+
require 'forwardable'
|
9
|
+
|
10
|
+
module RbsGoose
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
extend Forwardable
|
15
|
+
|
16
|
+
def configure(&block)
|
17
|
+
@configuration = Configuration.new(&block)
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset_configuration
|
21
|
+
@configuration = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def run(code_dir: 'lib', sig_dir: 'sig', base_path: ::Dir.pwd)
|
25
|
+
puts "Run RbsGoose.(Code Directory: #{code_dir}, Signature Directory: #{sig_dir})"
|
26
|
+
target_group = RbsGoose::IO::TargetGroup.load_from(base_path, code_dir: code_dir, sig_dir: sig_dir)
|
27
|
+
RbsGoose::TypeInferrer.new.infer(target_group).each do |refined_rbs|
|
28
|
+
puts "write refined rbs to #{refined_rbs.path}\n"
|
29
|
+
refined_rbs.write
|
30
|
+
puts "done.\n\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
attr_reader :configuration
|
35
|
+
|
36
|
+
def_delegators :configuration, :llm, :instruction, :example_groups
|
37
|
+
end
|
38
|
+
end
|