Merge branch 'python-cssbeautifier'

This commit is contained in:
Daniel Stockman 2013-10-17 11:52:24 -07:00
commit 42fbfe36e5
7 changed files with 470 additions and 29 deletions

View File

@ -38,26 +38,35 @@
css_beautify(source_text);
css_beautify(source_text, options);
The options are:
indent_size (default 4) indentation size,
indent_char (default space) character to indent with,
The options are (default in brackets):
indent_size (4) indentation size,
indent_char (space) character to indent with,
selector_separator_newline (true) - separate selectors with newline or
not (e.g. "a,\nbr" or "a, br")
end_with_newline (false) - end with a newline
e.g
css_beautify(css_source_text, {
'indent_size': 1,
'indent_char': '\t'
'indent_char': '\t',
'selector_separator': ' ',
'end_with_newline': false,
});
*/
// http://www.w3.org/TR/CSS21/syndata.html#tokenization
// http://www.w3.org/TR/css3-syntax/
(function() {
(function () {
function css_beautify(source_text, options) {
options = options || {};
var indentSize = options.indent_size || 4;
var indentCharacter = options.indent_char || ' ';
var selectorSeparatorNewline = true;
if (options.selector_separator_newline != undefined)
selectorSeparatorNewline = options.selector_separator_newline;
var endWithNewline = options.end_with_newline || false;
// compatibility
if (typeof indentSize === "string") {
@ -81,13 +90,13 @@
return source_text.charAt(pos + 1);
}
function eatString(comma) {
function eatString(endChar) {
var start = pos;
while (next()) {
if (ch === "\\") {
next();
next();
} else if (ch === comma) {
} else if (ch === endChar) {
break;
} else if (ch === "\n") {
break;
@ -125,7 +134,8 @@
function lookBack(str) {
return source_text.substring(pos - str.length, pos).toLowerCase() === str;
return source_text.substring(pos - str.length, pos).toLowerCase() ===
str;
}
// printer
@ -144,20 +154,24 @@
}
var print = {};
print["{"] = function(ch) {
print["{"] = function (ch) {
print.singleSpace();
output.push(ch);
print.newLine();
};
print["}"] = function(ch) {
print["}"] = function (ch) {
print.newLine();
output.push(ch);
print.newLine();
};
print.newLine = function(keepWhitespace) {
print._lastCharWhitespace = function () {
return whiteRe.test(output[output.length - 1]);
}
print.newLine = function (keepWhitespace) {
if (!keepWhitespace) {
while (whiteRe.test(output[output.length - 1])) {
while (print._lastCharWhitespace()) {
output.pop();
}
}
@ -169,8 +183,8 @@
output.push(indentString);
}
};
print.singleSpace = function() {
if (output.length && !whiteRe.test(output[output.length - 1])) {
print.singleSpace = function () {
if (output.length && !print._lastCharWhitespace()) {
output.push(' ');
}
};
@ -180,27 +194,40 @@
}
/*_____________________--------------------_____________________*/
var insideRule = false;
while (true) {
var isAfterSpace = skipWhitespace();
if (!ch) {
break;
}
if (ch === '{') {
indent();
print["{"](ch);
} else if (ch === '/' && peek() === '*') { // comment
print.newLine();
output.push(eatComment(), "\n", indentString);
var header = lookBack("")
if (header) {
print.newLine();
}
} else if (ch === '{') {
eatWhitespace();
if (peek() == '}') {
next();
output.push(" {}");
} else {
indent();
print["{"](ch);
}
} else if (ch === '}') {
outdent();
print["}"](ch);
insideRule = false;
} else if (ch === ":") {
eatWhitespace();
output.push(ch, " ");
insideRule = true;
} else if (ch === '"' || ch === '\'') {
output.push(eatString(ch));
} else if (ch === ';') {
output.push(ch, '\n', indentString);
} else if (ch === '/' && peek() === '*') { // comment
print.newLine();
output.push(eatComment(), "\n", indentString);
} else if (ch === '(') { // may be a url
if (lookBack("url")) {
output.push(ch);
@ -224,7 +251,11 @@
} else if (ch === ',') {
eatWhitespace();
output.push(ch);
print.singleSpace();
if (!insideRule && selectorSeparatorNewline) {
print.newLine();
} else {
print.singleSpace();
}
} else if (ch === ']') {
output.push(ch);
} else if (ch === '[' || ch === '=') { // no whitespace before or after
@ -241,12 +272,21 @@
var sweetCode = output.join('').replace(/[\n ]+$/, '');
// establish end_with_newline
var should = endWithNewline;
var actually = /\n$/.test(sweetCode)
if (should && !actually)
sweetCode += "\n";
else if (!should && actually)
sweetCode = sweetCode.slice(0, -1);
return sweetCode;
}
if (typeof define === "function") {
// Add support for require.js
define(function(require, exports, module) {
define(function (require, exports, module) {
exports.css_beautify = css_beautify;
});
} else if (typeof exports !== "undefined") {
@ -261,4 +301,4 @@
global.css_beautify = css_beautify;
}
}());
}());

View File

@ -1,7 +1,7 @@
/*global js_beautify: true */
/*jshint */
function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify)
function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_beautify)
{
var opts = {
@ -12,7 +12,9 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify)
keep_array_indentation: false,
brace_style: 'collapse',
space_before_conditional: true,
break_chained_methods: false
break_chained_methods: false,
selector_separator: '\n',
end_with_newline: true
};
function test_js_beautifier(input)
@ -25,6 +27,11 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify)
return html_beautify(input, opts);
}
function test_css_beautifier(input)
{
return css_beautify(input, opts);
}
var sanitytest;
// test the input on beautifier with the current flag settings
@ -103,6 +110,16 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify)
}
}
// test css
function btc(input, expectation)
{
var wrapped_input, wrapped_expectation;
expectation = expectation || input;
sanitytest.test_function(test_css_beautifier, 'css_beautify');
test_fragment(input, expectation);
}
// test the input on beautifier with the current flag settings,
// but dont't
function bt_braces(input, expectation)
@ -1703,7 +1720,38 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify)
'<div>Some test text that should wrap_inside_this\n' +
' section here.</div>');
// css beautifier
opts.indent_size = 1;
opts.indent_char = '\t';
opts.selector_separator_newline = true;
opts.end_with_newline = true;
// test basic css beautifier
btc('', '\n');
btc(".tabs{}", ".tabs {}\n");
btc(".tabs{color:red;}", ".tabs {\n\tcolor: red;\n}\n");
btc(".tabs{color:rgb(255, 255, 0)}", ".tabs {\n\tcolor: rgb(255, 255, 0)\n}\n");
btc(".tabs{background:url('back.jpg')}", ".tabs {\n\tbackground: url('back.jpg')\n}\n");
btc("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}\n");
btc("@media print {.tab{}}", "@media print {\n\t.tab {}\n}\n");
// comments
btc("/* test */", "/* test */\n");
btc(".tabs{/* test */}", ".tabs {\n\t/* test */\n}\n");
btc("/* header */.tabs {}", "/* header */\n\n.tabs {}\n");
// separate selectors
btc("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}\n");
btc("a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}\n");
// test options
opts.indent_size = 2;
opts.indent_char = ' ';
opts.selector_separator_newline = false;
btc("#bla, #foo{color:green}", "#bla, #foo {\n color: green\n}\n");
btc("@media print {.tab{}}", "@media print {\n .tab {}\n}\n");
btc("#bla, #foo{color:black}", "#bla, #foo {\n color: black\n}\n");
return sanitytest;
}

View File

@ -4,11 +4,12 @@
var SanityTest = require('./sanitytest'),
Urlencoded = require('../lib/unpackers/urlencode_unpacker'),
js_beautify = require('../index').js_beautify,
css_beautify = require('../index').css_beautify,
html_beautify = require('../index').html_beautify,
run_beautifier_tests = require('./beautify-tests').run_beautifier_tests;
function node_beautifier_tests() {
var results = run_beautifier_tests(new SanityTest(), Urlencoded, js_beautify, html_beautify);
var results = run_beautifier_tests(new SanityTest(), Urlencoded, js_beautify, html_beautify, css_beautify);
console.log(results.results_raw());
return results;
}

View File

@ -0,0 +1,290 @@
from __future__ import print_function
import sys
import re
from cssbeautifier.__version__ import __version__
#
# The MIT License (MIT)
# Copyright (c) 2013 Einar Lielmanis and contributors.
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
class BeautifierOptions:
def __init__(self):
self.indent_size = 4
self.indent_char = ' '
self.selector_separator_newline = True
self.end_with_newline = False
def __repr__(self):
return \
"""indent_size = %d
indent_char = [%s]
separate_selectors_newline = [%s]
end_with_newline = [%s]
""" % (self.indent_size, self.indent_char,
self.separate_selectors, self.end_with_newline)
def default_options():
return BeautifierOptions()
def beautify(string, opts=default_options()):
b = Beautifier(string, opts)
return b.beautify()
def beautify_file(file_name, opts=default_options()):
if file_name == '-': # stdin
stream = sys.stdin
else:
stream = open(file_name)
content = ''.join(stream.readlines())
b = Beautifier(content, opts)
return b.beautify()
def usage(stream=sys.stdout):
print("cssbeautifier.py@" + __version__ + """
CSS beautifier (http://jsbeautifier.org/)
""", file=stream)
if stream == sys.stderr:
return 1
else:
return 0
WHITE_RE = re.compile("^\s+$")
WORD_RE = re.compile("[\w$\-_]")
class Printer:
def __init__(self, indent_char, indent_size, default_indent=""):
self.indentSize = indent_size
self.singleIndent = (indent_size) * indent_char
self.indentString = default_indent
self.output = [default_indent]
def __lastCharWhitespace(self):
return WHITE_RE.search(self.output[len(self.output) - 1]) is not None
def indent(self):
self.indentString += self.singleIndent
def outdent(self):
self.indentString = self.indentString[:-(self.indentSize + 1)]
def push(self, string):
self.output.append(string)
def openBracket(self):
self.singleSpace()
self.output.append("{")
self.newLine()
def closeBracket(self):
self.newLine()
self.output.append("}")
self.newLine()
def colon(self):
self.output.append(":")
self.singleSpace()
def semicolon(self):
self.output.append(";")
self.newLine()
def comment(self, comment):
self.output.append(comment)
def newLine(self, keepWhitespace=False):
if not keepWhitespace:
while self.__lastCharWhitespace():
self.output.pop()
if len(self.output) > 0:
self.output.append("\n")
if len(self.indentString) > 0:
self.output.append(self.indentString)
def singleSpace(self):
if len(self.output) > 0 and not self.__lastCharWhitespace():
self.output.append(" ")
def result(self):
return "".join(self.output)
class Beautifier:
def __init__(self, source_text, opts=default_options()):
self.source_text = source_text
self.opts = opts
self.indentSize = opts.indent_size
self.indentChar = opts.indent_char
self.pos = -1
self.ch = None
def next(self):
self.pos = self.pos + 1
if self.pos < len(self.source_text):
self.ch = self.source_text[self.pos]
else:
self.ch = None
return self.ch
def peek(self):
if self.pos + 1 < len(self.source_text):
return self.source_text[self.pos + 1]
else:
return ""
def eatString(self, endChar):
start = self.pos
while self.next():
if self.ch == "\\":
self.next()
self.next()
elif self.ch == endChar:
break
elif self.ch == "\n":
break
return self.source_text[start:self.pos] + endChar
def eatWhitespace(self):
start = self.pos
while WHITE_RE.search(self.peek()) is not None:
self.pos = self.pos + 1
return self.pos != start
def skipWhitespace(self):
start = self.pos
while self.next() and WHITE_RE.search(self.ch) is not None:
pass
return self.pos != start + 1
def eatComment(self):
start = self.pos
self.next()
while self.next():
if self.ch == "*" and self.peek() == "/":
self.pos = self.pos + 1
break
return self.source_text[start:self.pos + 1]
def lookBack(self, string):
past = self.source_text[self.pos - len(string):self.pos]
return past.lower() == string
def beautify(self):
m = re.search("^[\r\n]*[\t ]*", self.source_text)
indentString = m.group(0)
printer = Printer(self.indentChar, self.indentSize, indentString)
insideRule = False
while True:
isAfterSpace = self.skipWhitespace()
if not self.ch:
break
elif self.ch == '/' and self.peek() == '*':
comment = self.eatComment()
printer.comment(comment)
header = self.lookBack("")
if header:
printer.push("\n\n")
elif self.ch == '{':
self.eatWhitespace()
if self.peek() == '}':
self.next()
printer.push(" {}")
else:
printer.indent()
printer.openBracket()
elif self.ch == '}':
printer.outdent()
printer.closeBracket()
insideRule = False
elif self.ch == ":":
self.eatWhitespace()
printer.colon()
insideRule = True
elif self.ch == '"' or self.ch == '\'':
printer.push(self.eatString(self.ch))
elif self.ch == ';':
printer.semicolon()
elif self.ch == '(':
# may be a url
if self.lookBack("url"):
printer.push(self.ch)
self.eatWhitespace()
if self.next():
if self.ch is not ')' and self.ch is not '"' \
and self.ch is not '\'':
printer.push(self.eatString(')'))
else:
self.pos = self.pos - 1
else:
if isAfterSpace:
printer.singleSpace()
printer.push(self.ch)
self.eatWhitespace()
elif self.ch == ')':
printer.push(self.ch)
elif self.ch == ',':
self.eatWhitespace()
printer.push(self.ch)
if not insideRule and self.opts.selector_separator_newline:
printer.newLine()
else:
printer.singleSpace()
elif self.ch == ']':
printer.push(self.ch)
elif self.ch == '[' or self.ch == '=':
# no whitespace before or after
self.eatWhitespace()
printer.push(self.ch)
else:
if isAfterSpace:
printer.singleSpace()
printer.push(self.ch)
sweet_code = printer.result()
# establish end_with_newline
should = self.opts.end_with_newline
actually = sweet_code.endswith("\n")
if should and not actually:
sweet_code = sweet_code + "\n"
elif not should and actually:
sweet_code = sweet_code[:-1]
return sweet_code

View File

@ -0,0 +1 @@
__version__ = '1.0.0'

View File

@ -0,0 +1 @@
# Empty file :)

View File

@ -0,0 +1,60 @@
import unittest
import cssbeautifier
class CSSBeautifierTest(unittest.TestCase):
def resetOptions(self):
self.options = cssbeautifier.default_options()
self.options.indent_size = 1
self.options.indent_char = '\t'
self.options.selector_separator_newline = True
self.options.end_with_newline = True
def testBasics(self):
self.resetOptions()
t = self.decodesto
t("", "\n")
t(".tabs{}", ".tabs {}\n")
t(".tabs{color:red}", ".tabs {\n\tcolor: red\n}\n")
t(".tabs{color:rgb(255, 255, 0)}", ".tabs {\n\tcolor: rgb(255, 255, 0)\n}\n")
t(".tabs{background:url('back.jpg')}", ".tabs {\n\tbackground: url('back.jpg')\n}\n")
t("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}\n")
t("@media print {.tab{}}", "@media print {\n\t.tab {}\n}\n")
def testComments(self):
self.resetOptions()
t = self.decodesto
t("/* test */", "/* test */\n\n")
t(".tabs{/* test */}", ".tabs {\n\t/* test */\n}\n")
t("/* header */.tabs {}", "/* header */\n\n.tabs {}\n")
def testSeperateSelectors(self):
self.resetOptions()
t = self.decodesto
t("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}\n")
t("a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}\n")
def testOptions(self):
self.resetOptions()
self.options.indent_size = 2
self.options.indent_char = ' '
self.options.selector_separator_newline = False
t = self.decodesto
t("#bla, #foo{color:green}", "#bla, #foo {\n color: green\n}\n")
t("@media print {.tab{}}", "@media print {\n .tab {}\n}\n")
t("#bla, #foo{color:black}", "#bla, #foo {\n color: black\n}\n")
def decodesto(self, input, expectation=None):
self.assertEqual(
cssbeautifier.beautify(input, self.options), expectation or input)
if __name__ == '__main__':
unittest.main()