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