Skip to content

Commit 8d43405

Browse files
committed
first commit
0 parents  commit 8d43405

File tree

7 files changed

+506
-0
lines changed

7 files changed

+506
-0
lines changed

cssreflow.py

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
#!/usr/bin/env python
2+
3+
import getopt
4+
import re
5+
import sys
6+
7+
__author__ = "Erik Smartt"
8+
__copyright__ = "Copyright 2010, Erik Smartt"
9+
__license__ = "MIT"
10+
__version__ = "0.1"
11+
__usage__ = """Normal usage:
12+
cssreflow.py [INPUT_FILE_NAME] > [OUTPUT_FILE]
13+
14+
Options:
15+
--alphaprops Alphabetizes the CSS selectors (potentially altering inheritence rules.) [Default: off]
16+
--alphaselectors Alphabetizes the CSS properties. [Default: off]
17+
--clean Shortcut for 2-space indent with alpha-props.
18+
--erik Shortcut for flat with alpha-props and selectors.
19+
--flat Puts declarations/rules on a single line.
20+
--help Prints this Help message.
21+
--indent Indent properties. [Default: on]
22+
--indentsize Set the indent size. [Default: 2]
23+
--scan Shortcut for silent, with warnings and errors displayed.
24+
--silent Does not output the formatted text.
25+
--spaces Use spaces to indent. [Default: on]
26+
--tabs Use tabs to indent. [Default: off]
27+
--version Print the version number of cssreflow being used.
28+
"""
29+
30+
def get_config(options={}):
31+
# Setup default config (alter with command-line flags)
32+
config = {}
33+
34+
config['before_selector'] = ''
35+
config['after_selector'] = ' '
36+
37+
config['before_open_brace'] = ''
38+
config['after_open_brace'] = ''
39+
40+
config['before_property'] = ''
41+
config['after_property'] = ''
42+
43+
config['before_colon'] = ''
44+
config['after_colon'] = ' '
45+
46+
config['before_value'] = ''
47+
config['after_value'] = ''
48+
49+
config['before_semicolon'] = ''
50+
config['after_semicolon'] = ' '
51+
52+
config['before_close_brace'] = '\n'
53+
config['after_close_brace'] = '\n'
54+
55+
config['alphabetize_selectors'] = False
56+
config['alphabetize_properties'] = False
57+
58+
config['indent'] = True
59+
config['indent_size'] = 2
60+
config['indent_with'] = ' '
61+
62+
config['make_properties_lower_case'] = True
63+
config['make_values_lower_case'] = True
64+
65+
# Add/change settings based on values in 'options'
66+
for k in options:
67+
config[k] = options[k]
68+
69+
return config
70+
71+
72+
def parse_file(file_name, config):
73+
fp = open(file_name, 'r')
74+
text = fp.read()
75+
fp.close()
76+
77+
#print "Parsing: %s" % (file_name)
78+
79+
re_statement = re.compile("^\s*([\#\.\ a-zA-Z0-9\-\_\@]+)\s*{(.*?)}", re.M|re.S)
80+
re_property = re.compile("([-\w]+)\s*:\s*([\#\%\-\.\ a-zA-Z0-9]+)\s*;", re.M|re.S)
81+
82+
# Might be able to clean this up using Python 3.1's sorted dictionary someday...
83+
data = {'__keys__':[], '__errors__':[], '__warnings__':[]}
84+
85+
for mo in re_statement.finditer(text):
86+
if mo:
87+
selector = mo.group(1).strip()
88+
property_string = mo.group(2)
89+
property_set = []
90+
91+
if data.has_key(selector):
92+
data['__warnings__'].append("Found duplicate selector '%s'" % (selector))
93+
else:
94+
data[selector] = {'__keys__':[]}
95+
data['__keys__'].append(selector)
96+
97+
for po in re_property.finditer(property_string):
98+
if po:
99+
attr = po.group(1).strip()
100+
val = po.group(2).strip()
101+
102+
if data[selector].has_key(attr):
103+
data['__errors__'].append("Found duplicate attribute '%s' on selector '%s'" % (attr, selector))
104+
105+
data[selector]['__keys__'].append(attr)
106+
data[selector][attr] = val
107+
108+
if config['alphabetize_properties']:
109+
data[selector]['__keys__'].sort()
110+
111+
if config['alphabetize_selectors']:
112+
data['__keys__'].sort()
113+
114+
return data
115+
116+
117+
def structure_to_string(data, config):
118+
"""
119+
@param data is a dictionary of everything
120+
@param keys is a sorted list of style statements
121+
"""
122+
output = []
123+
124+
for statement in data['__keys__']:
125+
output.append(config['before_selector'])
126+
output.append(statement)
127+
output.append(config['after_selector'])
128+
output.append(config['before_open_brace'])
129+
output.append('{')
130+
output.append(config['after_open_brace'])
131+
132+
for attr in data[statement]['__keys__']:
133+
if config['indent']:
134+
output.append('\n');
135+
for i in range(0, config['indent_size']):
136+
output.append(config['indent_with'])
137+
138+
output.append(config['before_property'])
139+
output.append(attr)
140+
output.append(config['after_property'])
141+
142+
output.append(config['before_colon'])
143+
output.append(':')
144+
output.append(config['after_colon'])
145+
146+
output.append(config['before_value'])
147+
output.append(data[statement][attr])
148+
output.append(config['after_value'])
149+
150+
output.append(config['before_semicolon'])
151+
output.append(';')
152+
output.append(config['after_semicolon'])
153+
154+
# Replace the last 'after_semicolon' with whatever should be 'before_close_brace'.
155+
output[-1] = config['before_close_brace']
156+
output.append('}')
157+
output.append(config['after_close_brace'])
158+
159+
return ''.join(output)
160+
161+
162+
def run(mode_list):
163+
try:
164+
opts, args = getopt.getopt(mode_list[2:], "", ["alphaprops", "alphaselectors", "clean", "erik", "flat", "help", "indent", "indentsize=", "errors", "scan", "silent", "spaces", "tabs", "warn", "version"])
165+
flatopts = map(lambda t: t[0], opts)
166+
167+
except getopt.GetoptError, err:
168+
print str(err)
169+
print __usage__
170+
sys.exit(2)
171+
172+
173+
# First handle commands that exit
174+
if "--help" == mode_list[1]:
175+
print __usage__
176+
sys.exit(2)
177+
178+
if "--version" == mode_list[1]:
179+
print __version__
180+
sys.exit(2)
181+
182+
try:
183+
infile = mode_list[1]
184+
except IndexError:
185+
print "\n--------------------------\nERROR: Missing input file.\n--------------------------\n"
186+
print __usage__
187+
sys.exit(2)
188+
189+
config = get_config()
190+
191+
if "--alphaprops" in flatopts:
192+
config['alphabetize_properties'] = True
193+
194+
if "--alphaselectors" in flatopts:
195+
config['alphabetize_selectors'] = True
196+
197+
if "--clean" in flatopts:
198+
config['after_semicolon'] = ''
199+
config['alphabetize_selectors'] = False
200+
config['alphabetize_properties'] = True
201+
config['indent_size'] = 2
202+
config['indent_with'] = ' '
203+
204+
if "--erik" in flatopts:
205+
config['alphabetize_selectors'] = True
206+
config['alphabetize_properties'] = True
207+
config['indent'] = False
208+
config['before_close_brace'] = ''
209+
210+
if "--flat" in flatopts:
211+
config['indent'] = False
212+
config['before_close_brace'] = ''
213+
214+
if "--indent" in flatopts:
215+
config['indent'] = True
216+
217+
for o, a in opts:
218+
if o in ["--indentsize"]:
219+
config['indent_size'] = int(a)
220+
221+
if "--spaces" in flatopts:
222+
config['indent_with'] = ' '
223+
224+
if "--tabs" in flatopts:
225+
config['indent_with'] = '\t'
226+
227+
228+
if "--scan" in flatopts:
229+
flatopts.append('--silent')
230+
flatopts.append('--warn')
231+
flatopts.append('--errors')
232+
233+
data = parse_file(infile, config)
234+
235+
# Handle flags for optional output
236+
if "--warn" in flatopts:
237+
if data['__warnings__']:
238+
for msg in data['__warnings']:
239+
print 'WARNING: %s' % (msg)
240+
241+
if "--errors" in flatopts:
242+
if data['__errors__']:
243+
for msg in data['__errors__']:
244+
print 'ERROR: %s' % (msg)
245+
246+
if "--silent" in flatopts:
247+
pass
248+
else:
249+
return structure_to_string(data, config)
250+
251+
252+
253+
# ---------------------------------
254+
# MAIN
255+
# ---------------------------------
256+
if __name__ == "__main__":
257+
print run(sys.argv)
258+
259+
sys.exit(2)

readme.markdown

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
CSSReflow
3+
=========
4+
5+
CSSReflow is a CSS formatter/beautifier. It was written to clean-up existing CSS files (useful when inheriting someone else's markup.) It's written in Python, and expected to be run from the command-line (for now.)
6+
7+
8+
Usage
9+
-----
10+
11+
cssreflow.py INPUT-FILE [OPTIONS] > OUTPUT-FILE
12+
13+
Options:
14+
15+
--alphaprops Alphabetizes the CSS selectors (potentially altering inheritence rules.) [Default: off]
16+
--alphaselectors Alphabetizes the CSS properties. [Default: off]
17+
--clean Shortcut for 2-space indent with alpha-props.
18+
--erik Shortcut for flat with alpha-props and selectors.
19+
--flat Puts declarations/rules on a single line.
20+
--help Prints this Help message.
21+
--indent Indent properties. [Default: on]
22+
--indentsize Set the indent size. [Default: 2]
23+
--scan Shortcut for silent, with warnings and errors displayed.
24+
--silent Does not output the formatted text.
25+
--spaces Use spaces to indent. [Default: on]
26+
--tabs Use tabs to indent. [Default: off]
27+
--version Print the version number of cssreflow being used.
28+

runtests.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python
2+
3+
import hashlib
4+
import os
5+
import sys
6+
7+
__author__ = "Erik Smartt"
8+
__copyright__ = "Copyright 2010, Erik Smartt"
9+
__license__ = "MIT"
10+
11+
import cssreflow
12+
13+
14+
def next_test_file(dirname):
15+
for root, dirs, files in os.walk(dirname):
16+
for filename in files:
17+
if filename.endswith('-in.css'):
18+
yield (dirname, filename)
19+
20+
21+
def validate(inpath, mode, content):
22+
outpath = inpath.replace('-in', '-%s' % mode)
23+
24+
fp = open(outpath, 'r')
25+
expected_output = fp.read()
26+
fp.close()
27+
28+
# Hopefully this doesn't come back to bite me, but I'm using a hash of the
29+
# output to compare it with the known TEST PASS state. The odds of a false
30+
# positive are pretty slim...
31+
if (hashlib.sha224(content).hexdigest() == hashlib.sha224(expected_output).hexdigest()):
32+
print "PASS [%s --%s]" % (inpath, mode)
33+
else:
34+
print "FAIL [%s --%s]" % (inpath, mode)
35+
36+
# Write the expected output file for local diffing
37+
fout = open('%s_expected' % outpath, 'w')
38+
fout.write(content)
39+
fout.close()
40+
41+
42+
43+
# --------------------------------------------------
44+
# MAIN
45+
# --------------------------------------------------
46+
if __name__ == "__main__":
47+
print "Testing..."
48+
49+
for directory, infile in next_test_file("testfiles"):
50+
inpath = "%s/%s" % (directory, infile)
51+
print "----\ntesting %s" % (inpath)
52+
53+
for testmode in ['clean', 'flat', 'erik']:
54+
validate(inpath, testmode, cssreflow.run([None, inpath, '--%s' % (testmode)]))
55+
56+
print "Done."
57+
58+
sys.exit(2)
59+

0 commit comments

Comments
 (0)