Bring css python implementation into sync

This commit is contained in:
Liam Newman 2018-08-28 12:19:06 -07:00
parent 698dd05fc2
commit 77630e5005

View File

@ -77,33 +77,6 @@ CSS beautifier (http://jsbeautifier.org/)
return 0
class Printer:
def __init__(self, indent_char, indent_size, default_indent=""):
self.singleIndent = (indent_size) * indent_char
self.indentLevel = 0
self.nestedLevel = 0
self.baseIndentString = default_indent
self.output = Output(self.singleIndent, self.baseIndentString)
def indent(self):
self.indentLevel += 1
def outdent(self):
if self.indentLevel > 0:
self.indentLevel -= 1
def preserveSingleSpace(self, isAfterSpace):
if isAfterSpace:
self.output.space_before_token = True
def print_string(self, output_string):
if self.output.just_added_newline():
self.output.set_indent(self.indentLevel)
self.output.add_token(output_string)
class Beautifier:
@ -111,7 +84,7 @@ class Beautifier:
# in javascript, these two differ
# in python they are the same, different methods are called on them
# IMPORTANT: This string must be run through six to handle \u chars
self.lineBreak = re.compile(six.u(r"\r\n|[\n\r\u2028\u2029]"))
self.lineBreak = re.compile(six.u(r"\r\n|[\n\r]"))
self.allLineBreaks = self.lineBreak
self.comment_pattern = re.compile(
@ -124,14 +97,13 @@ class Beautifier:
self.__source_text = source_text
opts = BeautifierOptions(opts)
self.opts = opts
self.indentSize = opts.indent_size
self.indentChar = opts.indent_char
self.input = None
self.ch = None
self._options = BeautifierOptions(opts)
self._input = None
self._ch = None
self._indentLevel = 0
self._nestedLevel = 0
self._output = None
# https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule
# also in CONDITIONAL_GROUP_RULE below
@ -149,14 +121,14 @@ class Beautifier:
def eatString(self, endChars):
result = ''
self.ch = self.input.next()
while self.ch:
result += self.ch
if self.ch == "\\":
result += self.input.next()
elif self.ch in endChars or self.ch == "\n":
self._ch = self._input.next()
while self._ch:
result += self._ch
if self._ch == "\\":
result += self._input.next()
elif self._ch in endChars or self._ch == "\n":
break
self.ch = self.input.next()
self._ch = self._input.next()
return result
# Skips any white space in the source text from the current position.
@ -164,15 +136,15 @@ class Beautifier:
# newline character found; if the user has preserve_newlines off, only
# the first newline will be output
def eatWhitespace(self, allowAtLeastOneNewLine=False):
result = whitespaceChar.search(self.input.peek() or '') is not None
result = whitespaceChar.search(self._input.peek() or '') is not None
isFirstNewLine = True
while whitespaceChar.search(self.input.peek() or '') is not None:
self.ch = self.input.next()
if allowAtLeastOneNewLine and self.ch == "\n":
if self.opts.preserve_newlines or isFirstNewLine:
while whitespaceChar.search(self._input.peek() or '') is not None:
self._ch = self._input.next()
if allowAtLeastOneNewLine and self._ch == "\n":
if self._options.preserve_newlines or isFirstNewLine:
isFirstNewLine = False
self.output.add_new_line(True)
self._output.add_new_line(True)
return result
# Nested pseudo-class if we are insideRule
@ -181,7 +153,7 @@ class Beautifier:
def foundNestedPseudoClass(self):
openParen = 0
i = 1
ch = self.input.peek(i)
ch = self._input.peek(i)
while ch:
if ch == "{":
return True
@ -195,20 +167,39 @@ class Beautifier:
elif ch == ";" or ch == "}":
return False
i += 1
ch = self.input.peek(i)
ch = self._input.peek(i)
return False
def indent(self):
self._indentLevel += 1
def outdent(self):
if self._indentLevel > 0:
self._indentLevel -= 1
def preserveSingleSpace(self, isAfterSpace):
if isAfterSpace:
self._output.space_before_token = True
def print_string(self, output_string):
if self._output.just_added_newline():
self._output.set_indent(self._indentLevel)
self._output.add_token(output_string)
def beautify(self):
if self.opts.disabled:
if self._options.disabled:
return self.__source_text
source_text = self.__source_text
if self.opts.eol == 'auto':
self.opts.eol = '\n'
if self._options.eol == 'auto':
self._options.eol = '\n'
if self.lineBreak.search(source_text or ''):
self.opts.eol = self.lineBreak.search(source_text).group()
self._options.eol = self.lineBreak.search(source_text).group()
# HACK: newline parsing inconsistent. This brute force normalizes the
@ -216,19 +207,16 @@ class Beautifier:
source_text = re.sub(self.allLineBreaks, '\n', source_text)
m = re.search("^[\t ]*", source_text)
self.baseIndentString = m.group(0)
baseIndentString = m.group(0)
printer = Printer(
self.indentChar,
self.indentSize,
self.baseIndentString)
self.output = printer.output
self.input = InputScanner(source_text)
self._output = Output(self._options.indent_string, baseIndentString)
output = self.output
input = self.input
self._input = InputScanner(source_text)
self.ch = None
self._indentLevel = 0
self._nestedLevel = 0
self._ch = None
parenLevel = 0
insideRule = False
@ -236,27 +224,27 @@ class Beautifier:
enteringConditionalGroup = False
insideAtExtend = False
insideAtImport = False
topCharacter = self.ch
topCharacter = self._ch
while True:
whitespace = input.read(whitespacePattern)
whitespace = self._input.read(whitespacePattern)
isAfterSpace = whitespace != ''
previous_ch = topCharacter
self.ch = input.next()
topCharacter = self.ch
self._ch = self._input.next()
topCharacter = self._ch
if not self.ch:
if not self._ch:
break
elif self.ch == '/' and input.peek() == '*':
elif self._ch == '/' and self._input.peek() == '*':
# /* css comment */
# Always start block comments on a new line.
# This handles scenarios where a block comment immediately
# follows a property definition on the same line or where
# minified code is being beautified.
output.add_new_line()
input.back()
printer.print_string(
input.read(
self._output.add_new_line()
self._input.back()
self.print_string(
self._input.read(
self.block_comment_pattern))
# Ensures any new lines following the comment are preserved
@ -264,27 +252,27 @@ class Beautifier:
# Block comments are followed by a new line so they don't
# share a line with other properties
output.add_new_line()
elif self.ch == '/' and input.peek() == '/':
self._output.add_new_line()
elif self._ch == '/' and self._input.peek() == '/':
# // single line comment
# Preserves the space before a comment
# on the same line as a rule
output.space_before_token = True
input.back()
printer.print_string(input.read(self.comment_pattern))
self._output.space_before_token = True
self._input.back()
self.print_string(self._input.read(self.comment_pattern))
# Ensures any new lines following the comment are preserved
self.eatWhitespace(True)
elif self.ch == '@':
printer.preserveSingleSpace(isAfterSpace)
elif self._ch == '@':
self.preserveSingleSpace(isAfterSpace)
# deal with less propery mixins @{...}
if input.peek() == '{':
printer.print_string(self.ch + self.eatString('}'))
if self._input.peek() == '{':
self.print_string(self._ch + self.eatString('}'))
else:
printer.print_string(self.ch)
self.print_string(self._ch)
# strip trailing space, for hash property check
variableOrRule = input.peekUntilAfter(
variableOrRule = self._input.peekUntilAfter(
re.compile(r"[: ,;{}()[\]\/='\"]"))
if variableOrRule[-1] in ": ":
@ -293,8 +281,8 @@ class Beautifier:
variableOrRule = self.eatString(": ")
if variableOrRule[-1].isspace():
variableOrRule = variableOrRule[:-1]
printer.print_string(variableOrRule)
output.space_before_token = True
self.print_string(variableOrRule)
self._output.space_before_token = True
if variableOrRule[-1].isspace():
variableOrRule = variableOrRule[:-1]
@ -306,75 +294,75 @@ class Beautifier:
# might be a nesting at-rule
if variableOrRule in self.NESTED_AT_RULE:
printer.nestedLevel += 1
self._nestedLevel += 1
if variableOrRule in self.CONDITIONAL_GROUP_RULE:
enteringConditionalGroup = True
elif not insideRule and parenLevel == 0 and \
variableOrRule[-1] == ":":
insidePropertyValue = True
printer.indent()
elif self.ch == '#' and input.peek() == '{':
printer.preserveSingleSpace(isAfterSpace)
printer.print_string(self.ch + self.eatString('}'))
elif self.ch == '{':
self.indent()
elif self._ch == '#' and self._input.peek() == '{':
self.preserveSingleSpace(isAfterSpace)
self.print_string(self._ch + self.eatString('}'))
elif self._ch == '{':
if insidePropertyValue:
insidePropertyValue = False
printer.outdent()
printer.indent()
output.space_before_token = True
printer.print_string(self.ch)
self.outdent()
self.indent()
self._output.space_before_token = True
self.print_string(self._ch)
# when entering conditional groups, only rulesets are
# allowed
if enteringConditionalGroup:
enteringConditionalGroup = False
insideRule = printer.indentLevel > printer.nestedLevel
insideRule = self._indentLevel > self._nestedLevel
else:
# otherwise, declarations are also allowed
insideRule = printer.indentLevel >= printer.nestedLevel
insideRule = self._indentLevel >= self._nestedLevel
if self.opts.newline_between_rules and insideRule:
if output.previous_line and \
not output.previous_line.is_empty() and \
output.previous_line.item(-1) != '{':
output.ensure_empty_line_above('/', ',')
if self._options.newline_between_rules and insideRule:
if self._output.previous_line and \
not self._output.previous_line.is_empty() and \
self._output.previous_line.item(-1) != '{':
self._output.ensure_empty_line_above('/', ',')
self.eatWhitespace(True)
output.add_new_line()
elif self.ch == '}':
printer.outdent()
output.add_new_line()
self._output.add_new_line()
elif self._ch == '}':
self.outdent()
self._output.add_new_line()
if previous_ch == '{':
output.trim(True)
self._output.trim(True)
insideAtExtend = False
insideAtImport = False
if insidePropertyValue:
printer.outdent()
self.outdent()
insidePropertyValue = False
printer.print_string(self.ch)
self.print_string(self._ch)
insideRule = False
if printer.nestedLevel:
printer.nestedLevel -= 1
if self._nestedLevel:
self._nestedLevel -= 1
self.eatWhitespace(True)
output.add_new_line()
self._output.add_new_line()
if self.opts.newline_between_rules and \
not output.just_added_blankline():
if input.peek() != '}':
output.add_new_line(True)
elif self.ch == ":":
if self._options.newline_between_rules and \
not self._output.just_added_blankline():
if self._input.peek() != '}':
self._output.add_new_line(True)
elif self._ch == ":":
if (insideRule or enteringConditionalGroup) and \
not (input.lookBack('&') or
not (self._input.lookBack('&') or
self.foundNestedPseudoClass()) and \
not input.lookBack('(') and not insideAtExtend:
not self._input.lookBack('(') and not insideAtExtend:
# 'property: value' delimiter
# which could be in a conditional group query
printer.print_string(":")
self.print_string(":")
if not insidePropertyValue:
insidePropertyValue = True
output.space_before_token = True
self._output.space_before_token = True
self.eatWhitespace(True)
printer.indent()
self.indent()
else:
# sass/less parent reference don't use a space
@ -382,93 +370,93 @@ class Beautifier:
# preserve space before pseudoclasses/pseudoelements,
# as it means "in any child"
if input.lookBack(' '):
output.space_before_token = True
if input.peek() == ":":
if self._input.lookBack(' '):
self._output.space_before_token = True
if self._input.peek() == ":":
# pseudo-element
self.ch = input.next()
printer.print_string("::")
self._ch = self._input.next()
self.print_string("::")
else:
# pseudo-element
printer.print_string(":")
elif self.ch == '"' or self.ch == '\'':
printer.preserveSingleSpace(isAfterSpace)
printer.print_string(self.ch + self.eatString(self.ch))
self.print_string(":")
elif self._ch == '"' or self._ch == '\'':
self.preserveSingleSpace(isAfterSpace)
self.print_string(self._ch + self.eatString(self._ch))
self.eatWhitespace(True)
elif self.ch == ';':
elif self._ch == ';':
if insidePropertyValue:
printer.outdent()
self.outdent()
insidePropertyValue = False
insideAtExtend = False
insideAtImport = False
printer.print_string(self.ch)
self.print_string(self._ch)
self.eatWhitespace(True)
# This maintains single line comments on the same
# line. Block comments are also affected, but
# a new line is always output before one inside
# that section
if input.peek() is not '/':
output.add_new_line()
elif self.ch == '(':
if self._input.peek() is not '/':
self._output.add_new_line()
elif self._ch == '(':
# may be a url
if input.lookBack("url"):
printer.print_string(self.ch)
if self._input.lookBack("url"):
self.print_string(self._ch)
self.eatWhitespace()
self.ch = input.next()
if self.ch in {')', '"', '\''}:
input.back()
self._ch = self._input.next()
if self._ch in {')', '"', '\''}:
self._input.back()
parenLevel += 1
elif self.ch is not None:
printer.print_string(self.ch + self.eatString(')'))
elif self._ch is not None:
self.print_string(self._ch + self.eatString(')'))
else:
parenLevel += 1
printer.preserveSingleSpace(isAfterSpace)
printer.print_string(self.ch)
self.preserveSingleSpace(isAfterSpace)
self.print_string(self._ch)
self.eatWhitespace()
elif self.ch == ')':
printer.print_string(self.ch)
elif self._ch == ')':
self.print_string(self._ch)
parenLevel -= 1
elif self.ch == ',':
printer.print_string(self.ch)
elif self._ch == ',':
self.print_string(self._ch)
self.eatWhitespace(True)
if self.opts.selector_separator_newline and \
if self._options.selector_separator_newline and \
not insidePropertyValue and parenLevel < 1 and \
not insideAtImport:
output.add_new_line()
self._output.add_new_line()
else:
output.space_before_token = True
elif (self.ch == '>' or self.ch == '+' or self.ch == '~') and \
self._output.space_before_token = True
elif (self._ch == '>' or self._ch == '+' or self._ch == '~') and \
not insidePropertyValue and parenLevel < 1:
# handle combinator spacing
if self.opts.space_around_combinator:
output.space_before_token = True
printer.print_string(self.ch)
output.space_before_token = True
if self._options.space_around_combinator:
self._output.space_before_token = True
self.print_string(self._ch)
self._output.space_before_token = True
else:
printer.print_string(self.ch)
self.print_string(self._ch)
self.eatWhitespace()
# squash extra whitespace
if self.ch and bool(whitespaceChar.search(self.ch)):
self.ch = ''
elif self.ch == ']':
printer.print_string(self.ch)
elif self.ch == '[':
printer.preserveSingleSpace(isAfterSpace)
printer.print_string(self.ch)
elif self.ch == '=':
if self._ch and bool(whitespaceChar.search(self._ch)):
self._ch = ''
elif self._ch == ']':
self.print_string(self._ch)
elif self._ch == '[':
self.preserveSingleSpace(isAfterSpace)
self.print_string(self._ch)
elif self._ch == '=':
# no whitespace before or after
self.eatWhitespace()
printer.print_string('=')
if bool(whitespaceChar.search(self.ch)):
self.ch = ''
elif self.ch == '!': # !important
printer.print_string(' ')
printer.print_string(self.ch)
self.print_string('=')
if bool(whitespaceChar.search(self._ch)):
self._ch = ''
elif self._ch == '!': # !important
self.print_string(' ')
self.print_string(self._ch)
else:
printer.preserveSingleSpace(isAfterSpace)
printer.print_string(self.ch)
self.preserveSingleSpace(isAfterSpace)
self.print_string(self._ch)
sweet_code = output.get_code(self.opts.end_with_newline, self.opts.eol)
sweet_code = self._output.get_code(self._options.end_with_newline, self._options.eol)
return sweet_code