Skip to content

Commit

Permalink
css colon character fix port to python
Browse files Browse the repository at this point in the history
Closes #446
Closes #418
  • Loading branch information
bitwiseman committed Sep 29, 2014
1 parent d030e1c commit 57bc806
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 10 deletions.
2 changes: 1 addition & 1 deletion js/test/beautify-tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -2215,7 +2215,7 @@ function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify,
btc("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}");
btc("@media print {.tab{}}", "@media print {\n\t.tab {}\n}");
btc("@media print {.tab{background-image:url([email protected])}}", "@media print {\n\t.tab {\n\t\tbackground-image: url([email protected])\n\t}\n}");

//lead-in whitespace determines base-indent.
// lead-in newlines are stripped.
btc("\n\na, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}");
Expand Down
71 changes: 62 additions & 9 deletions python/cssbeautifier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ class Printer:
def __init__(self, indent_char, indent_size, default_indent=""):
self.indentSize = indent_size
self.singleIndent = (indent_size) * indent_char
self.indentLevel = 0
self.nestedLevel = 0

self.baseIndentString = default_indent
self.output = []
if self.baseIndentString:
Expand All @@ -95,10 +98,13 @@ def __lastCharWhitespace(self):
return WHITE_RE.search(self.output[-1]) is not None

def indent(self):
self.indentLevel += 1
self.baseIndentString += self.singleIndent

def outdent(self):
self.baseIndentString = self.baseIndentString[:-(len(self.singleIndent))]
if self.indentLevel:
self.indentLevel -= 1
self.baseIndentString = self.baseIndentString[:-(len(self.singleIndent))]

def push(self, string):
self.output.append(string)
Expand All @@ -113,10 +119,6 @@ def closeBracket(self):
self.output.append("}")
self.newLine()

def colon(self):
self.output.append(":")
self.singleSpace()

def semicolon(self):
self.output.append(";")
self.newLine()
Expand Down Expand Up @@ -152,6 +154,20 @@ def __init__(self, source_text, opts=default_options()):
self.indentChar = opts.indent_char
self.pos = -1
self.ch = None

# https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule
# also in CONDITIONAL_GROUP_RULE below
self.NESTED_AT_RULE = [ \
"@page", \
"@font-face", \
"@keyframes", \
"@media", \
"@supports", \
"@document"]
self.CONDITIONAL_GROUP_RULE = [ \
"@media", \
"@supports", \
"@document"]

def next(self):
self.pos = self.pos + 1
Expand Down Expand Up @@ -219,6 +235,8 @@ def beautify(self):
printer = Printer(self.indentChar, self.indentSize, baseIndentString)

insideRule = False
enteringConditionalGroup = False

while True:
isAfterSpace = self.skipWhitespace()

Expand All @@ -233,6 +251,22 @@ def beautify(self):
elif self.ch == '/' and self.peek() == '/':
printer.comment(self.eatComment(True)[0:-1])
printer.newLine()
elif self.ch == '@':
# strip trailing space, if present, for hash property check
atRule = self.eatString(" ")
if(atRule[-1] == " "):
atRule = atRule[:-1]

# pass along the space we found as a separate item
printer.push(atRule)
printer.push(self.ch)

# might be a nesting at-rule
if atRule in self.NESTED_AT_RULE:
printer.nestedLevel += 1
if atRule in self.CONDITIONAL_GROUP_RULE:
enteringConditionalGroup = True

elif self.ch == '{':
self.eatWhitespace()
if self.peek() == '}':
Expand All @@ -241,14 +275,34 @@ def beautify(self):
else:
printer.indent()
printer.openBracket()
# when entering conditional groups, only rulesets are allowed
if enteringConditionalGroup:
enteringConditionalGroup = False
insideRule = printer.indentLevel > printer.nestedLevel
else:
# otherwise, declarations are also allowed
insideRule = printer.indentLevel >= printer.nestedLevel
elif self.ch == '}':
printer.outdent()
printer.closeBracket()
insideRule = False
if printer.nestedLevel:
printer.nestedLevel -= 1
elif self.ch == ":":
self.eatWhitespace()
printer.colon()
insideRule = True
if insideRule or enteringConditionalGroup:
# 'property: value' delimiter
# which could be in a conditional group query
printer.push(self.ch)
printer.singleSpace()
else:
if self.peek() == ":":
# pseudo-element
self.next()
printer.push("::")
else:
# pseudo-element
printer.push(self.ch)
elif self.ch == '"' or self.ch == '\'':
printer.push(self.eatString(self.ch))
elif self.ch == ';':
Expand Down Expand Up @@ -307,5 +361,4 @@ def beautify(self):
if self.opts.end_with_newline:
sweet_code += "\n"

return sweet_code

return sweet_code
43 changes: 43 additions & 0 deletions python/cssbeautifier/tests/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ def testBasics(self):
t(".tabs{background:url('back.jpg')}", ".tabs {\n\tbackground: url('back.jpg')\n}")
t("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}")
t("@media print {.tab{}}", "@media print {\n\t.tab {}\n}")
t("@media print {.tab{background-image:url([email protected])}}", "@media print {\n\t.tab {\n\t\tbackground-image: url([email protected])\n\t}\n}")

# may not eat the space before "["
t('html.js [data-custom="123"] {\n\topacity: 1.00;\n}')
Expand All @@ -46,6 +47,7 @@ def testBasics(self):
t(" \t \na, img {padding: 0.2px}", " \t a,\n \t img {\n \t \tpadding: 0.2px\n \t }")
t("\n\n a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}")


def testComments(self):
self.resetOptions()
t = self.decodesto
Expand All @@ -71,6 +73,34 @@ def testSeperateSelectors(self):
t("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}")
t("a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}")


def testBlockNesting(self):
self.resetOptions()
t = self.decodesto

t("#foo {\n\tbackground-image: url([email protected]);\n\t@font-face {\n\t\tfont-family: 'Bitstream Vera Serif Bold';\n\t\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n\t}\n}")
t("@media screen {\n\t#foo:hover {\n\t\tbackground-image: url([email protected]);\n\t}\n\t@font-face {\n\t\tfont-family: 'Bitstream Vera Serif Bold';\n\t\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n\t}\n}")

# @font-face {
# font-family: 'Bitstream Vera Serif Bold';
# src: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');
# }
# @media screen {
# #foo:hover {
# background-image: url(foo.png);
# }
# @media screen and (min-device-pixel-ratio: 2) {
# @font-face {
# font-family: 'Helvetica Neue'
# }
# #foo:hover {
# background-image: url([email protected]);
# }
# }
# }
t("@font-face {\n\tfont-family: 'Bitstream Vera Serif Bold';\n\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n}\n@media screen {\n\t#foo:hover {\n\t\tbackground-image: url(foo.png);\n\t}\n\t@media screen and (min-device-pixel-ratio: 2) {\n\t\t@font-face {\n\t\t\tfont-family: 'Helvetica Neue'\n\t\t}\n\t\t#foo:hover {\n\t\t\tbackground-image: url([email protected]);\n\t\t}\n\t}\n}")


def testOptions(self):
self.resetOptions()
self.options.indent_size = 2
Expand All @@ -80,8 +110,21 @@ def testOptions(self):

t("#bla, #foo{color:green}", "#bla, #foo {\n color: green\n}")
t("@media print {.tab{}}", "@media print {\n .tab {}\n}")
t("@media print {.tab,.bat{}}", "@media print {\n .tab, .bat {}\n}")
t("#bla, #foo{color:black}", "#bla, #foo {\n color: black\n}")

# pseudo-classes and pseudo-elements
t("#foo:hover {\n background-image: url([email protected])\n}")
t("#foo *:hover {\n color: purple\n}")
t("::selection {\n color: #ff0000;\n}")

# TODO: don't break nested pseduo-classes
t("@media screen {.tab,.bat:hover {color:red}}", "@media screen {\n .tab, .bat:hover {\n color: red\n }\n}")

# particular edge case with braces and semicolons inside tags that allows custom text
t( "a:not(\"foobar\\\";{}omg\"){\ncontent: 'example\\';{} text';\ncontent: \"example\\\";{} text\";}",
"a:not(\"foobar\\\";{}omg\") {\n content: 'example\\';{} text';\n content: \"example\\\";{} text\";\n}")

def decodesto(self, input, expectation=None):
if expectation == None:
expectation = input
Expand Down

0 comments on commit 57bc806

Please sign in to comment.