Created
April 25, 2021 13:22
-
-
Save john-tornblom/5a9db5a3ea7b34890858517b3be86b5e to your computer and use it in GitHub Desktop.
Replace literal strings in source code with gettext macro calls
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# encoding: utf-8 | |
# Copyright (C) 2021 John Törnblom | |
""" | |
Replace literal strings in source code with gettext macro calls. | |
Requires python3 with clang bindings. In ubuntu: | |
sudo apt-get install python3-clang | |
""" | |
import ctypes.util | |
import fileinput | |
import glob | |
import os | |
from dataclasses import dataclass | |
from clang import cindex | |
from clang.cindex import Index, Config, CursorKind | |
for libname in ['clang', 'clang-9', 'clang-10']: | |
filename = ctypes.util.find_library(libname) | |
if filename: | |
Config.set_library_file(filename) | |
break | |
@dataclass(frozen=True) | |
class StringDescription: | |
offset : int | |
length : int | |
local : bool | |
def is_scope(kind): | |
return kind in (CursorKind.FUNCTION_DECL, CursorKind.CXX_METHOD, | |
CursorKind.CONSTRUCTOR, CursorKind.DESTRUCTOR, | |
CursorKind.CONVERSION_FUNCTION) | |
class StringTracking: | |
current_scope = None | |
filename = None | |
strings = None | |
def __init__(self, filename): | |
self.filename = os.path.abspath(filename) | |
self.current_scope = 0 | |
self.strings = set() | |
def analyze(self, node): | |
if not node.location.file: | |
return | |
filename = os.path.abspath(node.location.file.name) | |
if filename != self.filename: | |
return | |
if is_scope(node.kind): | |
self.current_scope += 1 | |
for child in node.get_children(): | |
self.analyze(child) | |
if is_scope(node.kind): | |
self.current_scope -= 1 | |
if node.kind != CursorKind.STRING_LITERAL: | |
return | |
sd = StringDescription(node.location.offset, | |
len(node.spelling), | |
self.current_scope > 0) | |
self.strings.add(sd) | |
def patch_strings(filename, strings): | |
with open(filename, 'rb') as f: | |
code = list(f.read()) | |
for s in sorted(strings, key=lambda s: -s.offset): | |
pos = s.offset | |
# skip macros that expands to string literals | |
if code[pos] != ord('"'): | |
continue | |
if not s.local: | |
code.insert(pos, ord('N')) | |
pos += 1 | |
code.insert(pos, ord('_')) | |
else: | |
code.insert(pos, ord('_')) | |
code.insert(pos+1, ord('(')) | |
code.insert(pos+1+s.length+1, ord(')')) | |
with open(filename, 'wb') as f: | |
f.write(bytearray(code)) | |
def main(): | |
cdb = cindex.CompilationDatabase.fromDirectory('build') | |
for cmd in cdb.getAllCompileCommands(): | |
if cmd.filename.find('Source') < 0: continue | |
index = Index.create() | |
tu = index.parse(None, list(cmd.arguments)).cursor | |
st = StringTracking(cmd.filename) | |
for child in tu.get_children(): | |
st.analyze(child) | |
if st.strings: | |
print(cmd.filename) | |
patch_strings(cmd.filename, st.strings) | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment