1- #! /bin/bash
1+ #! /usr/ bin/env bash
22
3- find * -type f -name " *.py" -print0 | xargs -0 sed -i -e ' /^ *$/{N;s/^ *\n\( *\)\(.*\)/\1\n\1\2/}'
3+ usage () {
4+ cat << 'EOF '
5+ Usage:
6+ fix_indent <filename>
7+ Fix a single file in-place.
8+
9+ fix_indent -R <folder>
10+ Recurse through <folder>, fixing all *.py files.
11+
12+ fix_indent -R
13+ Recurse from the current directory, fixing all *.py files.
14+
15+ fix_indent -h
16+ Show this help and exit.
17+
18+ Rules enforced:
19+ - Replace every tab with 4 spaces.
20+ - Collapse multiple blank lines to a single blank line.
21+ - That blank line must be indented to match the immediately following non-blank line.
22+ - Remove blank lines at EOF (no trailing blank lines).
23+ EOF
24+ }
25+
26+ process_file () {
27+ local file=" $1 "
28+ local tmp
29+
30+ # Only operate on regular files
31+ [[ -f " $file " ]] || return 0
32+
33+ tmp=" $( mktemp -- " ${file} .fix_indent.XXXXXX" ) " || {
34+ echo " fix_indent: mktemp failed for: $file " >&2
35+ return 1
36+ }
37+
38+ # AWK filter:
39+ # - Convert tabs to 4 spaces
40+ # - Collapse blank runs to a single indented blank line (indent from next non-blank line)
41+ # - Drop trailing blanks at EOF
42+ # - Preserve CRLF if present
43+ awk '
44+ BEGIN { pending_blank = 0; use_crlf = 0 }
45+
46+ function emit(s) {
47+ if (use_crlf) print s "\r";
48+ else print s;
49+ }
50+
51+ {
52+ s = $0
53+ if (sub(/\r$/, "", s)) use_crlf = 1
54+
55+ gsub(/\t/, " ", s)
56+
57+ # whitespace-only line => treat as blank
58+ if (s ~ /^[ ]*$/) {
59+ pending_blank = 1
60+ next
61+ }
62+
63+ # if we have pending blanks, emit exactly one blank indented to this line
64+ if (pending_blank) {
65+ match(s, /^[ ]*/)
66+ emit(substr(s, RSTART, RLENGTH))
67+ pending_blank = 0
68+ }
69+
70+ emit(s)
71+ }
72+
73+ END {
74+ # If file ends with blanks, emit nothing (no blank lines at/beyond EOF)
75+ }
76+ ' " $file " > " $tmp "
77+
78+ if [[ $? -ne 0 ]]; then
79+ echo " fix_indent: awk failed for: $file " >&2
80+ rm -f -- " $tmp "
81+ return 1
82+ fi
83+
84+ # Avoid touching files that would be unchanged
85+ if cmp -s -- " $file " " $tmp " ; then
86+ rm -f -- " $tmp "
87+ return 0
88+ fi
89+
90+ # Overwrite in place (preserves mode/owner; updates mtime)
91+ if ! cat -- " $tmp " > " $file " ; then
92+ echo " fix_indent: write failed for: $file " >&2
93+ rm -f -- " $tmp "
94+ return 1
95+ fi
96+
97+ rm -f -- " $tmp "
98+ return 0
99+ }
100+
101+ main () {
102+ local recursive=0
103+ local target=" "
104+
105+ if [[ $# -eq 0 ]]; then
106+ usage >&2
107+ exit 2
108+ fi
109+
110+ case " $1 " in
111+ -h)
112+ usage
113+ exit 0
114+ ;;
115+ -R)
116+ recursive=1
117+ shift
118+ if [[ $# -ge 1 ]]; then
119+ target=" $1 "
120+ shift
121+ else
122+ target=" ."
123+ fi
124+ if [[ $# -ne 0 ]]; then
125+ usage >&2
126+ exit 2
127+ fi
128+ ;;
129+ -* )
130+ usage >&2
131+ exit 2
132+ ;;
133+ * )
134+ if [[ $# -ne 1 ]]; then
135+ usage >&2
136+ exit 2
137+ fi
138+ target=" $1 "
139+ ;;
140+ esac
141+
142+ if [[ $recursive -eq 0 ]]; then
143+ # Single file mode
144+ process_file " $target " || exit $?
145+ exit 0
146+ fi
147+
148+ # Recursive mode
149+ if [[ ! -e " $target " ]]; then
150+ echo " fix_indent: path not found: $target " >&2
151+ exit 1
152+ fi
153+
154+ local rc=0
155+ while IFS= read -r -d ' ' f; do
156+ process_file " $f " || rc=1
157+ done < <( find " $target " -type f -name ' *.py' -print0)
158+
159+ exit $rc
160+ }
161+
162+ main " $@ "
0 commit comments