Esempio n. 1
0
def cleanup_stylesheets(
    file_path_normalized,
    windows_newline,
    fix_extra_newlines,
    property_order,
    rules_skip
):
    """
    Cleans up stylesheets. Sorts the css properties in the file, by the specified property order.
    Ensures that all property lines are correctly terminated with semicolon.

    :type file_path_normalized: String
    :param file_path_normalized: The file path normalized.
    :type windows_newline: bool
    :param windows_newline: If the windows newline should be used.
    :type fix_extra_newlines: bool
    :param fix_extra_newlines: If the extra newlines should be fixed.
    :type property_order: List
    :param property_order: The list with the explicit order of property names.
    :type rules_skip: List
    :param rules_skip: The list of specific rules to skip.
    """

    # opens the file for reading in text mode so that the
    # lines are read in normalized mode
    file = open(file_path_normalized, "rb")

    try:
        # applies the property cleaning, this should run the
        # complete set of rules and clean the file
        string_buffer = cleanup_properties(
            file,
            windows_newline = windows_newline,
            fix_extra_newlines = fix_extra_newlines,
            property_order = property_order,
            rules_skip = rules_skip
        )

        # retrieves the string value from the output
        # buffer to be written to the file and then
        # encodes it using the default encoding of css
        string_value = string_buffer.getvalue()
        string_value = string_value.encode("utf-8")
    except Exception as exception:
        # retrieves the exception string and uses it in the log
        # message to be printed to the standard output, then logs
        # the complete traceback messages to the same output
        exception_string = legacy.UNICODE(exception)
        extra.error("%s. Skipping file %s" % (exception_string, file_path_normalized))
        traceback.print_exc(file = sys.stdout)

        # skips writing to the file
        return
    finally:
        # closes the file for reading
        file.close()

    # opens the file for writing and then outputs the
    # final normalized stylesheet contents into it
    file = open(file_path_normalized, "wb")
    try: file.write(string_value)
    finally: file.close()
Esempio n. 2
0
def cleanup_properties(
    input_buffer,
    windows_newline = True,
    fix_extra_newlines = True,
    property_order = (),
    rules_skip = ()
):
    """
    Cleans up the property lines. Sorts the css properties in the file,
    by the specified property order.

    Ensures that all property lines are correctly terminated with semicolon.

    :type input_buffer: StringBuffer
    :param input_buffer: The input buffer.
    :type windows_newline: bool
    :param windows_newline: If the windows newline should be used.
    :type windows_newline: bool
    :param windows_newline: If the extra newlines should be fixed.
    :type fix_extra_newlines: bool
    :param fix_extra_newlines: If the extra newlines should be fixed.
    :type property_order: List
    :param property_order: The list with the explicit order of property names.
    :type rules_skip: List
    :param rules_skip: The list of specific rules to skip.
    """

    # reads the input lines and counts the number of lines
    # in the buffer setting it as the number of "original" lines
    lines = input_buffer.readlines()
    number_original_lines = len(lines)

    # creates a string buffer that will hold the output of the
    # cleanup operation over the current stylesheet file
    output_buffer = legacy.StringIO()

    # initializes a series of state variables that will control
    # the way the parser/generator will work through the file
    rule_started = False
    line_number = 0
    open_rule_count = 0
    newlines = 0
    comments_started = 0
    needs_newline = False

    # for each of the input lines it's going to run the iteration
    # loop and match it against the proper rules
    for line in lines:
        # decodes the current line as a processing using unicode
        # characters is required for correct results
        line = line.decode("utf-8")

        # increments the line number as one more line is going
        # to be processes through the current iteration
        line_number += 1

        # updates the comparison key function
        get_comparison_key = lambda property_line: get_property_index(
            property_line,
            property_order,
            line_number
        )

        # in case the line contains a single line comment,
        # does nothing, will write the line as is
        if "/*" in line and "*/" in line and not "*/*/" in line:
            pass

        # in case the line contains the end of multiline comment
        elif "*/" in line:
            # in case the comment mode is currently not enabled prints
            # a warning as we're trying to close a comment without opening
            if not comments_started: extra.error(
                "Found closing comment without corresponding opening at line %d" % line_number
            )

            # in case there's more that one closing comment defined under
            # the same line prints an error as this is not allowed
            if line.count("*/") > 1: extra.error(
                "More that one closing comment at line %d" % line_number
            )

            # decrements the comments started counter and then
            # enables the needs newline flag
            comments_started = 0
            needs_newline = True

        # in case the line contains a the start of multiline comment
        elif "/*" in line:
            # in case the comment mode is already enabled (at least one)
            # prints a waning about the double opening
            if comments_started: extra.warn(
                "Found opening comment inside open comment at line %d" % line_number
            )

            # in case there's more that one opening comment defined under
            # the same line prints an error as this is not allowed
            if line.count("/*") > 1: extra.error(
                "More that one opening comment at line %d" % line_number
            )

            # increments the comments started counter
            comments_started += 1

        # in case this is a comment line (comment started)
        # does nothing, will just write the line as is
        elif comments_started:
            pass

        # in case the line contains an at rule specification
        elif AT_RULES_REGEX.match(line):
            # after an at rule, a newline must follow
            needs_newline = True

        # in case the line contains a full rule specification
        # does nothing, will just write line as is as there's
        # very few thing possible to optimize under this situation
        elif "{" in line and "}" in line:
            line = line.strip()
            needs_newline = True
            rule_started = False

        # in case this is a rule start line
        elif "{" in line:
            # increments the open rule count
            open_rule_count += 1

            # in case this is an actual rule, meaning that the open rule
            # count value is positive (assertion valid)
            if open_rule_count > 0:
                # resets the rule lines
                rule_lines = []

                # signals the rule started flag,
                # in case the rule is no to be skipped
                rule_started = not skip_rule(line, rules_skip)

            # replaces the various css tokens with proper separators
            # meaning that no extra spaces between them are allowed
            line = TOKEN_REGEX.sub(r" \1 ", line)

            # runs the space replacement operation so that the
            # extra spaces in the line (not required) are removed
            # and the line is simplified
            line = SPACE_REGEX.sub(" ", line)
            line = line.strip()

            # calculates the space padding that is going to be pre-pended
            # to the line and then adds it to the line note that the open
            # rule count is decremented by one (opened on this line)
            padding = (open_rule_count - 1) * 4 * " "
            line = padding + line

            # flushes the current line value to the output buffer and then
            # continues the current loop (avoids general operations)
            write_line(output_buffer, line, windows_newline)
            continue

        elif "}" in line:
            # decrements the open rule count
            open_rule_count -= 1

            # in case there is a mismatch in open and closed rules
            # must raise an exception indicating the problem
            if open_rule_count < 0: raise Exception("mismatched rules found")

            # strips the current line removing any extra (not expected)
            # values and simplifying the current line value
            padding = open_rule_count * 4 * " "
            line = line.strip()
            line = padding + line

            # in case the rule set does not contain any property
            # must log an information message about the empty rule
            if rule_started and not rule_lines:
                extra.warn("Empty stylesheet rule at line %d" % line_number)

            # updates the flag to signal the rule has ended
            rule_started = False

            # sorts the various property lines and the processes them
            property_lines = sorted(rule_lines, key = get_comparison_key)
            property_lines = process_property_lines(
                property_lines,
                line_number,
                indentation = open_rule_count + 1,
                avoid_empty = fix_extra_newlines
            )
            property_lines = sorted(property_lines, key = get_comparison_key)

            # writes the lines to the buffer, considering the windows newline
            # and then writes the line
            write_lines(
                output_buffer,
                property_lines,
                windows_newline = windows_newline,
                avoid_empty = fix_extra_newlines
            )
            write_line(output_buffer, line, windows_newline = windows_newline)

            # resets the newlines counter and then
            # enables the needs newline flag
            newlines = 0
            needs_newline = True
            rule_lines = []

            # skips further processing
            continue

        # in case this is part of rule selector declaration
        # the typical rule replacement operations are applied
        elif line.rstrip().endswith(","):
            line = TOKEN_REGEX.sub(r" \1 ", line)
            line = SPACE_REGEX.sub(" ", line)
            line = COMMA_REGEX.sub(r"\1", line)
            line = line.strip()

        # in case this line is part of a valid rule set
        elif rule_started:
            # appends the line to the rule set, and then
            # skips outputting the line to the buffer
            rule_lines.append(line)
            continue

        # in case the between rules mode is active
        elif not rule_started and not line.strip():
            # increments the newlines count
            newlines += 1

            # otherwise in case this is an extra newline, must either
            # remove it or print a warning message depending on mode
            if not needs_newline and newlines > 1:
                if fix_extra_newlines: continue
                else: extra.warn("Found extra newline at line %d" % line_number)

            # disables the needs newline flag
            needs_newline = False

        else:
            # warns about the statement outside a valid rule
            extra.warn("Found statement outside rule at line %d" % line_number)

        # "calculates" the padding string value taking into account the
        # current open rule count value and then removes any left space
        # compatible values and replaces them with proper indentation
        padding = open_rule_count * 4 * " "
        line = line.lstrip()
        line = padding + line

        # writes the line to the output buffer taking into
        # account the windows newline control flag
        write_line(output_buffer, line, windows_newline)

    # in case there is a mismatch in open and closed rules
    # must raise an exception indicating the problem
    if not open_rule_count == 0: raise Exception("mismatched rules found")

    # retrieves the output buffer value and then counts the
    # number of lines contained in it (assertion validation)
    output_buffer_value = output_buffer.getvalue()
    number_lines = output_buffer_value.count("\n")

    if not number_lines == number_original_lines and not fix_extra_newlines:
        raise Exception(
            "number of lines in processed file (%d) is different from original file (%d)" %\
            (number_lines, number_original_lines)
        )

    return output_buffer