#!/usr/bin/env ruby # encoding: UTF-8 require 'utils' include Utils require 'tins/xt' include Tins::GO require 'term/ansicolor' include Term::ANSIColor require 'tempfile' def convert_ruby_to_vim_regexp(pattern) regexp = pattern.source.dup regexp.gsub!(/([^\\])\?/, '\1\\?') pattern.casefold? and regexp << '\c' regexp end def read_line(path) filename, lineno = path.source_location File.open(filename) do |file| file.lazy.each_with_index.select { |l, n| n + 1 == lineno }.first&.first end end def replace_line(path, new_line) filename, lineno = path.source_location Tempfile.open do |output| File.open(filename, 'r+') do |file| file.each_with_index do |line, n| if n + 1 == lineno output.write new_line else output.write line end end file.truncate(0) file.rewind output.rewind output.each do |line| file.write line end end end end def replace_ask(editor, path, pattern, replace, all) line = read_line path display_new_line = line.gsub( pattern.matcher, "#{on_red(line[pattern.matcher])}#{on_green(replace)}" ) loop do puts red(path) puts display_new_line if all new_line = line.gsub(pattern.matcher, replace) replace_line path, new_line break true else print "Replace? (#{bold(?y)}/n/e/a/q) " case answer = STDIN.gets.chomp when ?y, '', ?a new_line = line.gsub(pattern.matcher, replace) replace_line path, new_line break answer == ?a when ?n break false when ?q throw :quit when ?e editor.edit(path.strip) break false else next end end end end def edit_files(pattern, paths, pick: false, replace: nil) editor = Utils::Editor.new editor.edit_remote_send("/#{convert_ruby_to_vim_regexp(pattern)}") case when replace editor.wait = true all = false catch :quit do for path in paths all |= replace_ask editor, path, pattern, replace, all end end when pick if paths.size > 1 path = complete(prompt: 'Pick? ') do |p| paths.grep(/#{p}/) end else path = paths.first end editor.edit(path.strip) else editor.wait = true for path in paths STDERR.puts "Edit #{path}" editor.edit(path) end end end def usage puts <<-EOT Usage: #{File.basename($0)} [OPTS] PATTERN [PATHS] PATTERN is a pattern expression which is used to match against the content of the files. PATHS are the directory and file paths that are searched. Options are -n PATTERN only search files whose names match fuzzy PATTERN -N PATTERN only search files whose names match regexp PATTERN -s PATTERN skip lines that match fuzzy PATTERN -S PATTERN skip lines that match regexp PATTERN -A NUMBER displays NUMBER lines of context after the match -B NUMBER displays NUMBER lines of context before the match -C NUMBER displays NUMBER lines of context around the match -f just list the paths of the files that would be searched -F just consider real files when searching -l just list the paths of the files with matches -L list only the path:linenumber of the files with matches -pX interpret PATTERN argument as X=f fuzzy or X=r for regexp -c disable color output -iX use case insensitive matches with X=y (default) or not with X=n -I SUFFIX only include files with suffix SUFFIX in search -e open the matching files with edit command -E pick one file to edit -r REPLACE replace the searched match with REPLACE -b also search binary files -g use git to determine author of the line -G AUTHOR only display lines authored by AUTHOR -a CSET use only character set CSET from PATTERN -v be verbose -h display this help Version is #{File.basename($0)} #{Utils::VERSION}. EOT exit 1 end args = go 'r:p:I:A:B:C:s:S:n:N:a:i:G:cfFlLeEvbgh' args[?h] and usage pattern = ARGV.shift or usage roots = (ARGV.empty? ? [ Dir.pwd ] : ARGV).map { |f| File.expand_path(f) } Term::ANSIColor.coloring = (STDIN.tty? && ENV['TERM'] !~ /dumb/) && !args[?c] STDOUT.sync = true config = Utils::ConfigFile.new config.configure_from_paths grepper = Grepper.new( :pattern => pattern, :args => args, :roots => roots, :config => config ).search case when args[?r] then edit_files grepper.pattern, grepper.paths, replace: args[?r] when args[?E] then edit_files grepper.pattern, grepper.paths, pick: true when args[?e] then edit_files grepper.pattern, grepper.paths when args[?l] then puts grepper.paths end