#!/usr/bin/env python # -*- coding: utf-8 -*- import os import sys import subprocess import datetime import argparse import commands import re # This script is used to extract analytics event names from the codebase, # and convert them to constants in OWSAnalyticsEvents. git_repo_path = os.path.abspath(subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).strip()) def splitall(path): allparts = [] while 1: parts = os.path.split(path) if parts[0] == path: # sentinel for absolute paths allparts.insert(0, parts[0]) break elif parts[1] == path: # sentinel for relative paths allparts.insert(0, parts[1]) break else: path = parts[0] allparts.insert(0, parts[1]) return allparts def objc_name_for_event_name(event_name): while True: index = event_name.find('_') if index < 0: break if index >= len(event_name) - 1: break nextChar = event_name[index + 1] event_name = event_name[:index] + nextChar.upper() + event_name[index + 2:] return event_name event_names = [] def process(filepath, c_macros, swift_macros): short_filepath = filepath[len(git_repo_path):] if short_filepath.startswith(os.sep): short_filepath = short_filepath[len(os.sep):] filename = os.path.basename(filepath) if filename.startswith('.'): return if filename == 'OWSAnalytics.h': return file_ext = os.path.splitext(filename)[1] is_swift = file_ext in ('.swift') if is_swift: macros = swift_macros else: macros = c_macros # print short_filepath, is_swift with open(filepath, 'rt') as f: text = f.read() replacement_map = {} position = 0 has_printed_filename = False while True: best_match = None best_macro = None for macro in macros: pattern = r'''%s\(([^,\)]+)[,\)]''' % macro # print '\t pattern', pattern matcher = re.compile(pattern) # matcher = re.compile(r'#define (OWSProd)') match = matcher.search(text, pos=position) if match: event_name = match.group(1).strip() # Ignore swift func definitions if is_swift and ':' in event_name: continue # print '\t', 'event_name', event_name if not best_match: pass elif best_match.start(1) > match.start(1): pass else: continue best_match = match best_macro = macro # TODO: if not best_match: break position = best_match.end(1) if not has_printed_filename: has_printed_filename = True print short_filepath raw_event_name = best_match.group(1).strip() if is_swift: pattern = r'^"(.+)"$' else: pattern = r'^@"(.+)"$' # print 'pattern:', pattern matcher = re.compile(pattern) # matcher = re.compile(r'#define (OWSProd)') match = matcher.search(raw_event_name) if match: event_name = match.group(1).strip() else: print '\t', 'Ignoring event: _%s_' % raw_event_name continue event_names.append(event_name) print '\t', 'event_name', event_name if is_swift: before = '"%s"' % event_name after = 'OWSAnalyticsEvents.%s()' % objc_name_for_event_name(event_name) else: before = '@"%s"' % event_name after = '[OWSAnalyticsEvents %s]' % objc_name_for_event_name(event_name) replacement_map[before] = after # macros.append(macro) # break # print 'replacement_map', replacement_map for before in replacement_map: after = replacement_map[before] text = text.replace(before, after) # if original_text == text: # return print 'Updating:', short_filepath with open(filepath, 'wt') as f: f.write(text) def should_ignore_path(path): ignore_paths = [ os.path.join(git_repo_path, '.git') ] for ignore_path in ignore_paths: if path.startswith(ignore_path): return True for component in splitall(path): if component.startswith('.'): return True if component.endswith('.framework'): return True if component in ('Pods', 'ThirdParty', 'Carthage',): return True return False def process_if_appropriate(filepath, c_macros, swift_macros): filename = os.path.basename(filepath) if filename.startswith('.'): return file_ext = os.path.splitext(filename)[1] if file_ext not in ('.h', '.hpp', '.cpp', '.m', '.mm', '.pch', '.swift'): return if should_ignore_path(filepath): return process(filepath, c_macros, swift_macros) def extract_macros(filepath): filename = os.path.basename(filepath) file_ext = os.path.splitext(filename)[1] is_swift = file_ext in ('.swift') macros = [] with open(filepath, 'rt') as f: text = f.read() lines = text.split('\n') for line in lines: # Match lines of this form: # #define OWSProdCritical(__eventName) ... if is_swift: matcher = re.compile(r'func (OWSProd[^\(]+)\(.+[,\)]') else: matcher = re.compile(r'#define (OWSProd[^\(]+)\(.+[,\)]') # matcher = re.compile(r'#define (OWSProd)') match = matcher.search(line) if match: macro = match.group(1).strip() # print 'macro', macro macros.append(macro) return macros def update_event_names(header_file_path, source_file_path): # global event_names # event_names = sorted(set(event_names)) code_generation_marker = '#pragma mark - Code Generation Marker' # Source filepath = source_file_path with open(filepath, 'rt') as f: text = f.read() code_generation_start = text.find(code_generation_marker) code_generation_end = text.rfind(code_generation_marker) if code_generation_start < 0: print 'Could not find marker in file:', file sys.exit(1) if code_generation_end < 0 or code_generation_end == code_generation_start: print 'Could not find marker in file:', file sys.exit(1) event_name_map = {} print print 'Parsing old generated code' print old_generated = text[code_generation_start + len(code_generation_marker):code_generation_end] # print 'old_generated', old_generated for split in old_generated.split('+'): split = split.strip() # print 'split:', split if not split: continue # Example: #(NSString *)call_service_call_already_set #{ # return @"call_service_call_already_set"; #} pattern = r'\(NSString \*\)([^\s\r\n\t]+)[\s\r\n\t]' matcher = re.compile(pattern) match = matcher.search(split) if not match: print 'Could not parse:', split print 'In file:', filepath sys.exit(1) method_name = match.group(1).strip() print 'method_name:', method_name pattern = r'return @"(.+)";' matcher = re.compile(pattern) match = matcher.search(split) if not match: print 'Could not parse:', split print 'In file:', filepath sys.exit(1) event_name = match.group(1).strip() print 'event_name:', event_name event_name_map[event_name] = method_name print all_event_names = sorted(set(event_name_map.keys() + event_names)) print 'all_event_names', all_event_names generated = code_generation_marker for event_name in all_event_names: # Example: # + (NSString *)call_service_call_already_set; if event_name in event_name_map: objc_name = event_name_map[event_name] else: objc_name = objc_name_for_event_name(event_name) text_for_event = '''+ (NSString *)%s { return @"%s"; }''' % (objc_name, event_name) generated = generated + '\n\n' + text_for_event generated = generated + '\n\n' + code_generation_marker print 'generated', generated new_text = text[:code_generation_start] + generated + text[code_generation_end + len(code_generation_marker):] print 'text', new_text with open(filepath, 'wt') as f: f.write(new_text) # Header filepath = header_file_path with open(filepath, 'rt') as f: text = f.read() code_generation_start = text.find(code_generation_marker) code_generation_end = text.rfind(code_generation_marker) if code_generation_start < 0: print 'Could not find marker in file:', file sys.exit(1) if code_generation_end < 0 or code_generation_end == code_generation_start: print 'Could not find marker in file:', file sys.exit(1) generated = code_generation_marker for event_name in all_event_names: # Example: # + (NSString *)call_service_call_already_set; objc_name = objc_name_for_event_name(event_name) text_for_event = '+ (NSString *)%s;' % (objc_name,) generated = generated + '\n\n' + text_for_event generated = generated + '\n\n' + code_generation_marker print 'generated', generated new_text = text[:code_generation_start] + generated + text[code_generation_end + len(code_generation_marker):] print 'text', new_text with open(filepath, 'wt') as f: f.write(new_text) if __name__ == "__main__": # print 'git_repo_path', git_repo_path macros_header_file_path = os.path.join(git_repo_path, 'SignalServiceKit', 'src', 'Util', 'OWSAnalytics.h') if not os.path.exists(macros_header_file_path): print 'Macros header does not exist:', macros_header_file_path sys.exit(1) c_macros = extract_macros(macros_header_file_path) print 'c_macros:', c_macros macros_header_file_path = os.path.join(git_repo_path, 'Signal', 'src', 'util', 'OWSAnalytics.swift') if not os.path.exists(macros_header_file_path): print 'Macros header does not exist:', macros_header_file_path sys.exit(1) swift_macros = extract_macros(macros_header_file_path) print 'swift_macros:', swift_macros event_names_header_file_path = os.path.join(git_repo_path, 'SignalServiceKit', 'src', 'Util', 'OWSAnalyticsEvents.h') if not os.path.exists(event_names_header_file_path): print 'event_names_header_file_path does not exist:', event_names_header_file_path sys.exit(1) event_names_source_file_path = os.path.join(git_repo_path, 'SignalServiceKit', 'src', 'Util', 'OWSAnalyticsEvents.m') if not os.path.exists(event_names_source_file_path): print 'event_names_source_file_path does not exist:', event_names_source_file_path sys.exit(1) for rootdir, dirnames, filenames in os.walk(git_repo_path): for filename in filenames: file_path = os.path.abspath(os.path.join(rootdir, filename)) process_if_appropriate(file_path, c_macros, swift_macros) print print 'event_names', sorted(set(event_names)) update_event_names(event_names_header_file_path, event_names_source_file_path)