require 'git/gsub/version' require 'active_support/inflector' require 'optparse' require 'shellwords' require 'English' require "open3" module Git module Gsub def self.run(argv) opts = OptionParser.new self.class.module_eval do define_method(:usage) do |msg = nil| puts opts.to_s puts "error: #{msg}" if msg exit 1 end end options = {} opts.on('-v', '--version', 'Print version') do |_v| options[:version] = true end opts.on('--snake', 'Experimental: substitute snake-cased expressions') do |_v| options[:snake] = true end opts.on('--camel', 'Experimental: substitute camel-cased expressions') do |_v| options[:camel] = true end opts.on('--kebab', 'Experimental: substitute kebab-cased expressions') do |_v| options[:kebab] = true end opts.on('--rename', 'Rename files along with substitution') do |_v| options[:rename] = true end opts.on('--dry-run', 'Print commands to be run') do |_v| options[:dry] = true end opts.banner = 'Usage: git gsub [options] FROM TO [PATHS]' begin args = opts.parse(argv) rescue OptionParser::InvalidOption => e usage e.message end if options[:version] version else from, to, *paths = args usage 'No FROM given' unless from usage 'No TO given' unless to Commands::Gsub.new(from, to, paths, options).run Commands::Rename.new(from, to, paths, options).run if options[:rename] end end def self.version puts Git::Gsub::VERSION end module Commands class Command attr_accessor :from, :to, :paths, :options def initialize(from, to, paths = [], options = {}) @from = from @to = to @paths = paths @options = options end def run_commands(commands) if options[:dry] commands.each { |args| puts args.join(' ') } else commands.each { |args| Open3.capture3(*args) } end end def args args = [] args << [from, to] args << [from.camelcase, to.camelcase] if options[:camel] args << [from.underscore, to.underscore] if options[:snake] args << [from.underscore.dasherize, to.underscore.dasherize] if options[:kebab] args.compact end end class Gsub < Command def run commands = args.map { |from, to| build_command(from, to, paths) }.compact run_commands commands end def build_command(from, to, paths = [], _options = {}) from, to, *paths = [from, to, *paths].map { |s| Shellwords.escape s } [from, to].each { |s| s.gsub! '@', '\@' } target_files = `git grep -l #{from} #{paths.join ' '}`.each_line.map(&:chomp) return if target_files.empty? ['perl', '-pi', '-e', "s{#{from}}{#{to}}g", *target_files] end end class Rename < Command def run commands = args.map { |from, to| build_command(from, to, paths) } run_commands commands end def build_command(from, to, paths = [], _options = {}) from, to, *paths = [from, to, *paths].map { |s| Shellwords.escape s } %[for f in $(git ls-files #{paths.join ' '} | grep #{from}); do mkdir -p $(dirname $(echo $f | sed 's/#{from}/#{to}/g')); git mv $f $(echo $f | sed 's/#{from}/#{to}/g') 2>/dev/null; done] end end end end end