ai_refactor 0.4.0 → 0.5.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +21 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +165 -1
- data/README.md +145 -16
- 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 +96 -42
- data/lib/ai_refactor/cli.rb +60 -8
- data/lib/ai_refactor/command_file_parser.rb +27 -0
- data/lib/ai_refactor/context.rb +1 -1
- data/lib/ai_refactor/file_processor.rb +1 -1
- data/lib/ai_refactor/prompt.rb +4 -4
- data/lib/ai_refactor/refactors/base_refactor.rb +9 -7
- data/lib/ai_refactor/refactors/{generic.rb → custom.rb} +2 -2
- data/lib/ai_refactor/refactors/minitest/write_test_for_class.md +5 -1
- data/lib/ai_refactor/refactors/minitest/write_test_for_class.rb +2 -2
- data/lib/ai_refactor/refactors/rails/minitest/rspec_to_minitest.rb +2 -2
- 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/run_configuration.rb +115 -0
- data/lib/ai_refactor/templated_path.rb +48 -0
- data/lib/ai_refactor/test_runners/minitest_runner.rb +1 -1
- data/lib/ai_refactor/version.rb +1 -1
- metadata +16 -4
data/exe/ai_refactor
CHANGED
@@ -8,77 +8,82 @@ require_relative "../lib/ai_refactor"
|
|
8
8
|
|
9
9
|
require "dotenv/load"
|
10
10
|
|
11
|
-
options = {}
|
12
|
-
|
13
11
|
supported_refactors = AIRefactor::Refactors.all
|
14
|
-
|
12
|
+
refactors_descriptions = AIRefactor::Refactors.descriptions
|
13
|
+
|
14
|
+
arguments = ARGV.dup
|
15
|
+
|
16
|
+
options_from_config_file = AIRefactor::Cli.load_options_from_config_file
|
17
|
+
arguments += options_from_config_file if options_from_config_file
|
18
|
+
|
19
|
+
run_config = AIRefactor::RunConfiguration.new
|
15
20
|
|
16
21
|
# General options for all refactor types
|
17
22
|
option_parser = OptionParser.new do |parser|
|
18
|
-
parser.banner = "Usage: ai_refactor
|
23
|
+
parser.banner = "Usage: ai_refactor REFACTOR_TYPE_OR_COMMAND_FILE INPUT_FILE_OR_DIR [options]\n\nWhere REFACTOR_TYPE_OR_COMMAND_FILE is either the path to a command YML file, or one of the refactor types to run: \n- #{refactors_descriptions.to_a.map { |refactor| refactor.join(": ") }.join("\n- ")}\n\n"
|
19
24
|
|
20
25
|
parser.on("-o", "--output [FILE]", String, "Write output to given file instead of stdout. If no path provided will overwrite input file (will prompt to overwrite existing files). Some refactor tasks will write out to a new file by default. This option will override the tasks default behaviour.") do |f|
|
21
|
-
|
26
|
+
run_config.output_file_path = f
|
22
27
|
end
|
23
28
|
|
24
29
|
parser.on("-O", "--output-template TEMPLATE", String, "Write outputs to files instead of stdout. The template is used to create the output name, where the it can have substitutions, '[FILE]', '[NAME]', '[DIR]', '[REFACTOR]' & '[EXT]'. Eg `[DIR]/[NAME]_[REFACTOR][EXT]` (will prompt to overwrite existing files)") do |t|
|
25
|
-
|
30
|
+
run_config.output_template_path = t
|
26
31
|
end
|
27
32
|
|
28
33
|
parser.on("-c", "--context CONTEXT_FILES", Array, "Specify one or more files to use as context for the AI. The contents of these files will be prepended to the prompt sent to the AI.") do |c|
|
29
|
-
|
34
|
+
run_config.context_file_paths = c
|
30
35
|
end
|
31
36
|
|
32
37
|
parser.on("-x", "--extra CONTEXT_TEXT", String, "Specify some text to be prepended to the prompt sent to the AI as extra information of note.") do |c|
|
33
|
-
|
38
|
+
run_config.context_text = c
|
34
39
|
end
|
35
40
|
|
36
41
|
parser.on("-r", "--review-prompt", "Show the prompt that will be sent to ChatGPT but do not actually call ChatGPT or make changes to files.") do
|
37
|
-
|
42
|
+
run_config.review_prompt = true
|
38
43
|
end
|
39
44
|
|
40
45
|
parser.on("-p", "--prompt PROMPT_FILE", String, "Specify path to a text file that contains the ChatGPT 'system' prompt.") do |f|
|
41
|
-
|
46
|
+
run_config.prompt_file_path = f
|
42
47
|
end
|
43
48
|
|
44
49
|
parser.on("-f", "--diffs", "Request AI generate diffs of changes rather than writing out the whole file.") do
|
45
|
-
|
50
|
+
run_config.diff = true
|
46
51
|
end
|
47
52
|
|
48
53
|
parser.on("-C", "--continue [MAX_MESSAGES]", Integer, "If ChatGPT stops generating due to the maximum token count being reached, continue to generate more messages, until a stop condition or MAX_MESSAGES. MAX_MESSAGES defaults to 3") do |c|
|
49
|
-
|
54
|
+
run_config.ai_max_attempts = c
|
50
55
|
end
|
51
56
|
|
52
57
|
parser.on("-m", "--model MODEL_NAME", String, "Specify a ChatGPT model to use (default gpt-4).") do |m|
|
53
|
-
|
58
|
+
run_config.ai_model = m
|
54
59
|
end
|
55
60
|
|
56
61
|
parser.on("--temperature TEMP", Float, "Specify the temperature parameter for ChatGPT (default 0.7).") do |p|
|
57
|
-
|
62
|
+
run_config.ai_temperature = p
|
58
63
|
end
|
59
64
|
|
60
65
|
parser.on("--max-tokens MAX_TOKENS", Integer, "Specify the max number of tokens of output ChatGPT can generate. Max will depend on the size of the prompt (default 1500)") do |m|
|
61
|
-
|
66
|
+
run_config.ai_max_tokens = m
|
62
67
|
end
|
63
68
|
|
64
69
|
parser.on("-t", "--timeout SECONDS", Integer, "Specify the max wait time for ChatGPT response.") do |m|
|
65
|
-
|
70
|
+
run_config.ai_timeout = m
|
66
71
|
end
|
67
72
|
|
68
73
|
parser.on("--overwrite ANSWER", "Always overwrite existing output files, 'y' for yes, 'n' for no, or 'a' for ask. Default to ask.") do |a|
|
69
|
-
|
74
|
+
run_config.overwrite = a
|
70
75
|
end
|
71
76
|
|
72
77
|
parser.on("-N", "--no", "Never overwrite existing output files, same as --overwrite=n.") do |a|
|
73
|
-
|
78
|
+
run_config.overwrite = "n"
|
74
79
|
end
|
75
80
|
|
76
81
|
parser.on("-v", "--verbose", "Show extra output and progress info") do
|
77
|
-
|
82
|
+
run_config.verbose = true
|
78
83
|
end
|
79
84
|
|
80
85
|
parser.on("-d", "--debug", "Show debugging output to help diagnose issues") do
|
81
|
-
|
86
|
+
run_config.debug = true
|
82
87
|
end
|
83
88
|
|
84
89
|
parser.on("-h", "--help", "Prints this help") do
|
@@ -109,45 +114,94 @@ option_parser = OptionParser.new do |parser|
|
|
109
114
|
refactorer.command_line_options.each do |option|
|
110
115
|
args = [option[:long], option[:type], option[:help]]
|
111
116
|
args.unshift(option[:short]) if option[:short]
|
117
|
+
AIRefactor::RunConfiguration.add_new_option(option[:key])
|
112
118
|
parser.on(*args) do |o|
|
113
|
-
|
119
|
+
run_config.send("#{option[:key]}=", o.nil? ? true : o)
|
114
120
|
end
|
115
121
|
end
|
116
122
|
end
|
117
123
|
end
|
118
124
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
125
|
+
def exit_with_option_error(message, option_parser = nil, logger = nil)
|
126
|
+
logger ? logger.error(message, bold: true) : puts(message)
|
127
|
+
puts option_parser if option_parser
|
128
|
+
exit false
|
129
|
+
end
|
124
130
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
home_config_file_path
|
131
|
+
def exit_with_error(message, logger = nil)
|
132
|
+
logger ? logger.error(message, bold: true) : puts(message)
|
133
|
+
exit false
|
129
134
|
end
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
135
|
+
|
136
|
+
# If no command was provided, prompt for one in interactive mode
|
137
|
+
if arguments.empty? || arguments.all? { |arg| arg.start_with?("-") && !(arg == "-h" || arg == "--help") }
|
138
|
+
interactive_log = AIRefactor::Logger.new
|
139
|
+
# For each option that is required but not provided, prompt for it
|
140
|
+
# Put the option in arguments to parse with option_parser
|
141
|
+
interactive_log.info "Interactive mode started. You can use tab to autocomplete:"
|
142
|
+
predefined_commands = AIRefactor::Refactors.names
|
143
|
+
|
144
|
+
interactive_log.info "Available refactors: #{predefined_commands.join(", ")}\n"
|
145
|
+
command = AIRefactor::Cli.request_input_with_autocomplete("Enter refactor name: ", predefined_commands)
|
146
|
+
exit_with_option_error("No refactor name provided.", option_parser) if command.nil? || command.empty?
|
147
|
+
initial = [command]
|
148
|
+
|
149
|
+
input_path = AIRefactor::Cli.request_file_inputs("Enter input file path: ", multiple: false)
|
150
|
+
exit_with_option_error("No input file path provided.", option_parser) if input_path.nil? || input_path.empty?
|
151
|
+
initial << input_path
|
152
|
+
|
153
|
+
arguments.prepend(*initial)
|
154
|
+
|
155
|
+
# Ask if template should be used - then prompt for it
|
156
|
+
|
157
|
+
output = AIRefactor::Cli.request_file_inputs("Enter output file path (blank for refactor default): ", multiple: false)
|
158
|
+
arguments.concat(["-o", " #{output}"]) unless output.nil? || output.empty?
|
159
|
+
|
160
|
+
context_text = AIRefactor::Cli.request_text_input("Enter extra text to add to prompt (blank for none): ")
|
161
|
+
arguments.concat(["-x", context_text]) unless context_text.nil? || context_text.empty?
|
162
|
+
|
163
|
+
context_files = AIRefactor::Cli.request_file_inputs("Enter extra context file path(s) (blank for none): ")
|
164
|
+
arguments.concat(["-c", context_files]) unless context_files.nil? || context_files.empty?
|
165
|
+
|
166
|
+
prompt_file = AIRefactor::Cli.request_file_inputs("Enter Prompt file path (blank for refactor default): ", multiple: false)
|
167
|
+
arguments.concat(["-p", prompt_file]) unless prompt_file.nil? || prompt_file.empty?
|
168
|
+
|
169
|
+
review = AIRefactor::Cli.request_switch("Dry-run (review prompt only)? (y/N) (blank for 'N'): ")
|
170
|
+
arguments << "-r" if review
|
134
171
|
end
|
135
172
|
|
136
|
-
|
173
|
+
File.write(".ai_refactor_history", arguments.join(" ") + "\n", mode: "a")
|
174
|
+
|
175
|
+
begin
|
176
|
+
option_parser.parse!(arguments)
|
177
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
178
|
+
exit_with_option_error($!, option_parser)
|
179
|
+
end
|
137
180
|
|
138
|
-
logger = AIRefactor::Logger.new(verbose:
|
181
|
+
logger = AIRefactor::Logger.new(verbose: run_config.verbose, debug: run_config.debug)
|
182
|
+
logger.info "Also loaded options from '.ai_refactor' file..." if options_from_config_file&.size&.positive?
|
139
183
|
|
140
|
-
|
141
|
-
|
184
|
+
command_or_file = arguments.shift
|
185
|
+
if AIRefactor::CommandFileParser.command_file?(command_or_file)
|
186
|
+
logger.info "Loading refactor command file '#{command_or_file}'..."
|
187
|
+
begin
|
188
|
+
run_config.set!(AIRefactor::CommandFileParser.new(command_or_file).parse)
|
189
|
+
rescue => e
|
190
|
+
exit_with_option_error(e.message, option_parser, logger)
|
191
|
+
end
|
192
|
+
else
|
193
|
+
logger.info "Requested to run refactor '#{command_or_file}'..."
|
194
|
+
run_config.refactor = command_or_file
|
142
195
|
end
|
143
196
|
|
144
|
-
|
197
|
+
run_config.input_file_paths = arguments
|
198
|
+
|
199
|
+
job = AIRefactor::Cli.new(run_config, logger: logger)
|
145
200
|
|
146
201
|
unless job.valid?
|
147
|
-
|
148
|
-
exit 1
|
202
|
+
exit_with_error("Refactor job failed or was not correctly configured. Did you specify the required inputs or options?.", logger)
|
149
203
|
end
|
150
204
|
|
151
205
|
unless job.run
|
152
|
-
exit
|
206
|
+
exit false
|
153
207
|
end
|
data/lib/ai_refactor/cli.rb
CHANGED
@@ -1,15 +1,67 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "readline"
|
4
|
+
|
3
5
|
module AIRefactor
|
4
6
|
class Cli
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
7
|
+
class << self
|
8
|
+
def load_options_from_config_file
|
9
|
+
# Load config from ~/.ai_refactor or .ai_refactor
|
10
|
+
home_config_file_path = File.expand_path("~/.ai_refactor")
|
11
|
+
local_config_file_path = File.join(Dir.pwd, ".ai_refactor")
|
12
|
+
|
13
|
+
config_file_path = if File.exist?(local_config_file_path)
|
14
|
+
local_config_file_path
|
15
|
+
elsif File.exist?(home_config_file_path)
|
16
|
+
home_config_file_path
|
17
|
+
end
|
18
|
+
return unless config_file_path
|
19
|
+
|
20
|
+
config_string = File.read(config_file_path)
|
21
|
+
config_lines = config_string.split(/\n+/).reject { |s| s =~ /\A\s*#/ }.map(&:strip)
|
22
|
+
config_lines.flat_map(&:shellsplit)
|
23
|
+
end
|
24
|
+
|
25
|
+
def request_text_input(prompt)
|
26
|
+
puts prompt
|
27
|
+
gets.chomp
|
28
|
+
end
|
29
|
+
|
30
|
+
def request_input_with_autocomplete(prompt, completion_list)
|
31
|
+
Readline.completion_append_character = nil
|
32
|
+
Readline.completion_proc = proc do |str|
|
33
|
+
completion_list.grep(/^#{Regexp.escape(str)}/)
|
34
|
+
end
|
35
|
+
Readline.readline(prompt, true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def request_file_inputs(prompt, multiple: true)
|
39
|
+
Readline.completion_append_character = multiple ? " " : nil
|
40
|
+
Readline.completion_proc = Readline::FILENAME_COMPLETION_PROC
|
41
|
+
|
42
|
+
paths = Readline.readline(prompt, true)
|
43
|
+
multiple ? paths.gsub(/[^\\] /, ",") : paths
|
44
|
+
end
|
45
|
+
|
46
|
+
def request_switch(prompt)
|
47
|
+
(Readline.readline(prompt, true) =~ /^y/i) ? true : false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(configuration, logger:)
|
52
|
+
@configuration = configuration
|
9
53
|
@logger = logger
|
10
54
|
end
|
11
55
|
|
12
|
-
attr_reader :
|
56
|
+
attr_reader :configuration, :logger
|
57
|
+
|
58
|
+
def refactoring_type
|
59
|
+
configuration.refactor || raise(StandardError, "No refactor provided")
|
60
|
+
end
|
61
|
+
|
62
|
+
def inputs
|
63
|
+
configuration.input_file_paths
|
64
|
+
end
|
13
65
|
|
14
66
|
def valid?
|
15
67
|
return false unless refactorer
|
@@ -23,7 +75,7 @@ module AIRefactor
|
|
23
75
|
OpenAI.configure do |config|
|
24
76
|
config.access_token = ENV.fetch("OPENAI_API_KEY")
|
25
77
|
config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID", nil)
|
26
|
-
config.request_timeout =
|
78
|
+
config.request_timeout = configuration.ai_timeout || 240
|
27
79
|
end
|
28
80
|
|
29
81
|
if refactorer.takes_input_files?
|
@@ -37,7 +89,7 @@ module AIRefactor
|
|
37
89
|
return_values = expanded_inputs.map do |file|
|
38
90
|
logger.info "Processing #{file}..."
|
39
91
|
|
40
|
-
refactor = refactorer.new(file,
|
92
|
+
refactor = refactorer.new(file, configuration, logger)
|
41
93
|
refactor_returned = refactor.run
|
42
94
|
failed = refactor_returned == false
|
43
95
|
if failed
|
@@ -63,7 +115,7 @@ module AIRefactor
|
|
63
115
|
name = refactorer.refactor_name
|
64
116
|
logger.info "AI Refactor - #{name} refactor\n"
|
65
117
|
logger.info "====================\n"
|
66
|
-
refactor = refactorer.new(nil,
|
118
|
+
refactor = refactorer.new(nil, configuration, logger)
|
67
119
|
refactor_returned = refactor.run
|
68
120
|
failed = refactor_returned == false
|
69
121
|
if failed
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "yaml"
|
4
|
+
|
5
|
+
module AIRefactor
|
6
|
+
class CommandFileParser
|
7
|
+
def self.command_file?(name)
|
8
|
+
name.match?(/\.ya?ml$/)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(path)
|
12
|
+
@path = path
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse
|
16
|
+
raise StandardError, "Invalid command file: file does not exist" unless File.exist?(@path)
|
17
|
+
|
18
|
+
options = YAML.safe_load_file(@path, permitted_classes: [Symbol], symbolize_names: true, aliases: true)
|
19
|
+
|
20
|
+
unless options && options[:refactor]
|
21
|
+
raise StandardError, "Invalid command file format, a 'refactor' key is required"
|
22
|
+
end
|
23
|
+
|
24
|
+
options
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/ai_refactor/context.rb
CHANGED
@@ -22,7 +22,7 @@ module AIRefactor
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def process!
|
25
|
-
logger.debug("Processing #{@prompt.input_file_path} with prompt in #{
|
25
|
+
logger.debug("Processing #{@prompt.input_file_path} with prompt in #{options.prompt_file_path}")
|
26
26
|
logger.debug("Options: #{options.inspect}")
|
27
27
|
messages = @prompt.chat_messages
|
28
28
|
if options[:review_prompt]
|
data/lib/ai_refactor/prompt.rb
CHANGED
@@ -9,18 +9,18 @@ module AIRefactor
|
|
9
9
|
CONTEXT_MARKER = "__{{context}}__"
|
10
10
|
CONTENT_MARKER = "__{{content}}__"
|
11
11
|
|
12
|
-
attr_reader :input_file_path
|
12
|
+
attr_reader :input_file_path
|
13
13
|
|
14
|
-
def initialize(options:, logger:, context: nil, input_content: nil, input_path: nil, output_file_path: nil,
|
14
|
+
def initialize(options:, logger:, context: nil, input_content: nil, input_path: nil, output_file_path: nil, prompt: nil, prompt_header: nil, prompt_footer: nil)
|
15
15
|
@input_content = input_content
|
16
16
|
@input_file_path = input_path
|
17
17
|
@output_file_path = output_file_path
|
18
|
-
@prompt_file_path = prompt_file_path
|
19
18
|
@logger = logger
|
20
19
|
@header = prompt_header
|
21
20
|
@footer = prompt_footer
|
22
21
|
@diff = options[:diff]
|
23
22
|
@context = context
|
23
|
+
@prompt = prompt || raise(StandardError, "Prompt not provided")
|
24
24
|
end
|
25
25
|
|
26
26
|
def chat_messages
|
@@ -41,7 +41,7 @@ module AIRefactor
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def system_prompt_template
|
44
|
-
|
44
|
+
@prompt
|
45
45
|
end
|
46
46
|
|
47
47
|
def system_prompt_footer
|
@@ -39,7 +39,7 @@ module AIRefactor
|
|
39
39
|
|
40
40
|
def file_processor
|
41
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,
|
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
43
|
AIRefactor::FileProcessor.new(prompt: prompt, ai_client: ai_client, output_path: output_file_path, logger: logger, options: options)
|
44
44
|
end
|
45
45
|
|
@@ -102,7 +102,11 @@ module AIRefactor
|
|
102
102
|
false
|
103
103
|
end
|
104
104
|
|
105
|
-
def
|
105
|
+
def prompt_input
|
106
|
+
if options && options[:prompt]&.length&.positive?
|
107
|
+
return options[:prompt]
|
108
|
+
end
|
109
|
+
|
106
110
|
file = if options && options[:prompt_file_path]&.length&.positive?
|
107
111
|
options[:prompt_file_path]
|
108
112
|
else
|
@@ -112,6 +116,8 @@ module AIRefactor
|
|
112
116
|
file.tap do |prompt|
|
113
117
|
raise "No prompt file '#{prompt}' found for #{refactor_name}" unless File.exist?(prompt)
|
114
118
|
end
|
119
|
+
|
120
|
+
File.read(file)
|
115
121
|
end
|
116
122
|
|
117
123
|
def output_file_path
|
@@ -140,11 +146,7 @@ module AIRefactor
|
|
140
146
|
end
|
141
147
|
|
142
148
|
def output_file_path_from_template
|
143
|
-
path =
|
144
|
-
.gsub("[NAME]", File.basename(input_file, ".*"))
|
145
|
-
.gsub("[DIR]", File.dirname(input_file))
|
146
|
-
.gsub("[REFACTOR]", self.class.refactor_name)
|
147
|
-
.gsub("[EXT]", File.extname(input_file))
|
149
|
+
path = AIRefactor::TemplatedPath.new(input_file, refactor_name, output_template_path).generate
|
148
150
|
raise "Output template could not be used" unless path.length.positive?
|
149
151
|
path
|
150
152
|
end
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
module AIRefactor
|
4
4
|
module Refactors
|
5
|
-
class
|
5
|
+
class Custom < BaseRefactor
|
6
6
|
def run
|
7
|
-
logger.verbose "
|
7
|
+
logger.verbose "Custom refactor to #{input_file}... (using user supplied prompt #{prompt_file_path})"
|
8
8
|
logger.verbose "Write output to #{output_file_path}..." if output_file_path
|
9
9
|
|
10
10
|
begin
|
@@ -4,8 +4,12 @@ Test 100% of the code.
|
|
4
4
|
The path to the file to test is: __{{input_file_path}}__
|
5
5
|
The output file path is: __{{output_file_path}}__
|
6
6
|
|
7
|
-
|
7
|
+
__{{prompt_header}}__
|
8
8
|
|
9
9
|
__{{context}}__
|
10
10
|
|
11
|
+
Only show me the test file code. Do NOT provide any other description of your work. Always enclose the output code in triple backticks (```).
|
12
|
+
|
13
|
+
__{{prompt_footer}}__
|
14
|
+
|
11
15
|
The class to test is:
|
@@ -19,9 +19,9 @@ module AIRefactor
|
|
19
19
|
|
20
20
|
logger.verbose "Generated #{output_file_path} from #{input_file} ..." if output_content
|
21
21
|
|
22
|
-
minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template:
|
22
|
+
minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template: options.minitest_run_command)
|
23
23
|
|
24
|
-
logger.verbose "Run generated test file #{output_file_path} (
|
24
|
+
logger.verbose "Run generated test file #{output_file_path} (`#{minitest_runner.command}`)..."
|
25
25
|
test_run = minitest_runner.run
|
26
26
|
|
27
27
|
if test_run.failed?
|
@@ -6,7 +6,7 @@ module AIRefactor
|
|
6
6
|
module Minitest
|
7
7
|
class RspecToMinitest < BaseRefactor
|
8
8
|
def run
|
9
|
-
spec_runner = AIRefactor::TestRunners::RSpecRunner.new(input_file)
|
9
|
+
spec_runner = AIRefactor::TestRunners::RSpecRunner.new(input_file, command_template: options.rspec_run_command)
|
10
10
|
logger.verbose "Run spec #{input_file}... (#{spec_runner.command})"
|
11
11
|
|
12
12
|
spec_run = spec_runner.run
|
@@ -32,7 +32,7 @@ module AIRefactor
|
|
32
32
|
|
33
33
|
logger.verbose "Converted #{input_file} to #{output_file_path}..." if result
|
34
34
|
|
35
|
-
minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path)
|
35
|
+
minitest_runner = AIRefactor::TestRunners::MinitestRunner.new(output_file_path, command_template: options.minitest_run_command)
|
36
36
|
|
37
37
|
logger.verbose "Run generated test file #{output_file_path} (#{minitest_runner.command})..."
|
38
38
|
test_run = minitest_runner.run
|
@@ -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
|