require 'tempfile' require 'readline' require 'getoptlong' require 'curses' require 'rrb/rrb' require 'rrb/completion' class String def trim split(/\s+/).find{|s| s != ""} end end module RRB module CUI USAGE = <= @str.size - 1 @cursor += 1 scroll_down if @cursor - @top >= Curses.lines end # scroll up the screen def scroll_up return if @top <= 0 @top -= 1 @cursor = @top + Curses.lines - 1 if @cursor > @top + Curses.lines - 1 end def cursor_up return if @cursor <= 0 @cursor -= 1 scroll_up if @cursor < @top end end class DiffOutputer def initialize(diff_file) @diff_file = diff_file end def output(script) # clear file content File.open(@diff_file, "wb"){} script.files.find_all{|sf| sf.new_script != nil}.each do |sf| tmp = Tempfile.new("rrbcui") begin tmp.print(sf.new_script) tmp.close system("diff -u #{sf.path} #{tmp.path} >> #{@diff_file}") ensure tmp.close(true) end end end end class FileRewriter def output(script) script.result_rewrite_file end end class UI def initialize(files, out) @script = Script.new_from_filenames(*files) @out = out end def select_one(prompt, words) CUI.select_one(prompt, words) end def select_any(prompt, words) CUI.select_any(prompt, words) end def input_str(prompt) Readline.completion_proc = proc{ [] } Readline.readline(prompt).trim end def select_region(scriptfile) Screen.new(scriptfile.input).select_region end def select_line(scriptfile) Screen.new(scriptfile.input).select_line end def classes @script.refactable_classes end def files @script.files.map{|sf| sf.path} end def check_and_execute(refactoring, *args) unless @script.__send__("#{refactoring}?", *args) STDERR.print(@script.error_message, "\n") exit end @script.__send__(refactoring, *args) @out.output(@script) end def input_new_namespace result = CUI.select_one("What namespace is your new class in: ", classes) return Namespace::Toplevel if result == nil return Namespace[result] end def methods @script.refactable_methods.map{|method| method.name} end end class RenameLocalVariable < UI def vars(method) @script.refactable_methods.find{|m| m.name == method}.local_vars.to_a end def run method = select_one("Refactored method: ", methods) old_var = select_one("Old variable: ", vars(method)) new_var = input_str("New variable: ") check_and_execute("rename_local_var", Method[method], old_var, new_var) end end class RenameInstanceVariable < UI def ivars(target) @script.refactable_classes_instance_vars.each do |classname, ivars| return ivars if classname == target.name end return [] end def run namespace = Namespace[select_one("Refactared class: ", classes)] old_var = select_one("Old variable: ", ivars(namespace)) new_var = input_str("New variable: ") check_and_execute("rename_instance_var", namespace, old_var, new_var) end end class RenameClassVariable < UI def cvars(target) @script.refactable_classes_class_vars.each do |cname, cvars| return cvars if cname == target.name end return [] end def run namespace = Namespace[select_one("Refactared class: ", classes)] old_var = select_one("Old variable: ", cvars(namespace)) new_var = input_str("New variable: ") check_and_execute("rename_class_var", namespace, old_var, new_var) end end class RenameGlobalVariable < UI def gvars @script.refactable_global_vars end def run old_var = select_one("Old variable: ", gvars) new_var = input_str("New variable: ") check_and_execute("rename_global_var", old_var, new_var) end end class RenameConstant < UI def consts @script.refactable_consts end def run old_const = select_one("Old contant: ", consts) new_const = input_str("New constant: ") check_and_execute("rename_constant", old_const, new_const) end end class RenameClass < UI def classes @scripts.refactable_classes end def run old_class = select_one("Old class: ", classes) new_class = input_str("New class: ") check_and_execute("rename_constant", old_class, new_class) end end class RenameMethodAll < UI def methods @script.refactable_methods.map{|m| m.bare_name}.sort.uniq end def run old_method = select_one("Old method: ", methods) new_method = input_str("New method: ") check_and_execute("rename_method_all", old_method, new_method) end end class ExtractMethod < UI def run path = select_one("What file?: ", files) region = select_region(@script.files.find{|sf| sf.path == path}) new_method = input_str("New method: ") check_and_execute("extract_method", path, new_method, region.begin, region.end) end end class ExtractSuperclass < UI def run path = select_one("What file new class is created?: ", files) lineno = select_line(@script.files.find{|sf| sf.path == path}) namespace = input_new_namespace new_class = input_str("New class: ") targets = select_any("Targets: ", classes).map{|cls| Namespace[cls]} check_and_execute("extract_superclass", namespace, new_class, targets, path, lineno) end end class PullupMethod < UI def run method = select_one("Old method: ", methods) new_namespace = input_new_namespace path = select_one("What file #{method} is pulled up?: ", files) lineno = select_line(@script.files.find{|sf| sf.path == path}) check_and_execute("pullup_method", Method[method], new_namespace, path, lineno) end end class PushDownMethod < UI def run method = select_one("Old method: ", methods) new_namespace = input_new_namespace path = select_one("What file #{method} is pushed down?: ", files) lineno = select_line(@script.files.find{|sf| sf.path == path}) check_and_execute("pushdown_method", Method[method], new_namespace, path, lineno) end end REFACTORING_MAP = { 'rename-local-variable' => RenameLocalVariable, 'rename-instance-variable' => RenameInstanceVariable, 'rename-class-variable' => RenameClassVariable, 'rename-global-variable' => RenameGlobalVariable, 'rename-constant' => RenameConstant, 'rename-class' => RenameClass, 'rename-method-all' => RenameMethodAll, 'rename-method' => nil, 'extract-method' => ExtractMethod, 'extract-superclass' => ExtractSuperclass, 'pullup-method' => PullupMethod, 'pushdown-method' => PushDownMethod, } REFACTORING = REFACTORING_MAP.map{|name, klass| name} # parse ARGV and do refactoring def execute print_usage if ARGV.empty? diff_file = 'output.diff' refactoring = nil overwrite_diff = false rewrite_source_files = false parser = GetoptLong.new parser.set_options( *OPTIONS ) parser.each_option do |name, arg| print_usage if name == '--help' diff_file = arg if name == '-d' refactoring = arg if name == '-r' overwrite_diff = true if name == '-o' rewrite_source_files = true if name == '-w' end if rewrite_source_files out = FileRewriter.new else if !overwrite_diff && File.exist?(diff_file) STDERR.print "ERROR: #{diff_file} exists\n" exit 1 end out = DiffOutputer.new(diff_file) end refactoring = select_one("Refactoring: ", REFACTORING) unless refactoring ui_class = REFACTORING_MAP.fetch(refactoring){ raise 'No such refactoring' } ui_class.new(ARGV, out).run end end end if $0 == __FILE__ exit if ARGV.empty? screen = RRB::CUI::Screen.new(File.read(ARGV[0])) p screen.select_region end