#!/usr/bin/env ruby require "optparse" require "colorize" require "openai" require_relative "../lib/ai_refactor" options = {} supported_refactors = AIRefactor::Refactors.all supported_names = AIRefactor::Refactors.names # General options for all refactor types option_parser = OptionParser.new do |parser| parser.banner = "Usage: ai_refactor REFACTOR_TYPE INPUT_FILE_OR_DIR [options]\n\nWhere REFACTOR_TYPE is one of: #{supported_names}\n\n" # todo: support for a sort of generic process which uses a custom prompt file parser.on("-p", "--prompt PROMPT_FILE", String, "Specify path to a text file that contains the ChatGPT 'system' prompt.") do |f| options[:prompt_file_path] = f end 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| options[:ai_max_attempts] = c || 3 end parser.on("-m", "--model MODEL_NAME", String, "Specify a ChatGPT model to use (default gpt-3.5-turbo).") do |m| options[:ai_model] = m end parser.on(nil, "--temperature TEMP", Float, "Specify the temperature parameter for ChatGPT (default 0.7).") do |p| options[:ai_temperature] = p end parser.on(nil, "--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| options[:ai_max_tokens] = m end parser.on("-t", "--timeout SECONDS", Integer, "Specify the max wait time for ChatGPT response.") do |m| options[:ai_timeout] = m end parser.on("-v", "--verbose", "Show extra output and progress info") do options[:verbose] = true end parser.on("-d", "--debug", "Show debugging output to help diagnose issues") do options[:debug] = true end supported_refactors.each do |_name, refactorer| refactorer.command_line_options.each do |option| parser.on(option[:short], option[:long], option[:type], option[:help]) do |o| options[option[:key]] = o end end end parser.on("-h", "--help", "Prints this help") do puts parser exit end end option_parser.parse! logger = AIRefactor::Logger.new(verbose: options[:verbose], debug: options[:debug]) refactoring_type = ARGV.shift input_file_path = ARGV if !AIRefactor::Refactors.supported?(refactoring_type) || input_file_path.nil? || input_file_path.empty? puts option_parser.help exit 1 end OpenAI.configure do |config| config.access_token = ENV.fetch("OPENAI_API_KEY") config.organization_id = ENV.fetch("OPENAI_ORGANIZATION_ID", nil) config.request_timeout = options[:ai_timeout] || 240 end refactorer = AIRefactor::Refactors.get(refactoring_type) inputs = input_file_path.map do |path| File.exist?(path) ? path : Dir.glob(path) end.flatten logger.info "AI Refactor #{inputs.size} files(s)/dir(s) '#{input_file_path}' with #{refactorer.refactor_name} refactor\n" logger.info "====================\n" return_values = inputs.map do |file| logger.info "Processing #{file}..." refactor = refactorer.new(file, options, logger) refactor_returned = refactor.run failed = refactor_returned == false if failed logger.warn "Refactor failed on #{file}\nFailed due to: #{refactor.failed_message}\n" else logger.success "Refactor succeeded on #{file}\n" if refactor_returned.is_a?(String) logger.info "Refactor #{file} output:\n\n#{refactor_returned}\n\n" end end failed ? [file, refactor.failed_message] : true end if return_values.all?(true) logger.success "All files processed successfully!" else files = return_values.select { |v| v != true } logger.warn "Some files failed to process:\n#{files.map { |f| "#{f[0]} :\n > #{f[1]}" }.join("\n")}" end logger.info "Done processing all files!"