#!/usr/bin/env ruby # Wrag stands for writeable-ag; a hacky script that allows you to search with ag # and edit the results in # $EDITOR. The changes are propagated to all listed files, if any. # # Usage: wrag [pattern] # # TODO Also rename changed filenames # TODO Assemble sed string to make this atomic require 'optparse' require 'tempfile' require 'shellwords' def usage puts "Usage: #{File.basename($0)} [-f|--rename-files] [-b|--backup] search_string" puts "\nAny of ag's flags will work with wrag" exit end usage if ARGV.empty? ## Parsing options ################################ options = {:rename => false, :backup => false} opt = OptionParser.new opt.on('-f', '--rename-files') { options[:rename] = true } opt.on('-b', '--backup') { options[:backup] = true } begin opt.parse # Get rid of misc args to pass the rest to ag args = ARGV - ["-f", "--rename-files", "-b", "--backup"] rescue OptionParser::InvalidOption => e puts "Invalid option: #{e}" usage end ## Perform search ################################# t = Tempfile.new('.ag-results') out = system("ag #{args.join(' ')} --group --nocolor > #{t.path}") puts "No matches" if out === false if out == nil t.unlink exit 2 end old_lines = File.open(t.path).readlines system("#{ENV['EDITOR']} #{t.path}") lines = File.open(t.path).readlines t.unlink if old_lines.length != lines.length puts "There was a line-number mismatch. Abort! Abort! Abort!" exit 3 end ## Parse edited output ############################ i = nil current_filename = nil lines.each_with_index do |line, i| line.strip! next if line.empty? if line =~ /^([0-9]+):(.+)/ # make sure it isn't actually a file filename = current_filename lineno = $1 content = $2 elsif line =~ /^(.+):([0-9]+):(.+)/ if current_file puts "Mixing and matching groups and non-groups! Aborting." exit 3 end filename = $1 lineno = $2 content = $3 elsif File.exists?(line) if File.readable?(line) current_filename = line else puts "#{line} isn't readable. Skipping." end next else puts "Malformed line: #{line}" exit 3 end next if line.eql?(old_lines[i].strip) filename = Shellwords.escape(filename) lineno = Shellwords.escape(lineno) content = content.gsub("'", "\\'") puts "#{filename}:#{lineno}:#{content}" if system("sed -i .bak '#{lineno}c\\ #{content} ' #{filename}") puts "Replaced: line #{lineno} in #{filename}" else puts "Failed to replace line #{lineno} in #{filename}" end unless options[:backup] system("rm -f #{filename}.bak") if File.exists?("#{filename}.bak") end end if i === nil puts "Didn't do nuthin'" else puts "Done!" end