session-ios/SignalServiceKit/Utilities/extract_analytics_event_nam...

374 lines
12 KiB
Python
Executable File

#!/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)