• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3# Copyright (c) 2012 Google Inc. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Pretty-prints the contents of a GYP file."""
8
9
10import sys
11import re
12
13
14# Regex to remove comments when we're counting braces.
15COMMENT_RE = re.compile(r"\s*#.*")
16
17# Regex to remove quoted strings when we're counting braces.
18# It takes into account quoted quotes, and makes sure that the quotes match.
19# NOTE: It does not handle quotes that span more than one line, or
20# cases where an escaped quote is preceded by an escaped backslash.
21QUOTE_RE_STR = r'(?P<q>[\'"])(.*?)(?<![^\\][\\])(?P=q)'
22QUOTE_RE = re.compile(QUOTE_RE_STR)
23
24
25def comment_replace(matchobj):
26    return matchobj.group(1) + matchobj.group(2) + "#" * len(matchobj.group(3))
27
28
29def mask_comments(input):
30    """Mask the quoted strings so we skip braces inside quoted strings."""
31    search_re = re.compile(r"(.*?)(#)(.*)")
32    return [search_re.sub(comment_replace, line) for line in input]
33
34
35def quote_replace(matchobj):
36    return "{}{}{}{}".format(
37        matchobj.group(1),
38        matchobj.group(2),
39        "x" * len(matchobj.group(3)),
40        matchobj.group(2),
41    )
42
43
44def mask_quotes(input):
45    """Mask the quoted strings so we skip braces inside quoted strings."""
46    search_re = re.compile(r"(.*?)" + QUOTE_RE_STR)
47    return [search_re.sub(quote_replace, line) for line in input]
48
49
50def do_split(input, masked_input, search_re):
51    output = []
52    mask_output = []
53    for (line, masked_line) in zip(input, masked_input):
54        m = search_re.match(masked_line)
55        while m:
56            split = len(m.group(1))
57            line = line[:split] + r"\n" + line[split:]
58            masked_line = masked_line[:split] + r"\n" + masked_line[split:]
59            m = search_re.match(masked_line)
60        output.extend(line.split(r"\n"))
61        mask_output.extend(masked_line.split(r"\n"))
62    return (output, mask_output)
63
64
65def split_double_braces(input):
66    """Masks out the quotes and comments, and then splits appropriate
67  lines (lines that matche the double_*_brace re's above) before
68  indenting them below.
69
70  These are used to split lines which have multiple braces on them, so
71  that the indentation looks prettier when all laid out (e.g. closing
72  braces make a nice diagonal line).
73  """
74    double_open_brace_re = re.compile(r"(.*?[\[\{\(,])(\s*)([\[\{\(])")
75    double_close_brace_re = re.compile(r"(.*?[\]\}\)],?)(\s*)([\]\}\)])")
76
77    masked_input = mask_quotes(input)
78    masked_input = mask_comments(masked_input)
79
80    (output, mask_output) = do_split(input, masked_input, double_open_brace_re)
81    (output, mask_output) = do_split(output, mask_output, double_close_brace_re)
82
83    return output
84
85
86def count_braces(line):
87    """keeps track of the number of braces on a given line and returns the result.
88
89  It starts at zero and subtracts for closed braces, and adds for open braces.
90  """
91    open_braces = ["[", "(", "{"]
92    close_braces = ["]", ")", "}"]
93    closing_prefix_re = re.compile(r"[^\s\]\}\)]\s*[\]\}\)]+,?\s*$")
94    cnt = 0
95    stripline = COMMENT_RE.sub(r"", line)
96    stripline = QUOTE_RE.sub(r"''", stripline)
97    for char in stripline:
98        for brace in open_braces:
99            if char == brace:
100                cnt += 1
101        for brace in close_braces:
102            if char == brace:
103                cnt -= 1
104
105    after = False
106    if cnt > 0:
107        after = True
108
109    # This catches the special case of a closing brace having something
110    # other than just whitespace ahead of it -- we don't want to
111    # unindent that until after this line is printed so it stays with
112    # the previous indentation level.
113    if cnt < 0 and closing_prefix_re.match(stripline):
114        after = True
115    return (cnt, after)
116
117
118def prettyprint_input(lines):
119    """Does the main work of indenting the input based on the brace counts."""
120    indent = 0
121    basic_offset = 2
122    for line in lines:
123        if COMMENT_RE.match(line):
124            print(line)
125        else:
126            line = line.strip("\r\n\t ")  # Otherwise doesn't strip \r on Unix.
127            if len(line) > 0:
128                (brace_diff, after) = count_braces(line)
129                if brace_diff != 0:
130                    if after:
131                        print(" " * (basic_offset * indent) + line)
132                        indent += brace_diff
133                    else:
134                        indent += brace_diff
135                        print(" " * (basic_offset * indent) + line)
136                else:
137                    print(" " * (basic_offset * indent) + line)
138            else:
139                print("")
140
141
142def main():
143    if len(sys.argv) > 1:
144        data = open(sys.argv[1]).read().splitlines()
145    else:
146        data = sys.stdin.read().splitlines()
147    # Split up the double braces.
148    lines = split_double_braces(data)
149
150    # Indent and print the output.
151    prettyprint_input(lines)
152    return 0
153
154
155if __name__ == "__main__":
156    sys.exit(main())
157