You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
283 lines
9.6 KiB
283 lines
9.6 KiB
2 years ago
|
#!/usr/bin/ruby -w
|
||
|
#
|
||
|
# == Synopsis
|
||
|
#
|
||
|
# Simple Ruby Formatter
|
||
|
#
|
||
|
# Created by: Stephen Becker IV
|
||
|
# Contributions: Andrew Nutter-Upham
|
||
|
# Contact: sbeckeriv@gmail.com
|
||
|
# SVN: http://svn.stephenbeckeriv.com/code/ruby_formatter/
|
||
|
#
|
||
|
# Its been done before RadRails did,
|
||
|
# http://vim.sourceforge.net/tips/tip.php?tip_id = 1368 that guy did it, but I did
|
||
|
# not look for a ruby formatter until i was done.
|
||
|
#
|
||
|
# It is called simple formatting because it is. I have the concept of 3 differnt
|
||
|
# indent actions In, Out and Both. I have mixed the concept of indenting and
|
||
|
# outdenting. Out means you have added white space and in means you remove a layer
|
||
|
# of white space.
|
||
|
#
|
||
|
# Basic logic
|
||
|
# Decrease current depth if
|
||
|
# ((the it is not a one line if unless statment
|
||
|
# (need to lookfor more oneline blocks) and it ends with end
|
||
|
# or if the } count is larger then {)
|
||
|
# or
|
||
|
# the first word is in the both list)
|
||
|
#
|
||
|
# and
|
||
|
# depth is larger then zero
|
||
|
#
|
||
|
# Increase current depth if
|
||
|
# It is not a one liner
|
||
|
# and
|
||
|
# (the word is in the out list
|
||
|
# or
|
||
|
# the word is in the both list
|
||
|
# or
|
||
|
# it looks like a start block)
|
||
|
# and
|
||
|
# temp_depth is nil (used for = comment blocks)
|
||
|
#
|
||
|
#
|
||
|
# Sure there are some regx's and a crap load of gsubs, but it still simple. Its
|
||
|
# not like its a pychecker (http://www.metaslash.com/brochure/ipc10.html)
|
||
|
#
|
||
|
# == Usage
|
||
|
#
|
||
|
# ruby [options] filelist
|
||
|
#
|
||
|
# options:
|
||
|
# -s # will change the indent to a space count of # per level
|
||
|
# by default we space with 1 tab per level
|
||
|
# -b # create a backup file
|
||
|
#
|
||
|
# examples:
|
||
|
# ruby simple_formatter.rb -s 3 -b /moo/cluck/cow.rb
|
||
|
# runs with the indent of 3 spaces,creates a backup file, and formats moo/cluck/cow.rb
|
||
|
#
|
||
|
#
|
||
|
# Tested with random files off of koders.com
|
||
|
#
|
||
|
#
|
||
|
::DEBUG_ME = false
|
||
|
require 'getoptlong'
|
||
|
require "fileutils"
|
||
|
require "pp"
|
||
|
$escape_strings = {:regex=>"EsCaPedReGex",:string=>"EsCaPeDStRiNg"}
|
||
|
begin
|
||
|
require 'rdoc/usage'
|
||
|
rescue Exception => e
|
||
|
#eat the no load of rdocs?
|
||
|
end
|
||
|
opts = GetoptLong.new(
|
||
|
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
|
||
|
[ '--spaces', '-s', GetoptLong::OPTIONAL_ARGUMENT ],
|
||
|
[ '--debug', '-d', GetoptLong::NO_ARGUMENT ],
|
||
|
[ '--backup', '-b', GetoptLong::NO_ARGUMENT ]
|
||
|
)
|
||
|
space_count = nil
|
||
|
backup = false
|
||
|
files = []
|
||
|
opts.each do | opt, arg|
|
||
|
case opt
|
||
|
when '--help'
|
||
|
begin
|
||
|
RDoc::usage
|
||
|
rescue Exception =>e
|
||
|
puts "If you want to use rdocs you need to install it"
|
||
|
exit(-1)
|
||
|
end
|
||
|
when '--spaces'
|
||
|
space_count = arg.to_i
|
||
|
when '--backup'
|
||
|
backup = true
|
||
|
when '--debug'
|
||
|
::DEBUG_ME = true
|
||
|
end
|
||
|
end
|
||
|
require "profile" if ::DEBUG_ME
|
||
|
if ARGV.length < 1
|
||
|
puts "Missing filelist argument (try --help)"
|
||
|
exit 0
|
||
|
end
|
||
|
array_loc = ARGV
|
||
|
#find if the string is a start block
|
||
|
#return true if it is
|
||
|
#rules
|
||
|
# does not include end at the end
|
||
|
# and ( { out number the } or it includes do
|
||
|
DO_RX = /\sdo\s*$/
|
||
|
def start_block?(string)
|
||
|
return true if string.gsub(/\|.*\|/, "").match(DO_RX) || (string.scan(/\{/).size > string.scan(/\}/).size)
|
||
|
false
|
||
|
end
|
||
|
#is this an end block?
|
||
|
#rules
|
||
|
#its not a one liner
|
||
|
#and it ends with end
|
||
|
#or } out number {
|
||
|
CHECK_ENDS_RX = /end$|end\s+while/
|
||
|
def check_ends?(string)
|
||
|
#check for one liners end and }
|
||
|
#string = just_the_string_please(string)
|
||
|
return true if (string.scan(/\{/).size < string.scan(/\}/).size) || string.match(CHECK_ENDS_RX)
|
||
|
false
|
||
|
end
|
||
|
IN_OUTS_RX = /^(def|class|module|begin|case|if|unless|loop|while|until|for)/
|
||
|
#look at first work does it start with one of the out works
|
||
|
def in_outs?(string)
|
||
|
string.sub!(/\(.*\)/, "")
|
||
|
return true if string.lstrip.match(IN_OUTS_RX) && string.strip.size == $1.strip.size
|
||
|
false
|
||
|
end
|
||
|
IN_BOTH_RX = /^(elsif|else|when|rescue|ensure)/
|
||
|
#look at first work does it start with one of the both words?
|
||
|
def in_both?(string)
|
||
|
return true if string.lstrip.match(IN_BOTH_RX) && string.strip.size == $1.strip.size
|
||
|
false
|
||
|
end
|
||
|
#extra formatting for the line
|
||
|
#we wrap = with spaces
|
||
|
#JUST_STRING_PLEASE_RX = /^#.*|\/.*\/|"([^"])*"|'([^'])*'/
|
||
|
LINE_CLEAN_UP_RX = /[a-zA-Z\]\'\"{\d]+=[a-zA-Z\[\'\"{\d]+/
|
||
|
def line_clean_up(x)
|
||
|
#this formatts strings and regexs remove and add in replacement works
|
||
|
|
||
|
x.gsub!(/\\\//,$escape_strings[:regex])
|
||
|
strings = x.scan(/#.*|["'\/].*?["'\/]/)
|
||
|
strings.each { | str |
|
||
|
x.sub!(str, $escape_strings[:string])
|
||
|
}
|
||
|
#lofted code from java formatter #{add in link}
|
||
|
# replace "){" with ") {"
|
||
|
x.sub!(/\)\s*\{/, ") {")
|
||
|
# replace "return(" with "return ("
|
||
|
# replace "if(" with "if ("
|
||
|
# replace "while(" with "while ("
|
||
|
# replace "switch(" with "switch (" ruby does not have a switch
|
||
|
# replace "catch(" with "catch ("
|
||
|
x.sub!(/\b(return|if|elsif|while|case|catch)\s*\(/, '\1 (')
|
||
|
# replace " ;" with ";"
|
||
|
# replace " ," with ","
|
||
|
x.gsub!(/\s+([\;\,])/, '\1')
|
||
|
#replace ",abc" with ", abc"
|
||
|
x.gsub!(/\,(\w+)/, ', \1')
|
||
|
|
||
|
x.gsub!(/(\)|"|\w)\s*([\+\-\*\/\&\|\^\%]|\&\&|\|\||[\>\<]|\>\=|\<\=|\=\=|\!\=|\<\<|\>\>|\>\>\>)\s*(?=(\w | "))/, '\1 \2 ')
|
||
|
# a space before and after AssignmentOperator
|
||
|
x.gsub!(/(\w)\s*(\+\=|\-\=|\*\=|\/\=|\&\=|\|\=|\^\=|\%\=|\<\<\=|\>\>\=|\>\>\>\=)\s*(?=(\w))/, '\1 \2 ')
|
||
|
# do not trim spaces
|
||
|
x.gsub!(/(\w)\=\s*(?=(\w|"))/, '\1 = ')
|
||
|
x.gsub!(/(\w)\s*\=(?=(\w|"))/, '\1 = ')
|
||
|
#becker format
|
||
|
#not complete list but alot of the common ones.
|
||
|
x.sub!(/(\.each|\.collect[!]*|\.map[!]*|\.delete_if|\.sort[!]*|\.each_[pair|key|value|byte|with_index|line|option]|\.reject[!]*|\.reverse_each|\.detect|\.find[_all]*|\.select|\.module_eval|\.all_waits|loop|proc|lambda|fork|at_exit)\s*\{/, '\1 {')
|
||
|
x.sub!(/def\s(\w*)?(\(.*?\))/, 'def \1\2') if x.match(/def\s+?(\w*)?\(.*?\)/)
|
||
|
x.sub!(/^for\s+(\w*)?\s+in\s+?(.*)$/, 'for \1 in \2') if x.match(/^for\s+(\w*)?\s*?in\s*?(.*)$/)
|
||
|
x.gsub!(/(\w)\=>\s*(?=(\w|"|:))/, '\1 => ')
|
||
|
x.gsub!(/(\w)\s*\=>(?=(\w|"|:))/, '\1 => ')
|
||
|
x.strip!
|
||
|
x.gsub!($escape_strings[:string]) {
|
||
|
strings.shift
|
||
|
}
|
||
|
x.gsub!($escape_strings[:regex], "\\\/")
|
||
|
return x
|
||
|
end
|
||
|
|
||
|
JUST_STRING_PLEASE_RX = /\/.*\/|"([^"])*" | '([^']) * '|#.*/
|
||
|
def just_the_string_please(org_string)
|
||
|
string = String.new(org_string)
|
||
|
#remove escaped chars
|
||
|
string.gsub!(/\\\/|\\"|\\'/, "")
|
||
|
string.gsub!(JUST_STRING_PLEASE_RX, "")
|
||
|
string = string.strip
|
||
|
string.sub!(/\b(return|if|while|case|catch)\s*\(/, '\1 (')
|
||
|
puts "clean string: #{string}" if ::DEBUG_ME
|
||
|
string
|
||
|
end
|
||
|
ONE_LINER_RX = /(unless|if).*(then).*end|(begin).*(rescue|ensure|else).*end/
|
||
|
def one_liner?(string)
|
||
|
return true if string.match(ONE_LINER_RX)
|
||
|
false
|
||
|
end
|
||
|
|
||
|
array_loc.each {|file_loc|
|
||
|
f = File.open(file_loc, "r")
|
||
|
text = f.read
|
||
|
f.close
|
||
|
if File.expand_path(file_loc) == File.expand_path($0)
|
||
|
$escape_strings = {:regex=>"EsCaPedReGex#{rand(200)}",:string=>"EsCaPeDStRiNg#{rand(200)}"}
|
||
|
end
|
||
|
new_text = ""
|
||
|
current_depth = 0
|
||
|
spaces = " " * space_count if space_count
|
||
|
here_doc_ending = nil
|
||
|
indenter = spaces || "\t"
|
||
|
temp_depth = nil
|
||
|
line_count = 1
|
||
|
|
||
|
text.split("\n").each { |x|
|
||
|
#comments
|
||
|
#The first idea was to leave them alone.
|
||
|
#after running a few test i did not like the way it looked
|
||
|
if temp_depth
|
||
|
puts "In temp_depth #{x} line ♯ #{line_count} here:#{here_doc_ending}" if ::DEBUG_ME
|
||
|
new_text << x << "\n"
|
||
|
#block comments, its going to get ugly
|
||
|
if !x.lstrip.scan(/^\=end/).empty? || (here_doc_ending && x.strip == here_doc_ending.strip)
|
||
|
#swap and set
|
||
|
puts "swap and set #{x} line # #{line_count}" if ::DEBUG_ME
|
||
|
current_depth = temp_depth
|
||
|
temp_depth = nil
|
||
|
here_doc_ending = nil
|
||
|
end
|
||
|
line_count += 1
|
||
|
next
|
||
|
end
|
||
|
#block will always be 0 depth
|
||
|
#block comments, its going to get ugly
|
||
|
unless x.lstrip.scan(/^\=begin/).empty?
|
||
|
#swap and set
|
||
|
puts "Looking for begin #{x} #{line_count}" if ::DEBUG_ME
|
||
|
temp_depth = current_depth
|
||
|
current_depth = 0
|
||
|
end
|
||
|
#here docs have same type of logic for block comments
|
||
|
unless x.lstrip.scan(/<<-/).empty?
|
||
|
#swap and set
|
||
|
here_doc_ending = x.lstrip.split(/<<-/).last.strip
|
||
|
temp_depth = current_depth
|
||
|
end
|
||
|
#whats the first word?
|
||
|
text_node = x.split.first || ""
|
||
|
just_string = just_the_string_please(x)
|
||
|
in_both = in_both?(text_node)
|
||
|
one_liner = one_liner?(just_string)
|
||
|
#check if its in end or both and that the current_depth is >0
|
||
|
#maybe i should raise if it goes negative ?
|
||
|
puts "minus one #{line_count} #{x} statement:#{(check_ends?(just_string) || in_both) && current_depth > 0} check_ends:#{check_ends?(just_string)} in_both:#{in_both} current_depth:#{ current_depth }" if ::DEBUG_ME
|
||
|
if (check_ends?(just_string) || in_both) && !one_liner
|
||
|
puts "We have a Negative depth count. This was caused around line:#{line_count}\nCheck for if( it should be if (" if current_depth == 0
|
||
|
current_depth -= 1 unless current_depth == 0
|
||
|
end
|
||
|
clean_string = line_clean_up(x)
|
||
|
current_indent = clean_string.size>0 ? indenter*current_depth : ""
|
||
|
new_text << current_indent << clean_string << "\n"
|
||
|
#we want to kick the indent out one
|
||
|
# x.match(/(unless|if).*(then).*end/): we use this match one liners for if statements not one-line blocks
|
||
|
# in_outs? returns true if the first work is in the out array
|
||
|
# in_both? does the same for the both array
|
||
|
# start_block looks for to not have an end at the end and {.count > }.count and if the word do is in there
|
||
|
# temp_depth is used when we hit the = comments should be nil unless you are in a comment
|
||
|
puts "plus one match:#{line_count} #{x} not a one liner:#{!(one_liner)} or statements:#{(in_outs?(text_node) || in_both?(text_node) || start_block?(x))} in_outs#{in_outs?(text_node)} in_both:#{ in_both?(text_node)} start_block:#{ start_block?(x)} temp_depth:#{temp_depth}" if ::DEBUG_ME
|
||
|
current_depth += 1 if ((in_outs?(text_node) || start_block?(just_string) || in_both || x.lstrip.slice(/\w*\s=\s(unless|if|case)/)) && !one_liner && !temp_depth)
|
||
|
line_count += 1
|
||
|
}
|
||
|
FileUtils.cp("#{file_loc}","#{file_loc}.bk.#{Time.now.to_s.gsub(/\s|:/,"_")}") if backup
|
||
|
f = File.open("#{file_loc}","w+")
|
||
|
f.puts new_text
|
||
|
f.close
|
||
|
puts "Done!"
|
||
|
}
|