#!/usr/bin/env ruby # mend [-v] [-d] [-i[.BAK]] [FILES...] - parallel in-place line editor # # {default} print changed file contents # -v verbose, print changed file contents with file and line numbers # -d show unified diff against original file # -i[.bak] change files inplace (keep a backup with extension if given) # # The input consists of lines (e.g. output of grep -nH): # FILE:NUMBER:CONTENT # mend will then replace the line NUMBER of the FILE by CONTENT. # Use # FILE:NUMBERd # to delete the line instead. require 'tempfile' mode = :print ext = nil while ARGV.first =~ /\A-/ arg = ARGV.shift if arg == "-d" mode = :diff elsif arg == "-v" mode = :verbose elsif arg =~ /\A-i(.*)/ mode = :inplace ext = $1.empty? ? nil : $1 else abort "Usage: mend [-d] [-i[.BAK]] [FILES...]" end end changes = {} ARGF.each { |line| if line =~ /\A([^:]*):(\d+)(:(.*)|d$)/ file, line, content = $1, $2.to_i, ($3 == "d" ? :delete : $4) changes[file] ||= {} changes[file][line] = content end } status = 0 changes.sort.each { |file, replacements| begin f = File.open(file) rescue SystemCallError => e warn "mend: can't read #{file}: #{e.message.sub(/ @ .*/, '')}" status = 2 next end begin case mode when :print out = STDOUT when :diff pipe_in, out = IO.pipe diff = Process.spawn("diff", "-u", "--label", file, "--label", file, file, "-", 0 => pipe_in) pipe_in.close when :inplace begin perm = File.stat(file).mode out = Tempfile.new(File.basename(file), File.dirname(file)) rescue SystemCallError => e warn "mend: can't create tempfile for #{file}: #{e.message}" status = 2 next end end while line = f.gets next if replacements[f.lineno] == :delete if mode == :verbose puts "#{file}:#{f.lineno}:#{replacements[f.lineno] || line}" else out.puts replacements[f.lineno] || line end end case mode when :diff out.close Process.wait(diff) when :inplace out.chmod(perm) out.close File.rename(file, file + ext) if ext File.rename(out.path, file) end rescue SystemCallError => e warn "mend: fatal error: #{e}" exit 4 ensure f.close end } exit status