ai_refactor 0.3.1 → 0.5.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -2
- data/Gemfile +9 -0
- data/Gemfile.lock +169 -1
- data/README.md +169 -43
- data/Rakefile +1 -1
- data/ai_refactor.gemspec +1 -0
- data/examples/.gitignore +1 -0
- data/examples/ex1_convert_a_rspec_test_to_minitest.yml +7 -0
- data/examples/ex1_input_spec.rb +32 -0
- data/examples/rails_helper.rb +21 -0
- data/examples/test_helper.rb +14 -0
- data/exe/ai_refactor +139 -52
- data/lib/ai_refactor/cli.rb +138 -0
- data/lib/ai_refactor/command_file_parser.rb +27 -0
- data/lib/ai_refactor/context.rb +33 -0
- data/lib/ai_refactor/file_processor.rb +34 -17
- data/lib/ai_refactor/prompt.rb +84 -0
- data/lib/ai_refactor/prompts/diff.md +17 -0
- data/lib/ai_refactor/prompts/input.md +1 -0
- data/lib/ai_refactor/refactors/base_refactor.rb +183 -0
- data/lib/ai_refactor/refactors/custom.rb +43 -0
- data/lib/ai_refactor/refactors/minitest/write_test_for_class.md +15 -0
- data/lib/ai_refactor/refactors/minitest/write_test_for_class.rb +51 -0
- data/lib/ai_refactor/refactors/project/write_changelog_from_history.md +35 -0
- data/lib/ai_refactor/refactors/project/write_changelog_from_history.rb +50 -0
- data/lib/ai_refactor/refactors/{prompts/rspec_to_minitest_rails.md → rails/minitest/rspec_to_minitest.md} +40 -1
- data/lib/ai_refactor/refactors/rails/minitest/rspec_to_minitest.rb +77 -0
- data/lib/ai_refactor/refactors/rspec/minitest_to_rspec.rb +13 -0
- data/lib/ai_refactor/refactors/ruby/refactor_ruby.md +10 -0
- data/lib/ai_refactor/refactors/ruby/refactor_ruby.rb +29 -0
- data/lib/ai_refactor/refactors/ruby/write_ruby.md +7 -0
- data/lib/ai_refactor/refactors/ruby/write_ruby.rb +33 -0
- data/lib/ai_refactor/refactors.rb +13 -5
- data/lib/ai_refactor/run_configuration.rb +115 -0
- data/lib/ai_refactor/{refactors/tests → test_runners}/minitest_runner.rb +2 -2
- data/lib/ai_refactor/{refactors/tests → test_runners}/rspec_runner.rb +1 -1
- data/lib/ai_refactor/{refactors/tests → test_runners}/test_run_diff_report.rb +1 -1
- data/lib/ai_refactor/{refactors/tests → test_runners}/test_run_result.rb +1 -1
- data/lib/ai_refactor/version.rb +1 -1
- data/lib/ai_refactor.rb +13 -8
- metadata +47 -13
- data/lib/ai_refactor/base_refactor.rb +0 -66
- data/lib/ai_refactor/refactors/generic.rb +0 -113
- data/lib/ai_refactor/refactors/minitest_to_rspec.rb +0 -11
- data/lib/ai_refactor/refactors/rspec_to_minitest_rails.rb +0 -103
- /data/lib/ai_refactor/refactors/{prompts → rspec}/minitest_to_rspec.md +0 -0
@@ -0,0 +1,183 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
class BaseRefactor
|
6
|
+
# All subclasses must register themselves with the Registry
|
7
|
+
def self.inherited(subclass)
|
8
|
+
super
|
9
|
+
Refactors.register(subclass)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.description
|
13
|
+
"(No description provided)"
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.takes_input_files?
|
17
|
+
true
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :input_file, :options, :logger
|
21
|
+
attr_accessor :input_content
|
22
|
+
attr_writer :failed_message
|
23
|
+
|
24
|
+
def initialize(input_file, options, logger)
|
25
|
+
@input_file = input_file
|
26
|
+
@options = options
|
27
|
+
@logger = logger
|
28
|
+
end
|
29
|
+
|
30
|
+
def run
|
31
|
+
raise NotImplementedError
|
32
|
+
end
|
33
|
+
|
34
|
+
def failed_message
|
35
|
+
@failed_message || "Reason not specified"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def file_processor
|
41
|
+
context = ::AIRefactor::Context.new(files: options[:context_file_paths], text: options[:context_text], logger: logger)
|
42
|
+
prompt = ::AIRefactor::Prompt.new(input_content: input_content, input_path: input_file, output_file_path: output_file_path, prompt: prompt_input, context: context, logger: logger, options: options)
|
43
|
+
AIRefactor::FileProcessor.new(prompt: prompt, ai_client: ai_client, output_path: output_file_path, logger: logger, options: options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def process!(strip_ticks: true)
|
47
|
+
processor = file_processor
|
48
|
+
|
49
|
+
if processor.output_exists?
|
50
|
+
return false unless overwrite_existing_output?(output_file_path)
|
51
|
+
end
|
52
|
+
|
53
|
+
logger.verbose "Processing #{input_file}..."
|
54
|
+
|
55
|
+
begin
|
56
|
+
output_content, finished_reason, usage = processor.process! do |content|
|
57
|
+
if block_given?
|
58
|
+
yield content
|
59
|
+
elsif strip_ticks
|
60
|
+
content.gsub("```ruby", "").gsub("```", "")
|
61
|
+
else
|
62
|
+
content
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
logger.verbose "AI finished, with reason '#{finished_reason}'..."
|
67
|
+
logger.verbose "Used tokens: #{usage["total_tokens"]}".colorize(:light_black) if usage
|
68
|
+
if finished_reason == "length"
|
69
|
+
logger.warn "Translation may contain an incomplete output as the max token length was reached. You can try using the '--continue' option next time to increase the length of generated output."
|
70
|
+
end
|
71
|
+
|
72
|
+
if !output_content || output_content.length == 0
|
73
|
+
logger.warn "Skipping #{input_file}, no translated output..."
|
74
|
+
logger.error "Failed to translate #{input_file}, finished reason #{finished_reason}"
|
75
|
+
self.failed_message = "AI conversion failed, no output was generated"
|
76
|
+
raise NoOutputError, "No output"
|
77
|
+
end
|
78
|
+
|
79
|
+
output_content
|
80
|
+
rescue => e
|
81
|
+
logger.error "Request to AI failed: #{e.message}"
|
82
|
+
puts e.backtrace
|
83
|
+
logger.warn "Skipping #{input_file}..."
|
84
|
+
self.failed_message = "Request to OpenAI failed"
|
85
|
+
raise e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def overwrite_existing_output?(output_path)
|
90
|
+
overwrite = options && options[:overwrite]&.downcase
|
91
|
+
answer = if ["y", "n"].include? overwrite
|
92
|
+
overwrite
|
93
|
+
else
|
94
|
+
logger.info "Do you wish to overwrite #{output_path}? (y/n)"
|
95
|
+
$stdin.gets.chomp.downcase
|
96
|
+
end
|
97
|
+
if answer == "y"
|
98
|
+
logger.verbose "Overwriting #{output_path}..."
|
99
|
+
return true
|
100
|
+
end
|
101
|
+
logger.warn "Skipping #{input_file}..."
|
102
|
+
self.failed_message = "Skipped as output file already exists"
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
def prompt_input
|
107
|
+
if options && options[:prompt]&.length&.positive?
|
108
|
+
return options[:prompt]
|
109
|
+
end
|
110
|
+
|
111
|
+
file = if options && options[:prompt_file_path]&.length&.positive?
|
112
|
+
options[:prompt_file_path]
|
113
|
+
else
|
114
|
+
location = Module.const_source_location(::AIRefactor::Refactors::BaseRefactor.name)
|
115
|
+
File.join(File.dirname(location.first), "#{refactor_name}.md")
|
116
|
+
end
|
117
|
+
file.tap do |prompt|
|
118
|
+
raise "No prompt file '#{prompt}' found for #{refactor_name}" unless File.exist?(prompt)
|
119
|
+
end
|
120
|
+
|
121
|
+
File.read(file)
|
122
|
+
end
|
123
|
+
|
124
|
+
def output_file_path
|
125
|
+
@output_file_path ||= determine_output_file_path
|
126
|
+
end
|
127
|
+
|
128
|
+
def determine_output_file_path
|
129
|
+
return output_file_path_from_template if output_template_path
|
130
|
+
|
131
|
+
path = options[:output_file_path]
|
132
|
+
return default_output_path unless path
|
133
|
+
|
134
|
+
if path == true
|
135
|
+
input_file
|
136
|
+
else
|
137
|
+
path
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def default_output_path
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def output_template_path
|
146
|
+
options[:output_template_path]
|
147
|
+
end
|
148
|
+
|
149
|
+
def output_file_path_from_template
|
150
|
+
path = output_template_path.gsub("[FILE]", File.basename(input_file))
|
151
|
+
.gsub("[NAME]", File.basename(input_file, ".*"))
|
152
|
+
.gsub("[DIR]", File.dirname(input_file))
|
153
|
+
.gsub("[REFACTOR]", self.class.refactor_name)
|
154
|
+
.gsub("[EXT]", File.extname(input_file))
|
155
|
+
raise "Output template could not be used" unless path.length.positive?
|
156
|
+
path
|
157
|
+
end
|
158
|
+
|
159
|
+
def ai_client
|
160
|
+
@ai_client ||= OpenAI::Client.new
|
161
|
+
end
|
162
|
+
|
163
|
+
def refactor_name
|
164
|
+
self.class.refactor_name
|
165
|
+
end
|
166
|
+
|
167
|
+
class << self
|
168
|
+
def command_line_options
|
169
|
+
[]
|
170
|
+
end
|
171
|
+
|
172
|
+
def refactor_name
|
173
|
+
name.gsub("AIRefactor::Refactors::", "")
|
174
|
+
.gsub(/::/, "/")
|
175
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
176
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
177
|
+
.tr("-", "_")
|
178
|
+
.downcase
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
class Custom < BaseRefactor
|
6
|
+
def run
|
7
|
+
logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path})"
|
8
|
+
logger.verbose "Write output to #{output_file_path}..." if output_file_path
|
9
|
+
|
10
|
+
begin
|
11
|
+
output_content = process!(strip_ticks: false)
|
12
|
+
rescue => e
|
13
|
+
logger.error "Failed to process #{input_file}: #{e.message}"
|
14
|
+
return false
|
15
|
+
end
|
16
|
+
|
17
|
+
return false unless output_content
|
18
|
+
|
19
|
+
output_file_path ? true : output_content
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.description
|
23
|
+
"Generic refactor using user supplied prompt"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def prompt_file_path
|
29
|
+
specified_prompt_path = options[:prompt_file_path]
|
30
|
+
if specified_prompt_path&.length&.positive?
|
31
|
+
if File.exist?(specified_prompt_path)
|
32
|
+
return specified_prompt_path
|
33
|
+
else
|
34
|
+
logger.error "No prompt file '#{specified_prompt_path}' found"
|
35
|
+
end
|
36
|
+
else
|
37
|
+
logger.error "No prompt file was specified!"
|
38
|
+
end
|
39
|
+
exit 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
You are an expert Ruby senior software developer. Write a unit test for the class show below, using minitest and minitest/mock if necessary.
|
2
|
+
Test 100% of the code.
|
3
|
+
|
4
|
+
The path to the file to test is: __{{input_file_path}}__
|
5
|
+
The output file path is: __{{output_file_path}}__
|
6
|
+
|
7
|
+
__{{prompt_header}}__
|
8
|
+
|
9
|
+
Only show me the test file code. Do NOT provide any other description of your work. Always enclose the output code in triple backticks (```).
|
10
|
+
|
11
|
+
__{{context}}__
|
12
|
+
|
13
|
+
__{{prompt_footer}}__
|
14
|
+
|
15
|
+
The class to test is:
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
module Minitest
|
6
|
+
class WriteTestForClass < BaseRefactor
|
7
|
+
def run
|
8
|
+
logger.verbose "'Write minitest test' refactor for #{input_file}..."
|
9
|
+
logger.verbose "Write output to #{output_file_path}..." if output_file_path
|
10
|
+
|
11
|
+
begin
|
12
|
+
output_content = process!
|
13
|
+
rescue => e
|
14
|
+
logger.error "Failed to process #{input_file}: #{e.message}"
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
return false unless output_file_path
|
19
|
+
|
20
|
+
logger.verbose "Generated #{output_file_path} from #{input_file} ..." if output_content
|
21
|
+
|
22
|
+
minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template: "bundle exec ruby __FILE__")
|
23
|
+
|
24
|
+
logger.verbose "Run generated test file #{output_file_path} (#{minitest_runner.command})..."
|
25
|
+
test_run = minitest_runner.run
|
26
|
+
|
27
|
+
if test_run.failed?
|
28
|
+
logger.warn "#{input_file} was translated to #{output_file_path} but the resulting test is failing..."
|
29
|
+
logger.error "Failed to run test, exited with status #{test_run.exitstatus}. Stdout: #{test_run.stdout}\n\nStderr: #{test_run.stderr}\n\n"
|
30
|
+
logger.error "New test failed!", bold: true
|
31
|
+
self.failed_message = "Generated test file failed to run correctly"
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
logger.verbose "\nNew test file ran and returned the following results:"
|
36
|
+
logger.verbose ">> Runs: #{test_run.example_count}, Failures: #{test_run.failure_count}, Skips: #{test_run.pending_count}\n"
|
37
|
+
|
38
|
+
output_file_path ? true : output_content
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.description
|
42
|
+
"Write a minitest test for a class"
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_output_path
|
46
|
+
File.join("test", input_file.gsub(/\.rb$/, "_test.rb"))
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
You are a senior Ruby software developer. You are diligent about writing clear changelogs for your users.
|
2
|
+
|
3
|
+
You have a git history of changes to your project. You want to write a changelog from this history.
|
4
|
+
|
5
|
+
The format of the changelog you will create entries for is from keepachangelog.com version 1.0.0.
|
6
|
+
|
7
|
+
Here is an example of a set of changelog entries:
|
8
|
+
|
9
|
+
```
|
10
|
+
## [0.2.0] - 2015-10-06
|
11
|
+
|
12
|
+
### Changed
|
13
|
+
|
14
|
+
- Remove exclusionary mentions of "open source" since this project can
|
15
|
+
benefit both "open" and "closed" source projects equally.
|
16
|
+
|
17
|
+
## [0.1.0] - 2015-10-06
|
18
|
+
|
19
|
+
### Added
|
20
|
+
|
21
|
+
- Answer "Should you ever rewrite a change log?".
|
22
|
+
|
23
|
+
### Changed
|
24
|
+
|
25
|
+
- Improve argument against commit logs.
|
26
|
+
- Start following [SemVer](https://semver.org) properly.
|
27
|
+
|
28
|
+
### Fixed
|
29
|
+
|
30
|
+
- Fix typos in recent CHANGELOG entries.
|
31
|
+
```
|
32
|
+
|
33
|
+
If the history contains any mention of changes of version (such as "Bump version to 1.0.0"), then you should use that version number in a new changelog entry.
|
34
|
+
|
35
|
+
Now write changelog entries from the following git history.
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
module Project
|
6
|
+
class WriteChangelogFromHistory < BaseRefactor
|
7
|
+
def self.description
|
8
|
+
"Write changelog entries from the git history"
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.takes_input_files?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
def run
|
16
|
+
logger.verbose "Creating changelog entries for project from #{options[:git_commit_count] || 3} commits..."
|
17
|
+
logger.verbose "Write output to #{output_file_path}..." if output_file_path
|
18
|
+
|
19
|
+
self.input_content = `git log -#{options[:git_commit_count] || 3} --pretty=format:"%ci %d %s"`.split("\n").map { |line| "- #{line}" }.join("\n")
|
20
|
+
logger.debug "\nInput messages: \n#{input_content}\n\n"
|
21
|
+
begin
|
22
|
+
output_content = process!(strip_ticks: false)
|
23
|
+
rescue => e
|
24
|
+
logger.error "Failed to process: #{e.message}"
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
return false unless output_content
|
29
|
+
|
30
|
+
output_file_path ? true : output_content
|
31
|
+
end
|
32
|
+
|
33
|
+
def default_output_path
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.command_line_options
|
38
|
+
[
|
39
|
+
{
|
40
|
+
key: :git_commit_count,
|
41
|
+
long: "--commits N",
|
42
|
+
type: Integer,
|
43
|
+
help: "The number of commits to analyse when creating the changelog entries (defaults to 3)"
|
44
|
+
}
|
45
|
+
]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
|
+
You are an expert software developer.
|
1
2
|
You convert RSpec tests to ActiveSupport::TestCase tests for Ruby on Rails.
|
2
3
|
ActiveSupport::TestCase uses MiniTest under the hood.
|
3
4
|
Remember that MiniTest does not support `context` blocks, instead these should be removed and the context
|
4
5
|
specified in them should be moved directly into the relevant tests.
|
5
|
-
Always enclose the output code in triple backticks (```).
|
6
6
|
|
7
7
|
Here are some examples to use as a guide:
|
8
8
|
|
@@ -42,6 +42,10 @@ subject(:model) { create(:order_state) }
|
|
42
42
|
context "when rejected" do
|
43
43
|
before { model.rejected_at = 1.day.ago }
|
44
44
|
|
45
|
+
it "should be not valid" do
|
46
|
+
expect(model).not_to be_valid
|
47
|
+
end
|
48
|
+
|
45
49
|
context "with reason and message" do
|
46
50
|
before do
|
47
51
|
model.rejected_message = reason
|
@@ -68,6 +72,11 @@ setup do
|
|
68
72
|
@reason = "my reason"
|
69
73
|
end
|
70
74
|
|
75
|
+
test "when rejected, model should be not valid" do
|
76
|
+
@model.rejected_at = 1.day.ago
|
77
|
+
refute @model.valid?
|
78
|
+
end
|
79
|
+
|
71
80
|
test "when rejected, with reason and message, model should be valid" do
|
72
81
|
@model.rejected_at = 1.day.ago
|
73
82
|
@model.rejected_message = @reason
|
@@ -231,3 +240,33 @@ test "stubs any instance" do
|
|
231
240
|
end
|
232
241
|
end
|
233
242
|
```
|
243
|
+
|
244
|
+
Example 9) RSpec:
|
245
|
+
```
|
246
|
+
assert_association @model, :message_thread, :belongs_to
|
247
|
+
```
|
248
|
+
|
249
|
+
Result 9) minitest:
|
250
|
+
|
251
|
+
```
|
252
|
+
assert_instance_of MessageThread, @model.message_thread
|
253
|
+
```
|
254
|
+
|
255
|
+
Example 10) RSpec:
|
256
|
+
```
|
257
|
+
assert_association @model, :message_thread, :belongs_to, optional: true
|
258
|
+
```
|
259
|
+
|
260
|
+
Result 10) minitest:
|
261
|
+
```
|
262
|
+
assoc = @model.reflect_on_association(:message_thread)
|
263
|
+
refute assoc.nil?, "no association :message_thread"
|
264
|
+
assert_equal :belongs_to, assoc.macro
|
265
|
+
assert assoc.options[:optional]
|
266
|
+
```
|
267
|
+
|
268
|
+
__{{context}}__
|
269
|
+
|
270
|
+
Only output the refactored class. Do NOT provide any other description of your work. Always enclose the output code in triple backticks (```).
|
271
|
+
|
272
|
+
Convert this Rspec file to minitest:
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
module Rails
|
6
|
+
module Minitest
|
7
|
+
class RspecToMinitest < BaseRefactor
|
8
|
+
def run
|
9
|
+
spec_runner = AIRefactor::TestRunners::RSpecRunner.new(input_file, command_template: options.rspec_run_command)
|
10
|
+
logger.verbose "Run spec #{input_file}... (#{spec_runner.command})"
|
11
|
+
|
12
|
+
spec_run = spec_runner.run
|
13
|
+
|
14
|
+
if spec_run.failed?
|
15
|
+
logger.warn "Skipping #{input_file}..."
|
16
|
+
logger.error "Failed to run #{input_file}, exited with status #{spec_run.exitstatus}. Stdout: #{spec_run.stdout}\n\nStderr: #{spec_run.stderr}\n\n"
|
17
|
+
self.failed_message = "Failed to run RSpec file, has errors"
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
logger.debug "\nOriginal test run results:"
|
22
|
+
logger.debug ">> Examples: #{spec_run.example_count}, Failures: #{spec_run.failure_count}, Pendings: #{spec_run.pending_count}\n"
|
23
|
+
|
24
|
+
begin
|
25
|
+
result = process!
|
26
|
+
rescue AIRefactor::NoOutputError => _e
|
27
|
+
return false
|
28
|
+
rescue => e
|
29
|
+
logger.error "Failed to convert #{input_file} to Minitest, error: #{e.message}"
|
30
|
+
return false
|
31
|
+
end
|
32
|
+
|
33
|
+
logger.verbose "Converted #{input_file} to #{output_file_path}..." if result
|
34
|
+
|
35
|
+
minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template: options.minitest_run_command)
|
36
|
+
|
37
|
+
logger.verbose "Run generated test file #{output_file_path} (#{minitest_runner.command})..."
|
38
|
+
test_run = minitest_runner.run
|
39
|
+
|
40
|
+
if test_run.failed?
|
41
|
+
logger.warn "Skipping #{input_file}..."
|
42
|
+
logger.error "Failed to run translated #{output_file_path}, exited with status #{test_run.exitstatus}. Stdout: #{test_run.stdout}\n\nStderr: #{test_run.stderr}\n\n"
|
43
|
+
logger.error "Conversion failed!", bold: true
|
44
|
+
self.failed_message = "Generated test file failed to run correctly"
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
|
48
|
+
logger.debug "\nTranslated test file results:"
|
49
|
+
logger.debug ">> Runs: #{test_run.example_count}, Failures: #{test_run.failure_count}, Skips: #{test_run.pending_count}\n"
|
50
|
+
|
51
|
+
report = AIRefactor::TestRunners::TestRunDiffReport.new(spec_run, test_run)
|
52
|
+
|
53
|
+
if report.no_differences?
|
54
|
+
logger.verbose "Done converting #{input_file} to #{output_file_path}..."
|
55
|
+
logger.success "\nNo differences found! Conversion worked!"
|
56
|
+
true
|
57
|
+
else
|
58
|
+
logger.warn report.diff.colorize(:yellow)
|
59
|
+
logger.verbose "Done converting #{input_file} to #{output_file_path}..."
|
60
|
+
logger.error "\nDifferences found! Conversion failed!", bold: true
|
61
|
+
self.failed_message = "Generated test file run output did not match original RSpec spec run output"
|
62
|
+
false
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.description
|
67
|
+
"Convert RSpec file to Minitest (for Rails apps)"
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_output_path
|
71
|
+
input_file.gsub("_spec.rb", "_test.rb").gsub("spec/", "test/")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
module Ruby
|
6
|
+
class RefactorRuby < Custom
|
7
|
+
def run
|
8
|
+
logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path})"
|
9
|
+
logger.verbose "Write output to #{output_file_path}..." if output_file_path
|
10
|
+
|
11
|
+
begin
|
12
|
+
output_content = process!(strip_ticks: true)
|
13
|
+
rescue => e
|
14
|
+
logger.error "Failed to process #{input_file}: #{e.message}"
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
return false unless output_content
|
19
|
+
|
20
|
+
output_file_path ? true : output_content
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.description
|
24
|
+
"Generic refactor using user supplied prompt (assumes Ruby code generation)"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module AIRefactor
|
4
|
+
module Refactors
|
5
|
+
module Ruby
|
6
|
+
class WriteRuby < Custom
|
7
|
+
def run
|
8
|
+
logger.verbose "Write some ruby code... (using user supplied prompt #{prompt_file_path})"
|
9
|
+
logger.verbose "Write output to #{output_file_path}..." if output_file_path
|
10
|
+
|
11
|
+
begin
|
12
|
+
output_content = process!(strip_ticks: true)
|
13
|
+
rescue => e
|
14
|
+
logger.error "Failed to process #{input_file}: #{e.message}"
|
15
|
+
return false
|
16
|
+
end
|
17
|
+
|
18
|
+
return false unless output_content
|
19
|
+
|
20
|
+
output_file_path ? true : output_content
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.takes_input_files?
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.description
|
28
|
+
"User supplied prompt to write Ruby code"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -2,6 +2,11 @@
|
|
2
2
|
|
3
3
|
module AIRefactor
|
4
4
|
module Refactors
|
5
|
+
def register(klass)
|
6
|
+
all[klass.refactor_name] = klass
|
7
|
+
end
|
8
|
+
module_function :register
|
9
|
+
|
5
10
|
def get(name)
|
6
11
|
all[name]
|
7
12
|
end
|
@@ -12,16 +17,19 @@ module AIRefactor
|
|
12
17
|
end
|
13
18
|
module_function :names
|
14
19
|
|
15
|
-
def
|
16
|
-
|
17
|
-
hash[klass.refactor_name] = klass
|
18
|
-
end
|
20
|
+
def descriptions
|
21
|
+
names.map { |n| "\"#{n}\"" }.zip(all.values.map(&:description)).to_h
|
19
22
|
end
|
20
|
-
module_function :
|
23
|
+
module_function :descriptions
|
21
24
|
|
22
25
|
def supported?(name)
|
23
26
|
names.include?(name)
|
24
27
|
end
|
25
28
|
module_function :supported?
|
29
|
+
|
30
|
+
def all
|
31
|
+
@all ||= {}
|
32
|
+
end
|
33
|
+
module_function :all
|
26
34
|
end
|
27
35
|
end
|