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