From ac44c0695b46c86b185c499ce5c9705396ff327f Mon Sep 17 00:00:00 2001
From: Bob Mottram <bob@robotics.uk.to>
Date: Sun, 24 Jul 2016 14:25:12 +0100
Subject: [PATCH] Script for tidying bash scripts

---
 tidy | 159 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 159 insertions(+)
 create mode 100755 tidy

diff --git a/tidy b/tidy
new file mode 100755
index 000000000..fc8964968
--- /dev/null
+++ b/tidy
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#**************************************************************************
+#   Copyright (C) 2011, 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.             *
+#**************************************************************************
+
+import re, sys
+
+PVERSION = '1.0'
+
+class BeautifyBash:
+
+  def __init__(self):
+    self.tab_str = ' '
+    self.tab_size = 4
+
+  def read_file(self,fp):
+    with open(fp) as f:
+      return f.read()
+
+  def write_file(self,fp,data):
+    with open(fp,'w') as f:
+      f.write(data)
+
+  def beautify_string(self,data,path = ''):
+    tab = 0
+    case_stack = []
+    in_here_doc = False
+    defer_ext_quote = False
+    in_ext_quote = False
+    ext_quote_string = ''
+    here_string = ''
+    output = []
+    line = 1
+    for record in re.split('\n',data):
+      record = record.rstrip()
+      stripped_record = record.strip()
+      
+      # collapse multiple quotes between ' ... '
+      test_record = re.sub(r'\'.*?\'','',stripped_record)
+      # collapse multiple quotes between " ... "
+      test_record = re.sub(r'".*?"','',test_record)
+      # collapse multiple quotes between ` ... `
+      test_record = re.sub(r'`.*?`','',test_record)
+      # collapse multiple quotes between \` ... ' (weird case)
+      test_record = re.sub(r'\\`.*?\'','',test_record)
+      # strip out any escaped single characters
+      test_record = re.sub(r'\\.','',test_record)
+      # remove '#' comments
+      test_record = re.sub(r'(\A|\s)(#.*)','',test_record,1)
+      if(not in_here_doc):
+        if(re.search('<<-?',test_record)):
+          here_string = re.sub('.*<<-?\s*[\'|"]?([_|\w]+)[\'|"]?.*','\\1',stripped_record,1)
+          in_here_doc = (len(here_string) > 0)
+      if(in_here_doc): # pass on with no changes
+        output.append(record)
+        # now test for here-doc termination string
+        if(re.search(here_string,test_record) and not re.search('<<',test_record)):
+          in_here_doc = False
+      else: # not in here doc
+        if(in_ext_quote):
+          if(re.search(ext_quote_string,test_record)):
+            # provide line after quotes
+            test_record = re.sub('.*%s(.*)' % ext_quote_string,'\\1',test_record,1)
+            in_ext_quote = False
+        else: # not in ext quote
+          if(re.search(r'(\A|\s)(\'|")',test_record)):
+            # apply only after this line has been processed
+            defer_ext_quote = True
+            ext_quote_string = re.sub('.*([\'"]).*','\\1',test_record,1)
+            # provide line before quote
+            test_record = re.sub('(.*)%s.*' % ext_quote_string,'\\1',test_record,1)
+        if(in_ext_quote):
+          # pass on unchanged
+          output.append(record)
+        else: # not in ext quote
+          inc = len(re.findall('(\s|\A|;)(case|then|do)(;|\Z|\s)',test_record))
+          inc += len(re.findall('(\{|\(|\[)',test_record))
+          outc = len(re.findall('(\s|\A|;)(esac|fi|done|elif)(;|\)|\||\Z|\s)',test_record))
+          outc += len(re.findall('(\}|\)|\])',test_record))
+          if(re.search(r'\besac\b',test_record)):
+            if(len(case_stack) == 0):
+              sys.stderr.write(
+                'File %s: error: "esac" before "case" in line %d.\n' % (path,line)
+              )
+            else:
+              outc += case_stack.pop()
+          # sepcial handling for bad syntax within case ... esac
+          if(len(case_stack) > 0):
+            if(re.search('\A[^(]*\)',test_record)):
+              # avoid overcount
+              outc -= 2
+              case_stack[-1] += 1
+            if(re.search(';;',test_record)):
+              outc += 1
+              case_stack[-1] -= 1
+          # an ad-hoc solution for the "else" keyword
+          else_case = (0,-1)[re.search('^(else)',test_record) != None]
+          net = inc - outc
+          tab += min(net,0)
+          extab = tab + else_case
+          extab = max(0,extab)
+          output.append((self.tab_str * self.tab_size * extab) + stripped_record)
+          tab += max(net,0)
+        if(defer_ext_quote):
+          in_ext_quote = True
+          defer_ext_quote = False
+        if(re.search(r'\bcase\b',test_record)):
+          case_stack.append(0)
+      line += 1
+    error = (tab != 0)
+    if(error):
+      sys.stderr.write('File %s: error: indent/outdent mismatch: %d.\n' % (path,tab))
+    return '\n'.join(output), error
+
+  def beautify_file(self,path):
+    error = False
+    if(path == '-'):
+      data = sys.stdin.read()
+      result,error = self.beautify_string(data,'(stdin)')
+      sys.stdout.write(result)
+    else: # named file
+      data = self.read_file(path)
+      result,error = self.beautify_string(data,path)
+      if(data != result):
+        # make a backup copy
+        self.write_file(path + '~',data)
+        self.write_file(path,result)
+    return error
+
+  def main(self):
+    error = False
+    sys.argv.pop(0)
+    if(len(sys.argv) < 1):
+      sys.stderr.write('usage: shell script filenames or \"-\" for stdin.\n')
+    else:
+      for path in sys.argv:
+        error |= self.beautify_file(path)
+    sys.exit((0,1)[error])
+
+# if not called as a module
+if(__name__ == '__main__'):
+  BeautifyBash().main()
-- 
GitLab