Skip to content

Commit 407e97c

Browse files
committed
Add script to convert spaces to tabs
This supports different tabstops for different files (e.g. 3-spaced or 4-spaced). It should be much less invasive than running Eclipse's formatter, which also does wrapping and braces and such.
1 parent de0cac6 commit 407e97c

File tree

1 file changed

+100
-0
lines changed

1 file changed

+100
-0
lines changed

development/spacestotabs.py

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
#! /usr/bin/env python
2+
#
3+
# Script to conservatively convert initial spaces to tabs
4+
#
5+
# The script first guesses the number of spaces used (e.g. 2,3,4, or 8).
6+
# It then passes this to the unexpand utility for the actual conversion.
7+
import sys,os
8+
import optparse
9+
import subprocess
10+
11+
def space_distribution(filename):
12+
"""Reads the specified file and creates a histogram of the number of initial
13+
spaces in each line of the file.
14+
15+
Returns a dictionary mapping n to the number of lines with n initial spaces
16+
"""
17+
spaces = {}
18+
with open(filename,'r') as file:
19+
for line in file.readlines():
20+
s = 0
21+
for c in line:
22+
if c == ' ':
23+
s += 1
24+
elif c == '*':
25+
s -= 1
26+
break
27+
else:
28+
break
29+
spaces.setdefault(s,0)
30+
spaces[s] += 1
31+
return spaces
32+
33+
def max_compatible(distribution, threshold=.8, possible=[8,4,3,2]):
34+
"""Guess the most likely number of spaces per tab for a file
35+
36+
distribution: a dictionary giving the number of lines with a particular
37+
number of initial spaces
38+
threshold: Fraction of lines that must be correctly spaced
39+
possible: list of possible tabstops to consider
40+
41+
Returns the highest tabstop such that at least threshold-fraction of the
42+
lines contain initial spaces that are a multiple of that tabstop.
43+
Returns None if no possiblities meet the threshold, or if the file
44+
did not contain sufficient lines to determine the tabstop
45+
unambiguously.
46+
"""
47+
for poss in sorted(possible,reverse=True):
48+
compat = 0
49+
incompat = 0
50+
for spaces, count in distribution.items():
51+
# Only assess lines with spaces
52+
if spaces == 0:
53+
continue
54+
if spaces%poss == 0:
55+
compat += count
56+
else:
57+
incompat += count
58+
# require sufficient lines with spaces
59+
if threshold < 1 and (compat+incompat)*(1-threshold) < 2:
60+
return None
61+
if compat >= (compat+incompat)*threshold:
62+
return poss
63+
64+
def unexpand(filename, spaces):
65+
"""Calls the unexpand command to convert initial spaces into tabs
66+
"""
67+
args = ["unexpand","--first-only","-t",str(spaces),filename]
68+
print " ".join(args)
69+
bits = subprocess.check_output(args)
70+
with open(filename,'wb') as out:
71+
out.write(bits)
72+
73+
74+
if __name__ == "__main__":
75+
parser = optparse.OptionParser( usage="usage: python %prog [options] filenames..." )
76+
parser.add_option("-t","--threshold",help="fraction of lines with compatible spacing",
77+
dest="threshold",default=0.8, type='float',action='store')
78+
parser.add_option("-x","--unexpand",help="Unexpand spaces in the input files",
79+
dest="unexpand",default=False, action='store_true')
80+
(options,args) = parser.parse_args()
81+
82+
if len(args) < 1:
83+
parser.print_usage()
84+
parser.exit("Error. No files")
85+
86+
87+
for f in args:
88+
dist = space_distribution(f)
89+
# skip tabbed files
90+
if dist.keys() == [0]:
91+
continue
92+
c = max_compatible(dist,options.threshold)
93+
if c is not None:
94+
if options.unexpand:
95+
unexpand(f,c)
96+
else:
97+
print "Match\t%s\t%d\t%s"%(f,c,dist)
98+
else:
99+
print "Unclear\t%s\t?\t%s"%(f,dist)
100+

0 commit comments

Comments
 (0)