#!/usr/bin/ruby -w =begin /*************************************************************************** * Copyright (C) 2008, Paul Lutus * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ =end PVERSION = "Version 1.2, 09/10/2009" module BeautifyBash # user-customizable values BeautifyBash::TabStr = " " BeautifyBash::TabSize = 3 def BeautifyBash.beautify_string(data,path) tab = 0 case_count = 0 case_stack = [] case_stack[0] = 0 in_here_doc = false defer_ext_quote = false in_ext_quote = false ext_quote_string = "" here_string = "" output = [] data.each do |record| record.chomp! stripped_record = record.strip if(in_here_doc) if(stripped_record =~ %r{#{here_string}}) in_here_doc = false end else # not in here_doc if(stripped_record =~ %r{<<-?}) here_string = stripped_record.sub(%r{.*<<-?\s*['|"]?([_|\w]+)['|"]?.*},"\\1") in_here_doc = true if (here_string.size > 0) # puts "here string: [#{here_string}]" end end if(in_here_doc) # pass unchanged output << record else # not in here_doc test_record = stripped_record.gsub(/\\./,"") # collapse multiple quotes between ' ... ' test_record = test_record.gsub(/'.*?'/,"") # collapse multiple quotes between " ... " test_record = test_record.gsub(/".*?"/,"") # remove '#' comments test_record = test_record.sub(/(\A|\s)(#.*)/,"") if(in_ext_quote) if(test_record =~ %r{#{ext_quote_string}}) # provide line after quote test_record = test_record.sub(%r{.*#{ext_quote_string}(.*)},"\\1") in_ext_quote = false end else # not in ext_quote if(test_record =~ %r{[^\\]('|")}) # apply only after this line has been processed defer_ext_quote = true ext_quote_string = test_record.sub(%r{.*(['|"]).*},"\\1") # provide line before quote test_record = test_record.sub(%r{(.*)#{ext_quote_string}.*},"\\1") end end if(in_ext_quote) # pass unchanged output << record else inc = test_record.scan(/(\s|\A|;)(case|then|do)(;|\Z|\s)/).length inc += test_record.scan(/(\{|\(|\[)/).length outc = test_record.scan(/(\s|\A|;)(esac|fi|done|elif)(;|\)|\||\Z|\s)/).length outc += test_record.scan(/(\}|\)|\])/).length if(test_record =~ /\besac\b/) outc += case_stack[case_count] case_count -= 1 end # sepcial handling for bad syntax within case ... esac if(case_count > 0) if(test_record =~ /\A[^(]*\)/) # avoid overcount outc -= 2; case_stack[case_count] += 1 end if test_record =~ /;;/ outc += 1 case_stack[case_count] -= 1 end end # an ad-hoc solution for the "else" keyword else_case = (test_record =~ /^(else)/)?-1:0 net = inc - outc tab += (net < 0)?net:0 extab = tab + else_case extab = (extab > 0)?extab:0 output << (TabStr * TabSize * extab) + stripped_record tab += (net > 0)?net:0 end # not in ext quote if(defer_ext_quote) in_ext_quote = true defer_ext_quote = false end # no deferred ext quote flag if(test_record =~ /\bcase\b/) case_count += 1 case_stack[case_count] = 0 end end # not in here doc end # each record error = (tab != 0) STDERR.puts "Error: indent/outdent mismatch: #{tab}." if error return output.join("\n") + "\n",error end # beautify_string def BeautifyBash.beautify_file(path) error = false if(path == '-') # stdin source source = STDIN.read dest,error = beautify_string(source,"stdin") print dest else # named file source source = File.read(path) dest,error = beautify_string(source,path) if(source != dest) # make a backup copy File.open(path + "~","w") { |f| f.write(source) } # overwrite the original File.open(path,"w") { |f| f.write(dest) } end end return error end # beautify_file def BeautifyBash.main error = false if(!ARGV[0]) STDERR.puts "usage: shell script filenames or \"-\" for stdin." exit 0 end ARGV.each do |path| error = (beautify_file(path))?true:error end error = (error)?1:0 exit error end # main end # module BeautifyBash # if launched as a standalone program, not invoked as a module if __FILE__ == $0 BeautifyBash.main end