Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9f78d96
Added command line option to set tab_size, and help
shri314 Jan 12, 2017
fbc9de3
Added a test
shri314 Jan 12, 2017
5ac785e
Added another test
shri314 Jan 12, 2017
ae68c1f
Removed unintended | char from within regex
shri314 Jan 12, 2017
5ea3431
Added test for elif
shri314 Jan 12, 2017
53f36d9
Reorg tests and testcases
shri314 Jan 13, 2017
fd1de93
Fix for here docs
shri314 Jan 13, 2017
04b3c71
Added more tests
shri314 Jan 14, 2017
2ef7a8e
Fixed test error reporting
shri314 Jan 14, 2017
dacd065
Added tests for quotes
shri314 Jan 14, 2017
e686d77
Remove archaic folding at 80 chars for better readability
shri314 Jan 15, 2017
94989b3
Pretty printing of tests
shri314 Jan 15, 2017
9e8d5a4
Added trailing spaces in test cases
shri314 Jan 15, 2017
07fe4f0
Added a few test w.r.to quotes
shri314 Jan 15, 2017
b4c1a8a
1. Indentation fixes to support quoted strings
shri314 Jan 15, 2017
5dea11b
Wrap lines ending with \, ||, &&, |
shri314 Jan 16, 2017
cf94559
Trim intervening lines with containing only spaces
shri314 Jan 16, 2017
810bb9b
Removed a few issues highlighted by pep8 (pep8 --max-line-length=150)
shri314 Jan 21, 2017
d25d45c
Experimenting with travis
Jun 9, 2017
132cced
Testing travis
Jun 9, 2017
bce10e0
Testing travis
Jun 9, 2017
f823917
Test using perl tap
cheako Jul 10, 2017
3174d7e
Show build status
cheako Jul 10, 2017
94d3080
Remove old testcases
cheako Jul 10, 2017
ffa12b3
Install differences
cheako Jul 10, 2017
7c3f30e
Remove misc files
cheako Jul 10, 2017
9cc1646
Add an installer
cheako Jul 10, 2017
dbd8938
Rename tests
cheako Jul 10, 2017
4092d12
Wrong file name
cheako Jul 10, 2017
ead2da0
Tests for future enhancements
cheako Jul 10, 2017
3b168c3
No need to test perl tainted
cheako Jul 10, 2017
1e5b796
Cleanup beautify_bash.py exec
cheako Jul 10, 2017
e1a3a1b
Add manpage
cheako Jul 10, 2017
d3989d8
Add meta files
cheako Jul 10, 2017
3b20e7e
Merge pull request #1 from cheako/master
shri314 Jul 11, 2017
c6d1d21
Updated travis status page in README
shri314 Jul 12, 2017
d2133c5
Added basic circleci configuration
shri314 Aug 19, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: 2

jobs:
build:
docker:
- image: circleci/python
steps:
- checkout
- run:
name: Build and install
command: 'sudo make install'
- run:
name: Test dependencies
command: 'sudo apt-get update; sudo apt-get install libtest-differences-perl'
- run:
name: Run tests
command: 'make test'
11 changes: 11 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
language: python

install: true

addons:
apt:
packages:
- libtest-differences-perl

script:
- make test
2 changes: 2 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Paul Lutus
Shriram V
6 changes: 6 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Version 1.1 07/10/2017. Rekindled.

* pep8
* testcases and todo items

Version 1.0 04/14/2011. Initial Public Release.
340 changes: 340 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
all:

install:
install -D beautify_bash.py $(DESTDIR)/usr/bin/beautify_bash

test:
prove -f
prove -v -f t_todo || true

.PHONY: all install test
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ beautify_bash
Code formatter / beautifier for bash written in python by
Paul Lutus (a remake of previous version in Ruby).

[![Build Status](https://travis-ci.org/shri314/beautify_bash.svg)](https://travis-ci.org/shri314/beautify_bash)

For further details please see the following blog record
http://arachnoid.com/python/beautify_bash_program.html

Expand Down
36 changes: 36 additions & 0 deletions beautify_bash.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
.\" Hey, EMACS: -*- nroff -*-
.\" (C) Copyright 2017 Mike Mestnik <cheako+apt_repo@mikemestnik.net>,
.\"
.\" First parameter, NAME, should be all caps
.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection
.\" other parameters are allowed: see man(7), man(1)
.TH Beautify-bash 1 "July 9 2017"
.\" Please adjust this date whenever revising the manpage.
.\"
.\" Some roff macros, for reference:
.\" .nh disable hyphenation
.\" .hy enable hyphenation
.\" .ad l left justify
.\" .ad b justify to both left and right margins
.\" .nf disable filling
.\" .fi enable filling
.\" .br insert line break
.\" .sp <n> insert n+1 empty lines
.\" for manpage-specific macros, see man(7)
.SH NAME
beautify-bash \- A beautifier for Bash shell scripts written in Python
.SH SYNOPSIS
.B beautify-bash
.RI " files" ...
.SH DESCRIPTION
This manual page documents briefly the
.B beautify-bash
command.
.PP
.\" TeX users may be more comfortable with the \fB<whatever>\fP and
.\" \fI<whatever>\fP escape sequences to invode bold face and italics,
.\" respectively.
\fBbeautify-bash\fP is a program that indets and stuff Bash shell scripts.
.SH OPTIONS
These programs follow the usual dashe (`-') means use stdin/out.
Files are modified and backups created with tilde (`~') suffix.
187 changes: 102 additions & 85 deletions beautify_bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

import re
import sys
import getopt

PVERSION = '1.0'

Expand All @@ -42,103 +43,105 @@ def write_file(self, fp, data):

def beautify_string(self, data, path=''):
tab = 0
wrap_tab = ""
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()

test_record = stripped_record = record.strip()
# 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)
# collapse multiple quotes between ' ... '
test_record = re.sub(r'\'.*?\'', '', stripped_record)
test_record = re.sub(r'\'.*?\'', '', test_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)

if(here_string): # pass on with no changes
# 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):
if(record == here_string):
here_string = ''
output.append(record)
continue

if(ext_quote_string):
if(re.search(ext_quote_string, test_record)):
# provide line after quotes
test_record = re.sub('.*%s(.*)' % ext_quote_string, '\\1', test_record, 1)
ext_quote_string = ''
# pass on left side unchanged
output.append(record.rstrip())
else:
# 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)
continue

if(re.search(r'[\'"]', test_record)):
# apply only after this line has been processed
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)
stripped_record = record.lstrip()

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) is not None]

net = inc - outc
tab += min(net, 0)
extab = tab + else_case
extab = max(0, extab)
tab += max(net, 0)

if(re.search(r'^\s*$', stripped_record) and wrap_tab == ""):
output.append("")
else:
output.append((self.tab_str * self.tab_size * extab) + wrap_tab + stripped_record)

if(re.search(r'\\\s*$', test_record)
or re.search(r'[&][&]\s*$', test_record)
or re.search(r'[|]\s*$', test_record)
):
wrap_tab = self.tab_str * self.tab_size
else:
wrap_tab = ""

if(re.search(r'\bcase\b', test_record)):
case_stack.append(0)
if(re.search('<<-?', test_record)):
here_string = re.sub('.*<<-?\s*[\'"]?([\w]+)[\'"]?.*', '\\1', record.strip(), 1)

line += 1
error = (tab != 0)
if(error):
sys.stderr.write(
'File %s: error: indent/outdent mismatch: %d.\n' % (path, tab))
sys.stderr.write('File %s: error: indent/outdent mismatch: %d.\n' % (path, tab))
return '\n'.join(output), error

def beautify_file(self, path):
Expand All @@ -156,15 +159,29 @@ def beautify_file(self, path):
self.write_file(path, result)
return error

def usage_ex(self, err_val):
sys.stderr.write('Usage: ' + sys.argv[0] + ' [-h|-t <n>] [<file-name>|-]...\n')
sys.exit(err_val)

def main(self):
try:
opts, paths = getopt.getopt(sys.argv[1:], "ht:", "help")
except getopt.GetoptError as err:
print(err)
self.usage_ex(2)

for o, v in opts:
if o == '-t':
self.tab_size = int(v)
elif o in ('-h', '--help'):
self.usage_ex(0)

if(len(paths) < 1):
paths.append('-')

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)
for path in paths:
error |= self.beautify_file(path)
sys.exit((0, 1)[error])

# if not called as a module
Expand Down
Loading