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