Skip to content

Instantly share code, notes, and snippets.

@XrXr
Last active June 18, 2020 20:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save XrXr/7d229955e24a44b71927894682b76f19 to your computer and use it in GitHub Desktop.
Save XrXr/7d229955e24a44b71927894682b76f19 to your computer and use it in GitHub Desktop.
Expand tabs and remove changes that only expand tabs for use in https://github.com/ruby/ruby
#!/bin/env ruby
# Copyright (c) 2020 Alan Wu
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Expand tabs and remove changes that only expand tabs for use in CRuby.
# Assuming your first run of this script is on a clean checkout, when you are
# done, `git diff --ignore-space-change` should output nothing and return zero.
# In my opinion, we should expand all the tabs in the repo and reject code
# changes that use tabs instead of staying content with the status quo of
# mixing tabs spaces.
$stderr.puts "#{$0}: Run `git diff <base> | ruby #{$0}` at the root of the repo"
LineMod = Struct.new(:file, :line_number, :new_line, keyword_init: true)
diff = $stdin.read
current_file = nil
block_start = nil
line_since_block_start = nil
buffered_changes = []
change_group = Hash.new { |hash, key| hash[key] = [] }
get_current_line = -> { Integer(block_start) + line_since_block_start }
buffer_modification = ->(new_line) do
buffered_changes << LineMod.new(
file: current_file,
line_number: get_current_line.call,
new_line: new_line.delete_prefix('+').delete_prefix('-')
)
end
diff.each_line do |line|
if line_since_block_start && !line.start_with?('-')
line_since_block_start += 1
end
if line =~ %r{^diff --git a/(.*) b/(.*)}
change_group.clear
current_file = $2
line_since_block_start = nil
elsif line =~ %r{^\@\@ -(\d+),(\d+) \+(\d+),(\d+) \@\@}
change_group.clear
_old_start, _old_line_count, new_start, _new_line_count = $1, $2, $3, $4
block_start = new_start
line_since_block_start = -1 # next line is the start
elsif line.start_with?('-')
expanded = false
tab_expanded = line[1..].sub(/^\t+/) do |match|
expanded = true
' ' * 8 * match.size
end
change_group[tab_expanded] << line if expanded
elsif line.start_with?('+')
without_plus = line[1..]
if without_plus =~ /^\s+/
indent = $&
tab_expanded = indent.gsub("\t", ' ' * 8)
if indent != tab_expanded
buffer_modification.call(tab_expanded + without_plus[indent.size..])
end
end
originals = change_group[without_plus]
case originals.size
when 1
buffer_modification.call(originals.first)
when 0
# do nothing
else
$stderr.puts("#{$0}: Warning: ambiguous whitespace change at #{current_file}:#{get_current_line.call}")
end
else
change_group.clear
end
end
changed_lines = 0
buffered_changes
.each do |mod|
content = File.read(mod.file)
lines = content.each_line.to_a
line_index = mod.line_number - 1
raise unless line_index >= 0 && line_index < lines.size
if mod.new_line != lines[line_index]
changed_lines += 1
lines[line_index] = mod.new_line
$stderr.puts("#{mod.file}:#{mod.line_number} = #{mod.new_line}") if $DEBUG
end
IO.write(mod.file, lines.join(''), mode: 'w')
end.then do |lines|
$stderr.puts("#{$0}: Modified #{changed_lines} lines")
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment